总结HashSet以及分析部分底层源码

1. HashSet继承的抽象类和实现的接口

  • 继承的抽象类:AbstractSet
  • 实现了Set接口
  • 实现了Cloneable接口
  • 实现了Serializable序列化接口:该接口标记此类支持序列化操作

2. HashSet底层数据结构

HashSet底层是基于HashMap实现的,HashMap底层数据结构是基于数组+链表实现的。

1. 特点

  • 既保存了数组查询和修改元素效率快的优点,也保存了链表在添加和删除元素时效率快的特点。
  • 存储的元素是无序的,不允许重复的,存储的元素最多只能有一个为null值,这是因为HashSet底层存储元素时只是利用了HashMap的key来存储元素,而HashMap的value都是存储的 一个new Object() 对象。所以说HashSet只是利用了HashMap的key,并没有利用HashMap的value。

2. HashSet的底层结构图

因为HashSet底层是使用的HashMap,所以下图实际上是HashMap的底层数据结构。当存储一个元素时,首先会给这个元素计算一个hash值。然后根据计算出来的hash值决定将元素存储到哈希表中的那个位置。

3. 优点

  • 存取效率高,可以动态扩容

4. 缺点

  • 每次存储新的元素都需要计算一次hashCode值,如果计算hash值的算法设计的不好,哈希碰撞产生过多,就可能造成一个节点小存储了多个元素,而哈希表中相邻的元素的位置没有存储任何元素。
  • HashSet线程不安全,在多线程情况下会出现线程安全问题。

3. HashSet适用的场景

  • 需要存储不重复的值,要求存取效率高,适合在单线程情况下使用。

  • 如果需要在多线程情况下使用,需要使用Collections集合工具类,创建一个线程安全的HashSet集合

    1. Set<Integer> hashSet = Collections.synchronizedSet(new HashSet<Integer>());

4. HashSet底层源码分析

1. 构造函数

1. 默认无参构造函数

  1. /**
  2. * 默认无参构造函数
  3. */
  4. public HashSet() {
  5. map = new HashMap<>();
  6. }

2. 传递一个集合的构造函数

  1. /**
  2. * 可以将集合中的数据全部添加到新创建的HashSet集合中,会去除掉重复的值。
  3. * @param c
  4. */
  5. public HashSet(Collection<? extends E> c) {
  6. map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
  7. addAll(c);
  8. }

2. 添加一个元素的流程

1. 将数据包装

在每次添加数据时,如果数据是基本数据类型,会先将基本数据类型进行装箱操作,把基本数据类型转换成对应的包装类型(引用数据类型)

  1. // 例如:集合中存放Integer数据类型,在进行add操作时,会先进行装箱操作
  2. /**
  3. * 将基本数据类转换为引用数据类型
  4. * @param i 传入的参数为一个基本型数据类型
  5. * @return 返回的参数是一个基本数据类型的包装类(引用数据类型)
  6. */
  7. public static Integer valueOf(int i) {
  8. if (i >= IntegerCache.low && i <= IntegerCache.high)
  9. return IntegerCache.cache[i + (-IntegerCache.low)];
  10. return new Integer(i);
  11. }

2. 调用add() 方法

  1. /**
  2. * HashSet的添加方法
  3. * @param i 传入需要添加的元素
  4. * @return 添加成功返回true,失败返回false
  5. */
  6. public boolean add(E e) {
  7. // 直接调用已经创建好的HashMap集合,调用HashMap中的put()方法进行添加,key为元素值,value为常量对象
  8. return map.put(e, PRESENT)==null;
  9. }
  • 常量说明

    1. // 该常量对象将作为HashSet集合的value
    2. private static final Object PRESENT = new Object();

3. HashMap中的put()方法

  1. /**
  2. * HashMap的put添加方法
  3. * @param key 对应的是HashSet要添加的元素
  4. * @param value 对应的是一个常量对象 new Object()
  5. * @return 添加成功返回null,添加失败返回value值
  6. */
  7. public V put(K key, V value) {
  8. // 调用putVal()方法,对元素进行添加
  9. return putVal(hash(key), key, value, false, true);
  10. }

4. HashMap中的hash()方法

  1. /**
  2. * HashMap的hash方法,用于计算每个key的hash值,这个hash值将决定key在哈希表中的具体位置
  3. * @param key 对应的是HashSet要添加的元素
  4. * @return 返回根据key计算出来的hash值
  5. */
  6. static final int hash(Object key) {
  7. // 用于接收计算好的hash值
  8. int h;
  9. // 返回根据key计算出来的hash值
  10. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  11. }

