[Java集合]Map源码分析:HashMap(下)

本文深入分析了Java HashMap的源码,主要聚焦于视图操作如keySet、values和entrySet的实现原理,特别是迭代器HashIterator的工作方式。同时,解释了为何使用transient修饰成员变量以避免不必要的序列化,并探讨了HashMap的序列化与反序列化策略,包括writeObject和readObject方法的作用。此外,提到了Spliterator在并行遍历中的应用。
摘要由CSDN通过智能技术生成

1. 视图

1.1 keySet()

父类AbstractMap的成员变量:

transient Set<K> keySet;

而keySet方法,也是覆盖了父类的方法:

//AbstractMap 中的keySet方法

    public Set<K> keySet() {
   
        Set<K> ks = keySet;
        if (ks == null) {
   
            ks = new AbstractSet<K>() {
   
                public Iterator<K> iterator() {
   
                    return new Iterator<K>() {
   
                        private Iterator<Entry<K,V>> i = entrySet().iterator();

                        public boolean hasNext() {
   
                            return i.hasNext();
                        }

                        public K next() {
   
                            return i.next().getKey();
                        }

                        public void remove() {
   
                            i.remove();
                        }
                    };
                }

                public int size() {
   
                    return AbstractMap.this.size();
                }

                public boolean isEmpty() {
   
                    return AbstractMap.this.isEmpty();
                }

                public void clear() {
   
                    AbstractMap.this.clear();
                }

                public boolean contains(Object k) {
   
                    return AbstractMap.this.containsKey(k);
                }
            };
            keySet = ks;
        }
        return ks;
    }

HashMap 中的keySet方法:

/**
 * 返回hashMap中所有key的视图。
 * 改变hashMap会影响到set,反之亦然。
 * 如果当迭代器迭代set时,hashMap被修改(除非是迭代器自己的remove()方法),迭代器的结果是不确定的。
 * set支持元素的删除,通过Iterator.remove、Set.remove、removeAll、retainAll、clear操作删除hashMap中对应的键值对。不支持add和addAll方法。
 *
 * @return 返回hashMap中所有key的set视图
 */
public Set<K> keySet() {
   
    //
    Set<K> ks = keySet;
    if (ks == null) {
   
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

可以看到,AbstractMap中keySet是一个AbstractSet类型,而覆盖后的keySet方法中,keySet被赋值为KeySet类型。翻翻构造器可以发现,在构造器中并没有初始化keySet,而是在KeySet方法中对keySet进行的初始化(HashMap中都是使用类似的懒加载机制),KeySet是HashMap中的一个内部类,让我们再来看看HashMap 中的内部类keySet:

/**
 * 内部类KeySet
 */
final class KeySet extends AbstractSet<K> {
   
    public final int size()                 {
    return size; }
    public final void clear()               {
    HashMap.this.clear(); }
    public final Iterator<K> iterator()     {
    return new KeyIterator(); }
    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 new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    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();
        }
    }
}

KeySet就是继承自AbstractSet,并覆盖了其中的大部分方法,遍历KeySet时,会使用其中的KeyIterator,至于Spliterator,是为并行遍历设计的,一般是用于Stream的并行操作。forEach方法则是用于遍历操作,将函数式接口操作action应用于每一个元素。
keySet里面的元素是什么时候放进去的呢?这个奥秘隐藏在KeySet的迭代器中,再回头看看,它的迭代器返回的是一个KeyIterator,而KeyIterator也是HashMap中的一个内部类,继承自HashMap中的另一个内部类HashIterator。

1.2 HashIterator

abstract class HashIterator {
   
        //指向下一个节点
        Node<K,V> next;
        //当前节点
        Node<K,V> current;
        //为实现 fast-fail 机制而设置的期望修改数
        int expectedModCount;
        //当前遍历到的序号
        int index;

        HashIterator() {
   
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) {
   
                // 移动到第一个非null节点
                do {
   } while (index < t.length && (next = t[index++]) == null);
            }
        }

        public final boolean hasNext() {
   
            return next != null;
        }

        final Node<K,V> nextNode() {
   
            Node<K,V>[] t;
            Node<K,V> e = next;
            // fast-fail 机制的实现 即在迭代器往后遍历时,每次都检测expectedModCount是否和modCount相等
            // 不相等则抛出ConcurrentModificationException异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            //如果遍历越界,则抛出NoSuchElementException异常
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
   
                //如果遍历到末尾,则跳到table中下一个不为null的节点处
                do {
   } while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
   
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            //移除节点
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

可以发现,在迭代器中,使用nextNode进行遍历时,先把next引用赋值给current,然后把next.next赋值给next,再获取了外部类HashMap中的table引用(t = table),这样就直接通过遍历table的方式来实现对key,value和entry的读取。
KeyIterator,ValueIterator,EntryIterator都是HashIterator的子类,实现也很简单,仅仅修改了泛型类型:

 final class KeyIterator extends HashIterator
        implements Iterator<K> {
   
        public final K next() {
    return nextNode().key; }
    }

    final class ValueIterator extends HashIterator
        implements Iterator<V> {
   
        public final V next() {
    return nextNode().value; }
    
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值