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 完!

时间: 2024-10-13 11:37:25

HashSet源码解析&Map迭代器的相关文章

给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&amp;Map迭代器

今天的主角是HashSet,Set是什么东东,当然也是一种java容器了. 现在再看到Hash心底里有没有会心一笑呢,这里不再赘述hash的概念原理等一大堆东西了(不懂得需要先回去看下HashMap了),需要在啰嗦一句的是hash表是基于快速存取的角度设计的,也是一种典型的空间换时间的做法(这个在分析HashMap中都有讲过).那么今天的HashSet它又是怎么一回事的,他的存在又是为了解决什么问题呢? 先来看下Set的特点:Set元素无顺序,且元素不可以重复. .想到了什么?无顺序,由于散列的

HashSet源码解析

今天来介绍下HashSet.前面,我们已经系统的对List和Map进行了学习.接下来,我们开始可以学习Set.相信经过Map的了解之后,学习Set会容易很多.毕竟,Set的实现类都是基于Map来实现的(HashSet是通过HashMap实现的). 构造图如下: 蓝色线条:继承 绿色线条:接口实现 正文 对于HashSet而言,它是基于HashMap来实现的,底层采用HashMap来保存元素.所以如果对HashMap比较熟悉,那么HashSet是so easy!! HashSet简介 HashSe

第六章 HashSet源码解析

6.1.对于HashSet需要掌握以下几点 HashSet的创建:HashSet() 往HashSet中添加单个对象:即add(E)方法 删除HashSet中的对象:即remove(Object key)方法 判断对象是否存在于HashSet中:containsKey(Object key)  注:HashSet没有获取单个对象的方法,需要使用iterator 6.2.构建HashSet 源代码: //HashSet底层数据结构:通过hashmap的key不可重复的原则,使得存放入HashSet

Java HashSet源码解析

本解析源码来自JDK1.7,HashSet是基于HashMap实现的,方法实现大都直接调用HashMap的方法 另一篇HashMap的源码解析文章 概要 实现了Set接口,实际是靠HashMap实现的 不保证遍历时的顺序,不保证集合顺序的不变性 HashSet允许出现null值 假定Hash算法能很好的分散元素,查询的时间复杂度为O(1) 遍历的时间复杂度由set的size和其依靠的HashMap的capacity来决定 HashSet是非同步的可以通过Set s = Collections.s

给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析

原文出自吞噬天地,链接整理自ImportNew 给jdk写注释系列之jdk1.6容器(2):LinkedList源码解析 给jdk写注释系列之jdk1.6容器(3):Iterator设计模式 给jdk写注释系列之jdk1.6容器(4)-HashMap源码解析 给jdk写注释系列之jdk1.6容器(5)-LinkedHashMap源码解析 给jdk写注释系列之jdk1.6容器(6)-HashSet源码解析&Map迭代器 给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析 工作中

【转】Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例--不错

原文网址:http://www.cnblogs.com/skywang12345/p/3311252.html 概要 这一章,我们对HashSet进行学习.我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet.内容包括:第1部分 HashSet介绍第2部分 HashSet数据结构第3部分 HashSet源码解析(基于JDK1.6.0_45)第4部分 HashSet遍历方式第5部分 HashSet示例 转载请注明出处:http://www.cnblogs.

Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例

概要 这一章,我们对HashSet进行学习.我们先对HashSet有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashSet.内容包括:第1部分 HashSet介绍第2部分 HashSet数据结构第3部分 HashSet源码解析(基于JDK1.6.0_45)第4部分 HashSet遍历方式第5部分 HashSet示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3311252.html 第1部分 HashSet介绍 HashSet 简

深入Java源码解析容器类List、Set、Map

1 常用容器继承关系图先上一张网上的继承关系图个人觉得有些地方不是很准确,比如Iterator不是容器,只是一个操作遍历集合的方法接口,所以不应该放在里面.并且Map不应该继承自Collection.所以自己整理了一个常用继承关系图如下如上图所示,接下去会自顶向下解释重要的接口和实现类.2 Collection和Map在Java容器中一共定义了2种集合, 顶层接口分别是Collection和Map.但是这2个接口都不能直接被实现使用,分别代表两种不同类型的容器.简单来看,Collection代表

JDK 源码解析 —— HashSet

零. 简介 这个类实现了 Set 接口,内部是由 HashMap 实例辅助实现的.它不保证元素的顺序,数据允许为 null. 假如 hash 方法将数据分散地比较合理,比如一个 bucket 一个数据,那么 add.remove.contains.size 性能开销是常数时间. 这个类非线程安全的,如果多线程并发访问,并且至少一个线程在做修改操作,那么必须在外部做好同步处理.例如使用:Set s = Collections.synchronizedSet(new HashSet(...)); 一