一、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();
}
}