zoukankan      html  css  js  c++  java
  • HashSet源码解析&Map迭代器

    今天的主角是HashSet,Set是什么东东,当然也是一种java容器了。

        那么今天的HashSet它又是怎么一回事的,他的存在又是为了解决什么问题呢?
         先来看下Set的特点:Set元素无顺序,且元素不可以重复。 。想到了什么?无顺序,由于散列的缘故;不可重复,HashMap的key就是不能重复的。是的,你有想对了。HashSet就是基于HashMap的key来实现的,整个HashSet中基本所有方法都是调用的HashMap的方法。利用HashMap可以实现两个卖点:1.不可重复,2.快速查找(contains)
         一起来看下吧:
     
    1.定义
    1
    2
    3
    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable

    我们看到HashSet继承了AbstractSet抽象类,并实现了Set、Cloneable、Serializable接口。AbstractSet是一个抽象类,对一些基础的set操作进行封装。继续来看下Set接口的定义:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public interface Set<E> extends Collection<E> {
        // Query Operations
        int size();
        boolean isEmpty();
        boolean contains(Object o);
        Iterator<E> iterator();
        Object[] toArray();
        <T> T[] toArray(T[] a);
        // Modification Operations
        boolean add(E e);
        boolean remove(Object o);
        // Bulk Operations
        boolean containsAll(Collection<?> c);
        boolean addAll(Collection<? extends E> c);
        boolean retainAll(Collection<?> c);
        boolean removeAll(Collection<?> c);
        void clear();
        // Comparison and hashing
        boolean equals(Object o);
        int hashCode();
    }

    发现了什么,Set接口和java.util.List接口一样也实现了Collection接口,但是Set和List所不同的是,Set没有get等跟下标先关的一些操作方法,那怎么取值呢?Iterator还记得吗,使用迭代器对不对。(不明白的回去看Iterator讲解

    2.底层存储

    1
    2
    3
    4
    5
    6
    // 底层使用HashMap来保存HashSet的元素
        private transient HashMap<E,Object> map;
     
        // Dummy value to associate with an Object in the backing Map
        // 由于Set只使用到了HashMap的key,所以此处定义一个静态的常量Object类,来充当HashMap的value
        private static final Object PRESENT = new Object();

    看到这里就明白了,和我们前面说的一样,HashSet是用HashMap来保存数据,而主要使用到的就是HashMap的key。

    看到private static final Object PRESENT = new Object();不知道你有没有一点疑问呢。这里使用一个静态的常量Object类来充当HashMap的value,既然这里map的value是没有意义的,为什么不直接使用null值来充当value呢?比如写成这样子privatefinal Object PRESENT = null;我们都知道的是,Java首先将变量PRESENT分配在栈空间,而将new出来的Object分配到堆空间,这里的new Object()是占用堆内存的(一个空的Object对象占用8byte),而null值我们知道,是不会在堆空间分配内存的。那么想一想这里为什么不使用null值。想到什么吗,看一个异常类java.lang.NullPointerException, 噢买尬,这绝对是Java程序员的一个噩梦,这是所有Java程序猿都会遇到的一个异常,你看到这个异常你以为很好解决,但是有些时候也不是那么容易解决,Java号称没有指针,但是处处碰到NullPointerException。所以啊,为了从根源上避免NullPointerException的出现,浪费8个byte又怎么样,在下面的代码中我再也不会写这样的代码啦if (xxx == null) { … } else {….},好爽。

    3.构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    /**
         * 使用HashMap的默认容量大小16和默认加载因子0.75初始化map,构造一个HashSet
         */
        public HashSet() {
            map = new HashMap<E,Object>();
        }
     
        /**
         * 构造一个指定Collection参数的HashSet,这里不仅仅是Set,只要实现Collection接口的容器都可以
         */
        public HashSet(Collection<? extends E> c) {
            map = new HashMap<E,Object>(Math. max((int) (c.size()/.75f) + 1, 16));
           // 使用Collection实现的Iterator迭代器,将集合c的元素一个个加入HashSet中
           addAll(c);
        }
     
        /**
         * 使用指定的初始容量大小和加载因子初始化map,构造一个HashSet
         */
        public HashSet( int initialCapacity, float loadFactor) {
            map = new HashMap<E,Object>(initialCapacity, loadFactor);
        }
     
        /**
         * 使用指定的初始容量大小和默认的加载因子0.75初始化map,构造一个HashSet
         */
        public HashSet( int initialCapacity) {
            map = new HashMap<E,Object>(initialCapacity);
        }
     
        /**
         * 不对外公开的一个构造方法(默认default修饰),底层构造的是LinkedHashMap,dummy只是一个标示参数,无具体意义
         */
        HashSet( int initialCapacity, float loadFactor, boolean dummy) {
            map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
    }

    从构造方法可以很轻松的看出,HashSet的底层是一个HashMap,理解了HashMap后,这里没什么可说的。只有最后一个构造方法有写区别,这里构造的是LinkedHashMap,该方法不对外公开,实际上是提供给LinkedHashSet使用的,而第三个参数dummy是无意义的,只是为了区分其他构造方法。

    4.增加和删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
        /**
         * 利用HashMap的put方法实现add方法
         */
        public boolean add(E e) {
            return map .put(e, PRESENT)== null;
        }
     
        /**
         * 利用HashMap的remove方法实现remove方法
         */
        public boolean remove(Object o) {
            return map .remove(o)==PRESENT;
        }
     
        /**
         * 添加一个集合到HashSet中,该方法在AbstractCollection中
         */
        public boolean addAll(Collection<? extends E> c) {
            boolean modified = false;
           // 取得集合c迭代器Iterator
           Iterator<? extends E> e = c.iterator();
           // 遍历迭代器
            while (e.hasNext()) {
               // 将集合c的每个元素加入到HashSet中
               if (add(e.next()))
                  modified = true;
           }
            return modified;
        }
     
        /**
         * 删除指定集合c中的所有元素,该方法在AbstractSet中
         */
        public boolean removeAll(Collection<?> c) {
            boolean modified = false;
     
            // 判断当前HashSet元素个数和指定集合c的元素个数,目的是减少遍历次数
            if (size() > c.size()) {
                // 如果当前HashSet元素多,则遍历集合c,将集合c中的元素一个个删除
                for (Iterator<?> i = c.iterator(); i.hasNext(); )
                    modified |= remove(i.next());
            } else {
                // 如果集合c元素多,则遍历当前HashSet,将集合c中包含的元素一个个删除
                for (Iterator<?> i = iterator(); i.hasNext(); ) {
                    if (c.contains(i.next())) {
                        i.remove();
                        modified = true;
                    }
                }
            }
            return modified;
    }
     

    5.是否包含

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
         * 利用HashMap的containsKey方法实现contains方法
         */
        public boolean contains(Object o) {
            return map .containsKey(o);
        }
     
        /**
         * 检查是否包含指定集合中所有元素,该方法在AbstractCollection中
         */
        public boolean containsAll(Collection<?> c) {
           // 取得集合c的迭代器Iterator
           Iterator<?> e = c.iterator();
           // 遍历迭代器,只要集合c中有一个元素不属于当前HashSet,则返回false
            while (e.hasNext())
               if (!contains(e.next()))
                   return false;
            return true;
    }

      由于HashMap基于hash表实现,hash表实现的容器最重要的一点就是可以快速存取,那么HashSet对于contains方法,利用HashMap的containsKey方法,效率是非常之快的。在我看来,这个方法也是HashSet最核心的卖点方法之一。

    6.容量检查

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
         * Returns the number of elements in this set (its cardinality).
         *
         * @return the number of elements in this set (its cardinality)
         */
        public int size() {
            return map .size();
        }
     
        /**
         * Returns <tt>true</tt> if this set contains no elements.
         *
         * @return <tt> true</tt> if this set contains no elements
         */
        public boolean isEmpty() {
            return map .isEmpty();
        }
      以上代码都很简单,因为基本都是基于HashMap实现,只要理解了HashMap,HashSet理解起来真的是小菜一碟了。
         那么HashSet就结束了。。。等等,不对还有一个东西,那就是迭代器,在HashMap和LinkedHashMap中都说过,这两个的迭代器实现都要依赖Set接口,下面就让我们先看下HashSet的迭代器吧。
     
    7.迭代器
     
         7.1 HashMap的迭代器
     
         在《Iterator设计模式》中,我们分析了,实现Iterator迭代器的几个角色,并且自己简单实现了一个。而且我们看到Collection实现了Iterable接口,并且要求其子类实现一个返回Iterator接口的iterator()方法。那么既然HashSet是Collection的孙子类,那么HashSet也应该实现了一个返回Iterator接口的iterator()方法,对不对,我们去看看。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
         * Returns an iterator over the elements in this set.  The elements
         * are returned in no particular order.
         *
         * @return an Iterator over the elements in this set
         * @see ConcurrentModificationException
         */
        public Iterator<E> iterator() {
            return map .keySet().iterator();
        }

    我cha,咋回事,HashSet的iterator()方法竟然也是利用HashMap实现的,我们去看看HashMap的keySet()方法是什么鬼。

    1
    2
    3
    4
    public Set<K> keySet() {
            Set<K> ks = keySet;
            return (ks != null ? ks : (keySet = new KeySet()));
    }

    HashMap的keySet()方法的返回值竟然是一个Set,具体实现是一个叫KeySet的东东,KeySet又是什么鬼。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    private final class KeySet extends AbstractSet<K> {
            public Iterator<K> iterator() {
                return newKeyIterator();
            }
            public int size() {
                return size ;
            }
            public boolean contains(Object o) {
                return containsKey(o);
            }
            public boolean remove(Object o) {
                return HashMap.this.removeEntryForKey(o) != null;
            }
            public void clear() {
                HashMap. this.clear();
            }
    }

    哦,KeySet是一个实现了AbstractSet的HashMap的内部类。而KeySet的iterator()方法返回的是一个newKeyIterator()方法,好绕好绕,头晕了。

    1
    2
    3
    Iterator<K> newKeyIterator()   {
            return new KeyIterator();
    }

    newKeyIterator()方法返回的又是一个KeyIterator()方法,what are you 弄啥嘞?

    1
    2
    3
    4
    5
    private final class KeyIterator extends HashIterator<K> {
            public K next() {
                return nextEntry().getKey();
            }
    }

      好吧,不想说什么了,继续往下看吧。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    private abstract class HashIterator<E> implements Iterator<E> {
            // 下一个需要返回的节点
            Entry<K,V> next;   // next entry to return
            int expectedModCount ;     // For fast-fail
            int index ;          // current slot
            // 当前需要返回的节点
            Entry<K,V> current;// current entry
     
            HashIterator() {
                expectedModCount = modCount ;
                if (size > 0) { // advance to first entry
                    Entry[] t = table;
                   // 初始化next参数,将next赋值为HashMap底层的第一个不为null节点
                    while (index < t.length && ( next = t[index ++]) == null)
                        ;
                }
            }
     
            public final boolean hasNext() {
                return next != null;
            }
     
            final Entry<K,V> nextEntry() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                // 取得HashMap底层数组中链表的一个节点
                Entry<K,V> e = next;
                if (e == null)
                    throw new NoSuchElementException();
     
                // 将next指向下一个节点,并判断是否为null
                if ((next = e.next) == null) {
                    Entry[] t = table;
                    // 如果为null,则遍历真个数组,知道取得一个不为null的节点
                    while (index < t.length && ( next = t[index ++]) == null)
                        ;
                }
               current = e;
               // 返回当前节点
                return e;
            }
     
            public void remove() {
                if (current == null)
                    throw new IllegalStateException();
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                Object k = current.key ;
                current = null;
                HashMap. this.removeEntryForKey(k);
                expectedModCount = modCount ;
            }
     
    }
     
      最终找到了HashIterator这个类(也是HashMap的内部类),好累。。。主要看下nextEntry()这个方法,该方法主要思路是,首选拿去HashMap低层数组中第一个不为null的节点,每次调用迭代器的next()方法,就用该节点next一下,当当前节点next到最后为null,就拿数组中下一个不为null的节点继续遍历。什么意思呢,就是循环从数组第一个索引开始,遍历整个Hash表。
         至于你问我Iterator实现起来本来挺容易的一件事,为什么HashMap搞得这么复杂,我只想说不要问我,我也不知道。。。
         当然map是一个k-v键值对的容器,除了有对key的迭代keySet(),当然还有对value的迭代values(为什么value的迭代不是返回Set,因为value是可以重复的嘛),还有对整个键值对k-v的迭代entrySet(),和上面的代码都是一个原理,这里就不多讲了。
     
         7.2 LinkedHashMap的迭代器
     
         看完HashMap的Iterator实现,再来看下LinkedHashMap是怎么实现的吧(不从头开始找了,直接看最核心代码吧)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    private abstract class LinkedHashIterator<T> implements Iterator<T> {
           // header.after为LinkedHashMap双向链表的第一个节点,因为LinkedHashMap的header节点不保存数据
           Entry<K,V> nextEntry    = header .after;
           // 最后一次返回的节点
           Entry<K,V> lastReturned = null;
     
            /**
            * The modCount value that the iterator believes that the backing
            * List should have.  If this expectation is violated, the iterator
            * has detected concurrent modification.
            */
            int expectedModCount = modCount;
     
            public boolean hasNext() {
                return nextEntry != header;
           }
     
            public void remove() {
               if (lastReturned == null)
                   throw new IllegalStateException();
               if (modCount != expectedModCount)
                   throw new ConcurrentModificationException();
     
                LinkedHashMap. this.remove(lastReturned .key);
                lastReturned = null;
                expectedModCount = modCount ;
           }
     
           Entry<K,V> nextEntry() {
               if (modCount != expectedModCount)
                   throw new ConcurrentModificationException();
                if (nextEntry == header)
                    throw new NoSuchElementException();
     
                // 将要返回的节点nextEntry赋值给lastReturned
                // 将nextEntry赋值给临时变量e(因为接下来nextEntry要指向下一个节点)
                Entry<K,V> e = lastReturned = nextEntry ;
                // 将nextEntry指向下一个节点
                nextEntry = e.after ;
                // 放回当前需返回的节点
                return e;
           }
    }
      可以看出LinkedHashMap的迭代器,不在遍历真个Hash表,而只是遍历其自身维护的双向循环链表,这样就不在需要对数组中是否为空节点进行的判断。所以说LinkedHashMap在迭代器上的效率面通常是高与HashMap的,既然这里是通常,那么什么时候不通常呢,那就是HashMap中元素较少,分布均匀,没有空节点的时候。
     
         Map的迭代器源码读起来比较不太容易懂(主要是各种调用,各种内部类,核心代码不好找),但是找到核心代码后,逻辑原理也就很容易看懂了,当然前提是建立在了解了HashMap和LinkedHashMap的底层存储结构。
     
         额,这一篇确实是讲HashSet的,不是讲Map,这算不算走题了。。。
     
         HashSet 完!
  • 相关阅读:
    Windows环境下使用cygwin ndk_r9c编译FFmpeg
    android 开发环境搭建
    POJ 2559 单调栈入门,三种代码实现
    poj 2100 尺取法(尺度法)
    尺取算法 入门+模板+例题
    POJ 3579 Median 二分+思维
    POJ 3685 Matrix 二分套二分
    CodeForces 371C Hamburgers 二分
    HDU 4355 Party All the Time 三分算法
    HDU 3613 Best Reward Manacher算法
  • 原文地址:https://www.cnblogs.com/vn2019/p/5167742.html
Copyright © 2011-2022 走看看