深入扒 HashMap 源码 - 6.3 HashMap 内部类 KeySet、Values、EntrySet

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 的元素进行重排,慎用~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值