5. HashMap中putVal()方法

  1. /**
  2. * HashMap的hash方法,用于计算每个key的hash值,这个hash值将决定key在哈希表中的具体位置
  3. * @param hash 计算好的hash值
  4. * @param value 需要存储的key值
  5. * @param onlyIfAbsent 需要存储的value值
  6. * @param onlyIfAbsent 如果返回true说明添加的key是首次添加,false说明是修改了对应key的value
  7. * @param evict 目前HashMap并没有使用改变了,留给了实现HashMap的子类
  8. * @return
  9. */
  10. final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
  11. // 创建一个类型为Node的数组,其实就是哈希表
  12. Node<K,V>[] tab;
  13. //
  14. Node<K,V> p;
  15. // 辅助n,记录tab的长度。辅助变量i,存储经过计算得到的tab表的下标值
  16. int n, i;
  17. // 判读tab表是否为空,或者长度为0,满足则说明是第一次创建tab表
  18. if ((tab = table) == null || (n = tab.length) == 0)
  19. n = (tab = resize()).length; // 为tab表创建初始大小16,赋给辅助变量n
  20. // 将tab表长度减一在和hash进行按位与运算,得到一个tab表的下标值,赋给i,
  21. // 再将当前下标所指向的tab表的对象赋给p,判断当前位置上是否存储对象,即是否为null
  22. if ((p = tab[i = (n - 1) & hash]) == null)
  23. tab[i] = newNode(hash, key, value, null); // 如果当前位置为null,直接添加一个新节点
  24. // 如果当前位置已经存储过节点
  25. else {
  26. // 创建一个节点对象e
  27. Node<K,V> e;
  28. // 创建一个与key相同类型的变量k
  29. K k;
  30. /*
  31. 如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样,
  32. 并且满足下面两个条件之一:
  33. (1)准备加入的key和p指向的Node结点的key是同一个对象
  34. (2)p指向的Node结点的key的equals()和准备加入的key比较后相同
  35. */
  36. if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
  37. e = p;
  38. // 判断p是不是红黑树的一个节点对象
  39. else if (p instanceof TreeNode)
  40. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 作为节点添加到红黑树
  41. // 如果table对应索引位置,已经是一个链表,就使用for循环比较
  42. else {
  43. /*
  44. 1. 依次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
  45. 注意在把元素添加到链表后,立即判断该链表是否已经达到8个结点,
  46. 达到8个节点数就调用treeifyBin()对当前这个链表进行树化(转成红黑树)
  47. 注意:
  48. if(tab==null||(n=tab.length)<MIN_TREEIFY_CAPACITY(64))
  49. resize();
  50. 如果上面条件成立,先table扩容,只有上面条件不成立时,才进行转成红黑树
  51. */
  52. for (int binCount = 0; ; ++binCount) {
  53. if ((e = p.next) == null) {
  54. p.next = newNode(hash, key, value, null);
  55. if (binCount >= TREEIFY_THRESHOLD - 1)
  56. treeifyBin(tab, hash);
  57. break;
  58. }
  59. // 2. 依次和该链表的每一个元素比较过程中,如果有key相同情况,就直接break
  60. if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
  61. break;
  62. // 将对应位置上的节点
  63. p = e;
  64. }
  65. }
  66. if (e != null) {
  67. V oldValue = e.value;
  68. if (!onlyIfAbsent || oldValue == null)
  69. e.value = value;
  70. afterNodeAccess(e);
  71. return oldValue;
  72. }
  73. }
  74. // 记录集合被修改的次数
  75. ++modCount;
  76. // 判断当前哈希表中实际存储的元素个数是否得到扩容条件,threshold的大小为哈希表长度的0.75(默认值)
  77. if (++size > threshold)
  78. resize(); // 调用扩容方法
  79. afterNodeInsertion(evict); // 该方法在HashMap中没有实际作用,是留给HashMap的子类的
  80. return null; // 添加节点元素成功,返回null
  81. }

