Set接口及其 实现类


基本
  1. 无序(添加和取出的顺序不一样),没有索引
  2. 不允许有重复的元素
  3. 常用方法和Collection的子接口相同
    • 遍历的方式和Collection 相同
    • 可以使用iterator和增强for 但是不能使用索引的方式来获取
  4. 补充第一点:取出的顺序不是添加的顺序,但是取出后 顺序固定

Set接口实现类-HashSet
(1)HashSet基础说明:
  1. HashSet实现了Set接口

  2. HashSet实际上是基于HashMap实现的

    public HashSet(){map = new HashMap<>();
    }
    
  3. 可以存放null值,但是只能有一个null

  4. HashSet不保证元素是有序的,取决于hash后,在确定索引的结果

  5. 不能有重复的元素 / 对象


(2)HashSet底层机制说明:
  • HashSet底层是HashMap ;HashMap的底层是(数组+链表+红黑树)

  • -------> 用一段代码来模拟一下 (数组+链表)

  • /*** 模拟一个HashSet底层(一个HashMap结构)*/
    public class HashSetMode {public static void main(String[] args) {// 创建一个数组  数组类型是Node[]Node01[] table = new Node01[16];// 创建一个 join的节点Node01 join = new Node01("join", null);// 将join作为第一个节点放入 第二个数组内table[1] = join;// 将 节点 mark 悬挂在join后 形成链表Node01 mark = new Node01("mark", null);join.next = mark;for (Object o :table) {System.out.println(o);}}
    }class Node01{Object item;  // 存放内容Node01 next;   // 指向下一个节点public Node01(Object item, Node01 next) {this.item = item;this.next = next;}@Overridepublic String toString() {return "Node01{" +"item=" + item +", next=" + next +'}';}
    }
    
    null
    Node01{item=join, next=Node01{item=mark, next=null}}
    null
    null
    .
    .
    .
    


(3)分析HashSet的添加元素底层是如何实现的(hash() + equals())
  • 结论:
HashSet底层是HashMap
添加一个元素时,先得到hash值 -会转成 -> 索引值
找到存储数据表table,看这个索引位置是否已经存放元素
如果没有,直接加入
如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则悬挂到后面
java8中,如果链表元素个数超过TREE_THRESHOLD(默认为8),并且table的大小 >= MIN_TREELFY-CAPACITY(默认为64),就进行树化(红黑树)
  • debug 测试代码

    public static void main(String[] args) {HashSet hashSet = new HashSet();hashSet.add("java");hashSet.add("php");hashSet.add("java");System.out.println(hashSet);}
    
    1. 执行 HashSet

       public HashSet() {map = new HashMap<>();}
      
    2. 执行add() 方法

      public boolean add(E e) {   // e: "java"return map.put(e, PRESENT)==null;  // e: "java"  map: "{}"}
      // 这里的 PRESENT 来自HashSet的 Static final 类型的一个对象,作用是占位,为了HashSet能够使用HashMap
      
    3. 追进 执行put()方法

       public V put(K key, V value) {  // key:"java"   value = PRESENT 不变return putVal(hash(key), key, value, false, true);}
      
    4. 追进 看hash(key) ,是按照这个算法得到key对应的hash值

       static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
      
    5. 返回 追进 putVal()

      1. 这里的 table 是HashMap的一个属性,是一个Node数组
      2. 第一次执行add 的情况
        final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// 1.table = null 执行进入 ;if语句表示 如果当前的table为 null 或者大小为 0,就第一次扩容大小为16if ((tab = table) == null || (n = tab.length) == 0)  n = (tab = resize()).length; //  resize 缓冲扩容机制 n: 16// 2.根据key得到的hash值,去计算该key应该存放到table表的哪个索引位置,并把这个位置的对象赋给p
      // 3. 判断p是否为null = 判断对应table索引位置有没有存放元素
      // 3. 如果p为null,表示还未放入元素,就创建一个Node(key,value)if ((p = tab[i = (n - 1) & hash]) == null)    // i: 3tab[i] = newNode(hash, key, value, null); // key:"java"  value:PRESENTelse {Node<K,V> e; K k;if (p.hash == hash &&  // p指向的是当前索引位置对应的最后一个元素;判断与预添加的key的hash是否相同((k = p.key) == key || (key != null && key.equals(k)))) // 同一个对象或内容相同e = p;// 判断p 是不是红黑树
      // 如果是红黑树 就调用 putTreeVal();进行添加else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {// 如果对应索引的table 已经形成了一个链表;就用这个for循环去判断
      // 依次和链表的每一个元素比较,如果都不相同,直接悬挂到最后面,否则 breakfor (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);    // 当前链表如果到达8 就进行树化;treeifyBin()内要求table>= 64 才会树化,不然只会先扩容break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)  // * 这里的size 增加的条件: 只要成功加入了一个节点 不管是加入table内还是Node内都满足条件,从而触发扩容resize();  // 扩容afterNodeInsertion(evict);return null;}
      
      // resize()方法 第一次进入的处理  标示上面的 第二步
      // 扩容机制: 0.75 x 16 = 12
      // 当使用12个空间的时候,就开始缓存=此时开始扩容,不等到16个空间全部使用完再扩容else {               // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
      // DEFAULT_LOAD_FACTOR(加载因子) =0.75  DEFAULT_INITIAL_CAPACITY = 1 >> 4 =16}
      

LinkedHashSet

  1. 为HashSet的一个子类
  2. 底层是 LinkedHashMap,底层维护的是一个(数组table+双向链表)
  3. 根据元素的Hashcode值,来决定元素的存储位置。同时使用链表维护元素的次序,使得元素的输出与插入次序一致。(加入次序和取出次序一致)
  4. 不允许添加重复的元素
  • 补充说明:

    1. 在LinkedHashSet 中维护了一个hash表和一个双向链表(LinkedHashSet有 head 和 tail)
    2. 每个节点有pre 和 next 属性,这样形成一个双向链表
    3. 在添加一个元素时,先求hash值,再求索引。确定该元素再hashtable的位置,然后将添加的元素添加到双向链表(如果已经存在不添加,原则和hashSet一样)
    4. 这样就实现了遍历LinkedHashSet时,插入顺序和输出顺序一致
  • 扩容机制:

添加第一次时,直接将table数组 扩容到 16 ,存放的节点类型是 LinkedHash E n t r y , 数 组 是 H a s h M a p Entry ,数组是 HashMap Entry,数组是HashMapNode[]

3.2.6 Set接口及其实现类相关推荐

  1. java面向对象 接口和实现类_类实现java面向对象上:接口

    最近应用开发的过程中出现了一个小问题,顺便记录一下原因和方法--类实现 10.接口: 接口的设计:        1.应用接口处理多继承:        2.应用接口为外部类添加功能:        ...

  2. 深入struts2.0(七)--ActionInvocation接口以及3DefaultActionInvocation类

    1.1.1       ActionInvocation类 ActionInvocation定义为一个接口.主要作用是表现action的运行状态.它拥有拦截器和action的实例.通过重复的运行inv ...

  3. Jfinal 2.1 集成 Guice,实现注解功能, 加入Service接口和实现类,直接晒代码

    2019独角兽企业重金招聘Python工程师标准>>> [1].第一步,Maven引入Google的Guice的jar包: <dependency><groupId ...

  4. yii2.0框架中自定义接口,实现类的多继承

    一.前言 最近有个需求,比如有A,B,C,D四个文件,原来的逻辑是,A继承B,C继承D.后来由于A和C的逻辑太像,既然如此的话,为什么不把A,C写一起呢,第一是方便维护,第二也是减少无用的代码,说干就 ...

  5. 接口有class类对象吗

    接口有class类对象 public interface Test{} Class c=Test.class; 拓展: 哪些类型可以有Class对象

  6. java 抽象类,接口,object类详解

    抽象类: 如果一个类没有足够的信息去描绘一个具体的对象,那么这个类就可以叫做抽象类. 也可以叫做:可声明抽象方法的类==>抽象类 注意: <1>抽象类不可以实例化对象,所以它只能被继 ...

  7. UI组件之AdapterView及其子类关系,Adapter接口及其实现类关系

    AdapterView本身是一个抽象基类,它派生的的子类在用法上十分相似.AdapterView直接派生的三个子类:AbsListView,AbsSpinner,AdapterViewAnimator ...

  8. set删除一个元素时间复杂度_set/map接口及其实现类

    ---------------[ 感谢小郡提供的图片] 1.Map接口/Hashtable.HashMap.TreeMap实现类 Hashtable.HashMap.TreeMap 都是最常见的一些 ...

  9. java8获取实现某个接口的所有类_Java 试题八

    Java 试题八 1.java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法为何不推荐使用? 答:有两种实现方法,分别是继承Thread类与实现Runn ...

  10. abstract类_012 JAVA 抽象类、接口、String类的基础了解

    1.抽象方法和抽象类 抽象方法:使用abstract修饰的方法,没有方法体,只有声明.抽象方法可以当做是一种规范,让子类必须实现. 注意: 1.抽象方法没有方法体,只能以分号结尾 2.抽象方法只能声明 ...

最新文章

  1. 莹石云存储卡不兼容_继入股无锡好达之后,华为再度入股国产滤波器厂商德清华莹...
  2. SQLite入门与分析(四)---Page Cache之事务处理(3)
  3. express 配置支持https
  4. 《赤壁》:吴宇森恶搞三国的经过
  5. 百练OJ:2973:Skew数
  6. 天刀手游制作人亲笔详解制作思路
  7. Agrona的Threadsafe堆外缓冲区
  8. POJ 3694 Network
  9. 按钮点击打开新页面_PDF怎么打开?如何制作一个PDF格式的文档?
  10. USACO 5.3 Window Area
  11. CCF 2013-12-2 ISBN号码
  12. 整理电力系统GPS时间同步装置(GPS对时系统)孤岛方案
  13. AMS1117-3.3电源芯片损坏分析
  14. Windows及Linux系统下--adb驱动安装及问题总结
  15. Twaver-HTML5基础学习(32)Network样式andTree样式
  16. 2020年CSP-J2 CSP-S2 复赛题解
  17. 5.31 综合案例2.0 - 在线音乐盒
  18. 广东省计算机一级网络题教学,2017年广东计算机一级考试试题
  19. (附源码)springboot客户信息管理系统 毕业设计 181936
  20. 一个简易的基于内容的新闻推荐系统

热门文章

  1. 硬盘坏了数据可以恢复吗?盘点数据恢复技巧
  2. android studio yasea,安卓直播推流yasea的使用
  3. QT 、C++试卷生成系统 /也可作为--考试类型的问卷程序
  4. 大文娱,文学与文娱(2)
  5. 陌生的城市、陌生的一切
  6. 视频流 zynq Linux 至 pl,AR# 46913: Zynq-7000 示例设计:使用面向 DEVCFG 的 Linux 驱动程序对 PL 进行编程...
  7. 【动手教你学故障诊断:Python实现Tensorflow+CNN深度学习的轴承故障诊断(西储大学数据集)(含完整代码)】
  8. 97.在字符串前加入n个*号
  9. fabric+cpabe加密的医疗数据共享代码(分级加密、多授权中心)
  10. 为什么我们要学习基本理论