[关闭]
@w1992wishes 2018-05-10T09:34:53.000000Z 字数 4239 阅读 942

java容器源码分析--HashSet(JDK1.8)

JAVA_java容器 源码分析


本篇结构:

一、前言

HashSet也是常用的数据结构,是一个没有重复元素的集合,也不能保证元素的顺序,可以有null值,但最多只能有一个。

HashSet的实现是基于HashMap的,在了解过HashMap的源码(java容器源码分析--HashMap(JDK1.8))后,再看HashSet的源码,会简单很多,所以本文也会简短很多。

二、数据结构

HashSet是基于HashMap来实现的,底层采用HashMap来保存元素。所以参考HashMap的数据结构即可。

三、重要参数

  1. private transient HashMap<E,Object> map;
  2. // Dummy value to associate with an Object in the backing Map
  3. private static final Object PRESENT = new Object();

一个map属性,是底层使用该map来保存HashSet的元素;

一个静态的Object常量PRESENT,因为HashMap存的是Key-Value对,所以这里分配了一个静态空对象用作所有Key的Value。

四、常用方法

  1. public class HashSetTest {
  2. public static void main(String[] args) {
  3. // HashSet常用API
  4. testHashSetAPIs() ;
  5. }
  6. private static void testHashSetAPIs() {
  7. // new
  8. HashSet set = new HashSet();
  9. // add
  10. set.add("a");
  11. set.add("b");
  12. set.add("c");
  13. set.add("d");
  14. set.add("e");
  15. // size
  16. System.out.printf("size : %d\n", set.size());
  17. // contains
  18. System.out.printf("HashSet contains a :%s\n", set.contains("a"));
  19. System.out.printf("HashSet contains g :%s\n", set.contains("g"));
  20. // remove
  21. set.remove("e");
  22. // toArray
  23. String[] arr = (String[])set.toArray(new String[0]);
  24. for (String str:arr)
  25. System.out.printf("for each : %s\n", str);
  26. // 新建一个包含b、c、f的HashSet
  27. HashSet otherset = new HashSet();
  28. otherset.add("b");
  29. otherset.add("c");
  30. otherset.add("f");
  31. // 克隆一个removeset,内容和set一模一样
  32. HashSet removeset = (HashSet)set.clone();
  33. // 删除“removeset中,属于otherSet的元素”
  34. removeset.removeAll(otherset);
  35. // 打印removeset
  36. System.out.printf("removeset : %s\n", removeset);
  37. // 克隆一个retainset,内容和set一模一样
  38. HashSet retainset = (HashSet)set.clone();
  39. // 保留“retainset中,属于otherSet的元素”
  40. retainset.retainAll(otherset);
  41. // 打印retainset
  42. System.out.printf("retainset : %s\n", retainset);
  43. // 遍历HashSet
  44. for(Iterator iterator = set.iterator();
  45. iterator.hasNext(); )
  46. System.out.printf("iterator : %s\n", iterator.next());
  47. // 清空HashSet
  48. set.clear();
  49. // 输出HashSet是否为空
  50. System.out.printf("%s\n", set.isEmpty()?"set is empty":"set is not empty");
  51. }
  52. }

运行结果:

size : 5
HashSet contains a :true
HashSet contains g :false
for each : a
for each : b
for each : c
for each : d
removeset : [a, d]
retainset : [b, c]
iterator : a
iterator : b
iterator : c
iterator : d
set is empty

比较懒,这段代码是直接拷贝过来的(http://www.cnblogs.com/skywang12345/p/3311252.html)。

主要是了解HashSet的常用方法。

五、源码分析

5.1、构造方法

一共有5个构造方法:

  1. // 无参构造,默认初始化一个HashMap,HashMap的默认容量大小16和默认加载因子0.75
  2. public HashSet() {
  3. map = new HashMap<>();
  4. }
  5. // 构造一个指定Collection参数的HashSet,底层HashMap的容量为Math.max((int) (c.size()/.75f) + 1, 16)
  6. public HashSet(Collection<? extends E> c) {
  7. map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
  8. addAll(c);
  9. }
  10. // 构造指定初始容量大小和加载因子的底层HashMap
  11. public HashSet(int initialCapacity, float loadFactor) {
  12. map = new HashMap<>(initialCapacity, loadFactor);
  13. }
  14. // 构造指定初始容量大小和默认的加载因子0.75的底层HashMap
  15. public HashSet(int initialCapacity) {
  16. map = new HashMap<>(initialCapacity);
  17. }
  18. // 不对外公开的一个构造方法(默认default修饰),底层构造的是LinkedHashMap,dummy只是一个标示参数,无具体意义
  19. HashSet(int initialCapacity, float loadFactor, boolean dummy) {
  20. map = new LinkedHashMap<>(initialCapacity, loadFactor);
  21. }

可以看到 new HashSet 其实就是 new HashMap, 所以可以预见,对 HashSet 的各种操作,其实对底层 HashMap 的操作。

5.2、添加操作

add(E e)

  1. public boolean add(E e) {
  2. return map.put(e, PRESENT)==null;
  3. }

addAll(Collection c)

addAll(Collection c)是在其父类AbstractCollection中定义的。

  1. public boolean addAll(Collection<? extends E> c) {
  2. boolean modified = false;
  3. // 遍历加入
  4. for (E e : c)
  5. if (add(e))
  6. modified = true;
  7. return modified;
  8. }

5.3、删除操作

remove(Object o)

  1. public boolean remove(Object o) {
  2. return map.remove(o)==PRESENT;
  3. }

removeAll(Collection c)

removeAll(Collection c)是父类AbstractSet中定义的。

  1. public boolean removeAll(Collection<?> c) {
  2. Objects.requireNonNull(c);
  3. boolean modified = false;
  4. // 如果删除的集合容量小于HashSet的容量
  5. if (size() > c.size()) {
  6. // 遍历集合一个个删除
  7. for (Iterator<?> i = c.iterator(); i.hasNext(); )
  8. modified |= remove(i.next());
  9. // 否则遍历HashSet,如果包含就删除
  10. } else {
  11. for (Iterator<?> i = iterator(); i.hasNext(); ) {
  12. if (c.contains(i.next())) {
  13. i.remove();
  14. modified = true;
  15. }
  16. }
  17. }
  18. return modified;
  19. }

5.4、包含操作

contains(Object o)

  1. public boolean contains(Object o) {
  2. return map.containsKey(o);
  3. }

containsAll(Collection c)

  1. // 一个个遍历判断,只要有一个不包含,就返回false
  2. public boolean containsAll(Collection<?> c) {
  3. for (Object e : c)
  4. if (!contains(e))
  5. return false;
  6. return true;
  7. }

5.5、其他操作

size()

  1. public int size() {
  2. return map.size();
  3. }

isEmpty()

  1. public boolean isEmpty() {
  2. return map.isEmpty();
  3. }

iterator()

  1. public Iterator<E> iterator() {
  2. return map.keySet().iterator();
  3. }

六、疑问解答

为什么要用HahSet?

假如我们现在想要在一大堆数据中查找X数据。LinkedList是基于链表的形式,查找需要逐级遍历,效率低。ArrayList如果不知道X的位置序号,还是一样要全部遍历一次直到查到结果,效率一样低。HashSet则根据散列值计算数组下标,速度很快。

七、分析总结

HashSet的源码更多的是对HashMap的封装,简单总结一下:

HashSet是基于HashMap实现的,不能有重复的元素;
HashSet可以插入null,但只能有一个;
HashSet不是线程安全的;
HashSet,HashMap都是hash表,而hash实现的容器最重要的一点就是可以快速存取。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注