KeySet、Values、EntrySet 操作其实都差不多,其内部并不存储任何数据,这三个内部内都是封装了方便外部对于 table 的 key,value,node 的遍历和操作
一、KeySet
是 HashMap 中所有 key 的集合
// HashMap 的 table 中所有 key 的集合
final class KeySet extends AbstractSet<K> {
// 获取到的其实是 table 的元素个数,并不是 key 的个数,不过其实也一样
public final int size() { return size; }
// 调用的外部的 clear(),所以调用这个方法会清空 table 中的数据,
// 里面的原理其实不是 table 引用重指向,而是 table 中的桶赋空
public final void clear() { HashMap.this.clear(); }
// 返回一个用于迭代 KeySet 的迭代器
public final Iterator<K> iterator() { return new KeyIterator(); }
// 调用外部 containsKey(o) 方法,用于查找 table 是否存在 key 为 o 的元素
public final boolean contains(Object o) { return containsKey(o); }
// 根据 key 移除元素,同样和对外暴露的 remove(Object key),功能一样
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
// 单看方法名的话就是一个分割迭代器了,同样用于迭代,但是分割了,所以可以并行迭代,具体操作后面关于并行迭代器再说~~
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
// jdk1.8 中出现的函数式编程,里面的参数是一个函数式接口,
// 也就是说可以传入该接口的实现,也可以传入一个 Lambda 表达式(箭头函数)
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
二、Values
是 HashMap 中所有 value 的集合
//HashMap 的 table 中所有 value 的集合
final class Values extends AbstractCollection<V> {
// 获取到的其实是 table 的元素个数,并不是 value 的个数,不过其实也一样
public final int size() { return size; }
// 和 KeySet 的功能一样
public final void clear() { HashMap.this.clear(); }
// 获取一个用于迭代 value 的迭代器
public final Iterator<V> iterator() { return new ValueIterator(); }
// 和 table 对外暴露的 containsValue(Object value),方法一样,判断 table 中是否存在值为 o 的元素
public final boolean contains(Object o) { return containsValue(o); }
// 获取一个 value 的分割迭代器,功能和操作类似 KeySet
public final Spliterator<V> spliterator() {
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
// 功能和操作类似 KeySet
public final void forEach(Consumer<? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
三、EntrySet
EntrySet 就不解释了,原理都一样,细节稍有不同,这三者较大的不同点在于 KeySet 和 EntrySet 继承于 AbstractSet,Values 继承于 AbstractCollection,这个就和里面的值能否重复有关了,KeySet 和 EntrySet 不允许重复,Values 允许有相同
另外一个要注意的是利用 EntrySet 移除节点时,会对 table 的元素进行重排,慎用~