HashMap之keyset() 方法底层原理
获取HashMap所有的键,通常调用方法keyset()即可返回所有的key集合。那么keyset()的工作原理是什么?它真的会维护一个Set吗,当Map的键值对发生变化,就来更新这个Set?
如果真的是这样,那么HashMap的性能将会大打折扣,并且存储空间的消耗也会翻倍。
其实,HashMap采用了一种比较巧妙的方式实现keyset。源码如下:
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new LinkedKeySet();
keySet = ks;
}
return ks;
}
final class LinkedKeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<K> iterator() {
return new LinkedKeyIterator();
}
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return Spliterators.spliterator(this, Spliterator.SIZED |
Spliterator.ORDERED |
Spliterator.DISTINCT);
}
public final void forEach(Consumer<? super K> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.key);
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
当我们调用keyset方法时,会先判断keyset是否已经初始化,如果没有,则new LinkedKeySet 对象,然后更新成员变量keyset,下次再调用时,则直接返回已经初始化的LinkedKeySet对象引用,不再初始化LinkedKeySet对象。
分析LinkedKeySet,发现该内部类只有无参的构造方法,并且构造方法仅仅new 了一个空对象,并没有给Set集合初始化值,那么keyset值从哪儿来呢?往下看,LinkedKeySet内部类有个方法iterator(),是集合类接口Set声明的iterator方法的一个具体实现,该方法会new一个迭代器。当我们做增强for循环时会调用该迭代器,该迭代器会遍历HashMap的各个节点,拿到key。
final class LinkedKeyIterator extends LinkedHashIterator
implements Iterator<K> {
public final K next() { return nextNode().getKey(); }
}
还有一个问题,当我们debug的时候,我们会发现,keyset()方法返回的set集合并非一个空集合,里面是有数据的,这是为什么呢?
原因是IDEA在debug时会默认调用toString()方法,所以我们debug看到的信息其实调用了父AbstractCollection的toString()方法。包括我们通过System.out.println(set)打印数据的时候,都会调用这个toString方法。
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
HashMap的values()、entrySet()方法也是如此。