总结HashSet以及分析部分底层源码的更多相关文章

  1. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  2. Java泛型底层源码解析-ArrayList,LinkedList,HashSet和HashMap

    声明:以下源代码使用的都是基于JDK1.8_112版本 1. ArrayList源码解析 <1. 集合中存放的依然是对象的引用而不是对象本身,且无法放置原生数据类型,我们需要使用原生数据类型的包 ...

  3. List-ArrayList集合基础增强底层源码分析

    List集合基础增强底层源码分析 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的 ...

  4. 2018.11.20 Struts2中对结果处理方式分析&struts2内置的方式底层源码剖析

    介绍一下struts2内置帮我们封装好的处理结果方式也就是底层源码分析 这是我们的jar包里面找的位置目录 打开往下拉看到result-type节点 name那一列就是我们的type类型取值 上一篇博 ...

  5. BAT资深工程师 由浅入深分析 Tp5&Tp6底层源码 - 分享

    BAT资深工程师由浅入深分析Tp5&Tp6底层源码 第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的 ...

  6. BAT资深工程师由浅入深分析Tp5&Tp6底层源码☆

    第1章 课程简介 本章主要让大家知道本套课程的主线, 导学内容,如何学习源码等,看完本章要让小伙伴觉得这个是必须要掌握的,并且对加薪有很大的帮助. 第2章 [TP5灵魂]自动加载Loader 深度分析 ...

  7. LInkedList总结及部分底层源码分析

    LInkedList总结及部分底层源码分析 1. LinkedList的实现与继承关系 继承:AbstractSequentialList 抽象类 实现:List 接口 实现:Deque 接口 实现: ...

  8. Vector总结及部分底层源码分析

    Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...

  9. 从底层源码浅析Mybatis的SqlSessionFactory初始化过程

    目录 搭建源码环境 POM依赖 测试SQL Mybatis全局配置文件 UserMapper接口 UserMapper配置 User实体 Main方法 快速进入Debug跟踪 源码分析准备 源码分析 ...

随机推荐

  1. 為什麼我的手機連Wi-Fi速度總是卡在75Mbps?Wi-Fi速度解惑~帶你一次看懂!

    正文字体大小:大 中 小 為什麼我的手機連Wi-Fi速度總是卡在75Mbps?Wi-Fi速度解惑-帶你一次看懂! (2017-02-21 10:57:48) 转载▼ 标签: wi-fi速度 手機wi- ...

  2. EFCore_环境搭建与简单使用_01

    开发环境搭建 经典步骤:建实体类.建DbContext.生成数据库 本次使用codefirst模式,走下流程,(你也可以先建好数据库,用命令行的形式,直接生成DbContext,而且生成的DbCont ...

  3. ShardingSphere-初见

    目录 概述 认识shardingjdbc shardingjdbc功能架构图 认识Sharding-Proxy 三个组件的比较 ShardingJdbc混合架构 ShardingShpere的功能清单 ...

  4. Discovery直播 | 3D“模”术师,还原立体世界——探秘3D建模服务

    通过多张普通的照片重建一个立体逼真的3D物体模型,曾经靠想象实现的事情,现在, 使用HMS Core 3D建模服务即可实现! 3D模型作为物品在数字世界中的孪生体,用户可以自己拍摄.建模并在终端直观感 ...

  5. "简单"的优化--希尔排序也没你想象中那么难

    写在前边 大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦. 不过这篇文章呢,还是想跟大家聊一聊数据结构与算法,学校也是大二上才开设了数据结构这 ...

  6. PTA 根据后序和中序遍历输出先序遍历 (25分)

    PTA 根据后序和中序遍历输出先序遍历 (25分) 本题要求根据给定的一棵二叉树的后序遍历和中序遍历结果,输出该树的先序遍历结果. 输入格式: 第一行给出正整数N(≤30),是树中结点的个数.随后两行 ...

  7. find 删除日志文件

    find 命令删除日志文件 find ./my_dir -mtime +10 -type f -delete EXPLANATIONS ./my_dir your directory (replace ...

  8. string类运用:特殊的翻译

    特殊的翻译 小明的工作是对一串英语字符进行特殊的翻译:当出现连续且相同的小写字母时,须替换成该字母的大写形式,在大写字母的后面紧跟该小写字母此次连续出现的个数:与此同时,把连续的小写字母串的左侧和右侧 ...

  9. Unity——有限状态机FSM修改

    FSM状态机改 一.前言 FSM状态机初版 之前写过一版有限状态机,后来发现很多问题: 前一个版本是记录了当前的状态,切换状态时,要等下一帧状态机Update的时候才会调动上个状态的退出,总会有一帧的 ...

  10. Alpine容器安装运行ssh

    写在前面 本文介绍了在Alpine容器(docker)上安装运行ssh并保证外界(宿主机)能通过ssh登录的方法,给出了相应的命令.在下在探索过程中借鉴了许多前人的经验,在此先行谢过,所有参考内容都会 ...