Map 几个常用方法的比较

一、containsValue(value)的区别

在 Map 体系中有提供判断某个值是否存在的方法 — containsValue(value),下面分别是 HashMap 和 LinkedHashMap 的两个方法。

1、HashMap#containsValue(value)

public boolean containsValue(Object value) {
    if (value == null)
        return containsNullValue();
    HashMapEntry[] tab = table;
    for (int i = 0; i < tab.length ; i++)
        for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
            if (value.equals(e.value))
                return true;
    return false;
}

2、LinkedHashMap#containsValue(value)

从下面的源码可以知道,LinkedHashMap#containsValue 方法跟 HashMap 的实现还有点区别的,它遍历的是双向链表,这样的效率就要 HashMap 遍历 table 数组,然后还有对 table 数组的每一个元素对应的链表(也就是整个 hash 表)进行遍历要高。因为 HashMap 的实现是双重 for 循环判断的,而双向链表只需要一个 for 即可完成判断。

hash表是由数组+单向链表组成,而由于使用hash算法,可能会导致散列不均匀,甚至数组的有些项是没有元素的(没有hash出对应的散列值),而LinkedHashMap的双向链表呢,是不存在空项的,所以LinkedHashMap的containsValue比HashMap的containsValue效率要好一些。

public boolean containsValue(Object value) {
    // Overridden to take advantage of faster iterator
    if (value==null) {
        for (LinkedHashMapEntry e = header.after; e != header; e = e.after)
            if (e.value==null)
                return true;
    } else {
        for (LinkedHashMapEntry e = header.after; e != header; e = e.after)
            if (value.equals(e.value))
                return true;
    }
    return false;
}

二、keySet() 和 entrySet 哪个遍历比较快?

1.HashMap#keySet()

示例代码

Set<String> keys = map.keySet();
Iterator<String> iter = keys.iterator();
while(iter.hasNext()){
    String key = iter.next();
    String value = map.get(key);
    System.out.println("key = "+key+";value = "+value);
}

源码分析

遍历 KeySet 集合最终会调用 iterator() 获取迭代器对象,然后遍历迭代器 Iterator 对象的 next() 方法获取到每一个 key 值。

public Set<K> keySet() {
    Set<K> ks = keySet;
    return (ks != null ? ks : (keySet = new KeySet()));
}

//遍历 KeySet 集合核心方法就是 iterator() 方法了
private final class KeySet extends AbstractSet<K> {

    public Iterator<K> iterator() {
        //遍历的核心方法
        return newKeyIterator();
    }
    ...
}


Iterator<K> newKeyIterator()   {
    return new KeyIterator();
}

private final class KeyIterator extends HashIterator<K> {
    public K next() {
        //遍历代码都会执行 next() 获取到 Map.Entry 对象
        //在这里可以看到它是先获取 Map.Entry 然后在获取 Entry 中对应的 key ,因为获取 key 集合之后,还要遍历 key 集合获取到对应的 value 值,多了一步循环判断的操作,因此效率要比直接获取 EntrySet 低下。
        return nextEntry().getKey();
    }
}

2.HashMap#entrySet()

示例代码:

for (Map.Entry<String, String> entry : map.entrySet()) {
    System.out.println("key:" + entry.getKey() + ";value:" + entry.getValue());
}

源码分析

entrySet 方法获取到的存储 Map.Entry 的 Set 集合,在方法 next() 中获取到的 Map.Entry 对象,那么相对于 keySet 先获取 key 集合然后再遍历获取 value 的方法, entrySet 一次性就获取 Entry 对象这种方式的效率会更好一些。

public Set<Map.Entry<K,V>> entrySet() {
    return entrySet0();
}

private Set<Map.Entry<K,V>> entrySet0() {
    Set<Map.Entry<K,V>> es = entrySet;
    return es != null ? es : (entrySet = new EntrySet());
}

private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            //核心方法
            return newEntryIterator();
        }
}

Iterator<Map.Entry<K,V>> newEntryIterator()   {
    return new EntryIterator();
}

private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
    public Map.Entry<K,V> next() {
        return nextEntry();
    }
}

//最后获取的就是一个个 Map.Entry 对象
final Entry<K,V> nextEntry() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    HashMapEntry<K,V> e = next;
    if (e == null)
        throw new NoSuchElementException();
    if ((next = e.next) == null) {
        HashMapEntry[] t = table;
        while (index < t.length && (next = t[index++]) == null)
            ;
    }
    current = e;
    return e;
}

三、remove /replace /put元素的效率

这里只分析 remove 方法

1、LinkedHashMap#remove(key)

因为 LinkedHashMap 没有实现 remove 方法因此去父类 Hashmap 查看。根据 key 移除某个对象,我们参考 LinkedHashMap 是如何实现的,因为 LinkedHashMap 的移除操作是基于 HashMap 实现,只要弄懂了 LinkedHashMap 的移除操作就明白了 HashMap 的移除操作了。

对于 HashMap 结构而言,其内部都是有数组+链表组成,每一个存储的单位但是一个个的 Entry 对象,但是 LinkedHashMap 的 Entry 还添加了 after 和 before 属性,表示前后两个 Entry ,这种方式构成了双向链表,也就是说在移除操作过程中处理要移除 hash 表的 Entry 对象,还要移除 双向链表的 Entry 对象,这就说明了 LinedHashMap 在移除数据方面效率比 HashMap 要低。

public V remove(Object key) {
    //根据 key 移除哈希表 table 对应的 Entry 对象
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.getValue());
}

final Entry<K,V> removeEntryForKey(Object key) {
    if (size == 0) {
        return null;
    }
    int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);
    int i = indexFor(hash, table.length);
    HashMapEntry<K,V> prev = table[i];
    HashMapEntry<K,V> e = prev;
    while (e != null) {
        HashMapEntry<K,V> next = e.next;
        Object k;
        //找到需要移除的 Entry 对象了,然后从哈希表中移除这个 Entry 对象。
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)
                table[i] = next;
            else
                prev.next = next;
            //找到需要移除的 Entry 对象了。前面已经从哈希表移除了,但是双向链表中维护这该对象。当前的 Entry 对象实际就是 LinkedHashMap.LinkedHashMapEntry 对象。如果当前对象是 HashMap 的话,recoreRemoval内部是一个空实现。
            //因此会调用 LinkedHashMapEntry.recoreRemoval(this)方法
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }
    return e;
}

//LinkedHashMapEntry 的源码
private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> {

    //recordRemoval() 被调用之后 remove()方法被调用
    private void remove() {
        before.after = after;
        after.before = before;
    }

    ...
    //当 HashMap 的 Entry 被移除了之后,该方法会被调用
    void recordRemoval(HashMap<K,V> m) {
        remove();
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值