iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java HashSet添加 遍历元素源码分析
  • 389
分享到

Java HashSet添加 遍历元素源码分析

2024-04-02 19:04:59 389人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录HashSet 类图HashSet 简单说明HashSet 底层机制说明模拟数组+链表的结构HashSet 添加元素底层机制HashSet 添加元素的底层实现HashSet 扩容

HashSet 类图

HashSet 简单说明

1.HashSet 实现了 Set 接口

2.HashSet 底层实际上是由 HashMap 实现的

public HashSet() {
        map = new HashMap<>();
}

3.可以存放 null,但是只能有一个 null

4.HashSet 不保证元素是有序的(即不保证存放元素的顺序和取出元素的顺序一致),取决于 hash 后,再确定索引的结果

5.不能有重复的元素

HashSet 底层机制说明

HashSet 底层是 HashMapHashMap 底层是 数组 + 链表 + 红黑树

模拟数组+链表的结构


public class HashSetStructureMain {
    public static void main(String[] args) {
        // 模拟一个 HashSet(HashMap) 的底层结构
        // 1. 创建一个数组,数组的类型为 node[]
        // 2. 有些地方直接把 Node[] 数组称为 表
        Node[] table = new Node[16];
        System.out.println(table);
        // 3. 创建节点
        Node john = new Node("john", null);
        table[2] = jhon; // 把节点 john 放在数组索引为 2 的位置
        Node jack = new Node("jack", null);
        jhon.next = jack; // 将 jack 挂载到 jhon 的后面
        Node rose = new Node("rose", null);
        jack.next = rose; // 将 rose 挂载到 jack 的后面
        Node lucy = new Node("lucy", null);
        table[3] = lucy; // 将 lucy 放在数组索引为 3 的位置
        System.out.println(table);

    }
}

// 节点类 存储数据,可以指向下一个节点,从而形成链表
class Node{
    Object item; // 存放数据
    Node next; // 指向下一个节点
    public Node(Object item, Node next){
        this.item = item;
        this.next = next;
    }
}

HashSet 添加元素底层机制

HashSet 添加元素的底层实现

1.HashSet 底层是 HashMap

2.当添加一个元素时,会先得到 待添加元素的 hash 值,然后将其转换成一个 索引值

3.查询存储数据表(Node 数组) table,看当前 待添加元素 所对应的 索引值 的位置是否已经存放了 其它元素

4.如果当前 索引值 所对应的的位置不存在 其它元素,就将当前 待添加元素 放到这个 索引值 所对应的的位置

5.如果当前 索引值 所对应的位置存在 其它元素,就调用 待添加元素.equals(已存在元素) 比较,结果为 true,则放弃添加;结果为 false,则将 待添加元素 放到 已存在元素 的后面(已存在元素.next = 待添加元素)

HashSet 扩容机制

1.HashSet 的底层是 HashMap,第一次添加元素时,table 数组扩容到 cap = 16threshold(临界值) = cap * loadFactor(加载因子 0.75) = 12

2.如果 table 数组使用到了临界值 12,就会扩容到 cap * 2 = 32,新的临界值就是 32 * 0.75 = 24,以此类推

3.在 Java8 中,如果一条链表上的元素个数 到达 TREEIFY_THRESHOLD(默认是 8),并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认是 64),就会进行 树化(红黑树)

4.判断是否扩容是根据 ++size > threshold,即是否扩容,是根据 HashMap 所存的元素个数(size)是否超过临界值,而不是根据 table.length() 是否超过临界值

HashSet 添加元素源码


public class HashSetSourceMain {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("PHP");
        hashSet.add("java");
        System.out.println("set = " + hashSet);

        // 源码分析
        // 1. 执行 HashSet()
        

        // 2. 执行 add()
        

        // 3. 执行 put()
        // hash(key) 得到 key(待存元素) 对应的hash值,并不等于 hashcode()
        // 算法是 h = key.hashCode()) ^ (h >>> 16
        

        // 4. 执行 putVal()
        // 定义的辅助变量:Node<K,V>[] tab; Node<K,V> p; int n, i;
        // table 是 HashMap 的一个属性,初始化为 null;transient Node<K,V>[] table;
        // resize() 方法,为 table 数组指定容量
        // p = tab[i = (n - 1) & hash] 计算 key的hash值所对应的 table 中的索引位置,将索引位置对应的 Node 赋给 p
        
    }
}

HashSet 遍历元素底层机制

HashSet 遍历元素底层机制

1.HashSet 的底层是 HashMapHashSet 的迭代器也是借由 HashMap 来实现的

2.HashSet.iterator() 实际上是去调用 HashMap 的 KeySet().iterator()

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

3.KeySet() 方法返回一个 KeySet 对象,而 KeySet 是 HashMap 的一个内部类

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

4.KeySet().iterator() 方法返回一个 KeyIterator 对象,KeyIterator 是 HashMap 的一个内部类

public final Iterator<K> iterator()     { return new KeyIterator(); }

5.KeyIterator 继承了 HashIterator(HashMap的内部类) 类,并实现了 Iterator 接口,即 KeyIteratorHashIterator 才是真正实现 迭代器 的类

final class KeyIterator extends HashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().key; }
}

6.当执行完 Iterator iterator = HashSet.iterator; 之后,此时的 iterator 对象中已经存储了一个元素节点

  • 怎么做到的?
  • 回到第 4 步,KeySet().iterator() 方法返回一个 KeyIterator 对象
  • new KeyIterator() 调用 KeyIterator 的无参构造器
  • 在这之前,会先调用其父类 HashIterator 的无参构造器
  • 因此,分析 HashIterator 的无参构造器就知道发生了什么

  • nextcurrentindex 都是 HashIterator 的属性
  • Node<K,V>[] t = table; 先把 Node 数组 talbe 赋给 t
  • current = next = null; currentnext 都置为 null
  • index = 0; index 置为 0
  • do {} while (index < t.length && (next = t[index++]) == null); 这个 do-while 会在 table 中遍历 Node 结点
  • 一旦 (next = t[index++]) == null 不成立 时,就说明找到了一个 table 中的 Node 结点
  • 将这个节点赋给 next,并退出当前 do-while 循环
  • 此时 Iterator iterator = HashSet.iterator; 就执行完了
  • 当前 iterator 的运行类型其实是 HashIterator,而 HashIterator 的 next 中存储着从 table 中遍历出来的一个 Node 结点

7.执行 iterator.hasNext

此时的 next 存储着一个 Node,所以并不为 null,返回 true

public final boolean hasNext() {
    return next != null;
}

8.执行 iterator.next()

I.Node<K,V> e = next; 把当前存储着 Node 结点的 next 赋值给了 e

II.(next = (current = e).next) == null 判断当前结点的下一个结点是否为 null

  • (a). 如果当前结点的下一个结点为 null,就执行 do {} while (index < t.length && (next = t[index++]) == null);,在 table 数组中遍历,寻找 table 数组中的下一个 Node 并赋值给 next
  • (b). 如果当前结点的下一个结点不为 null,就将当前结点的下一个结点赋值给 next,并且此刻不会去 table 数组中遍历下一个 Node 结点

III.将找到的结点 e 返回

IV.之后每次执行 iterator.next() 都像 (a)(b) 那样去判断遍历,直到遍历完成

HashSet 遍历元素源码


public class HashSetSourceMain {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("php");
        hashSet.add("java");
        System.out.println("set = " + hashSet);
        // HashSet 迭代器实现原理
        // HashSet 底层是 HashMap,HashMap 底层是 数组 + 链表 + 红黑树
        // HashSet 本身没有实现迭代器,而是借由 HashMap 来实现的
        // 1. hashSet.iterator() 实际上是去调用 HashMap 的 keySet().iterator()
        
        // 2. KeySet() 方法返回一个 KeySet 对象,而 KeySet 是 HashMap 的一个内部类
        
        // 3. KeySet().iterator() 方法返回一个 KeyIterator 对象,KeyIterator 是 HashMap的一个内部类
        
        // 4. KeyIterator 继承了 HashIterator(HashMap的内部类) 类,并实现了 Iterator 接口
        // 即 KeyIterator、HashIterator 才是真正实现 迭代器的类
        
        // 5. 当执行完 Iterator iterator = hashSet.iterator(); 后
        // 此时的 iterator 对象中已经存储了一个元素节点
        // 怎么做到的?
        // 回到第 3 步,KeySet().iterator() 方法返回一个 KeyIterator 对象
        // new KeyIterator() 调用 KeyIterator 的无参构造器
        // 在这之前,会先调用 KeyIterator 父类 HashIterator 的无参构造器
        // 因此分析 HashIterator 的无参构造器就知道发生了什么
        
        // 5.0 next, current, index 都是 HashIterator 的属性
        // 5.1 Node<K,V>[] t = table; 先把 Node 数组 table 赋给 t
        // 5.2 current = next = null; 把 current 和 next 都置为 null
        // 5.3 index = 0; index 置为 0
        // 5.4 do {} while (index < t.length && (next = t[index++]) == null);
        // 这个 do{} while 循环会在 table 中 遍历 Node节点
        // 一旦 (next = t[index++]) == null 不成立时,就说明找到了一个 table 中的节点
        // 将这个节点赋给 next,并退出当前 do while循环
        // 此时 Iterator iterator = hashSet.iterator(); 就执行完了
        // 当前 iterator 的运行类型其实是 HashIterator,而 HashIterator 的 next 中存储着从 table 中遍历出来的一个 Node节点
        // 6. 执行 iterator.hasNext()
        
        // 6.1 此时的 next 存储着一个 Node,所以并不为 null,返回 true
        // 7. 执行 iterator.next(),其实是去执行 HashIterator 的 nextNode()
        
        // 7.1 Node<K,V> e = next; 把当前存储着 Node 节点的 next 赋值给了 e
        // 7.2 (next = (current = e).next) == null
        // 判断当前节点的下一个节点是否为 null
        // a. 如果当前节点的下一个节点为 null
        // 就执行 do {} while (index < t.length && (next = t[index++]) == null);
        // 再 table 数组中 遍历,寻找 table 数组中的下一个 Node 并赋值给 next
        // b. 如果当前节点的下一个节点不为 null
        // 就将当前节点的下一个节点赋值给 next,并且此刻不会去 table 数组中遍历下一个 Node 节点
        // 7.3 将找到的节点 e 返回
        // 7.4 之后每次执行 iterator.next(),都像 a、b 那样去判断遍历,直到遍历完成
        Iterator iterator = hashSet.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println(next);
        }
    }
}

到此这篇关于Java HashSet添加 遍历元素源码分析的文章就介绍到这了,更多相关HashSet添加 遍历元素内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java HashSet添加 遍历元素源码分析

本文链接: https://www.lsjlt.com/news/153926.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java HashSet添加 遍历元素源码分析
    目录HashSet 类图HashSet 简单说明HashSet 底层机制说明模拟数组+链表的结构HashSet 添加元素底层机制HashSet 添加元素的底层实现HashSet 扩容...
    99+
    2024-04-02
  • Java HashSet怎么添加遍历元素
    本篇内容主要讲解“Java HashSet怎么添加遍历元素”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java HashSet怎么添加遍历元素”吧!HashSet 类图Ha...
    99+
    2023-07-02
  • 基于java构造方法Vector遍历元素源码分析
    (注意:本文基于JDK1.8) 前言 任何一个容器类对象用于持有元素后,总是需要遍历元素的,即挨个去访问每个元素1次,而遍历元素,除了常规的依赖于数组对象的下标之外,更常用的是封装好...
    99+
    2024-04-02
  • 基于java构造方法Vevtor添加元素源码分析
    (注意:本文基于JDK1.8) 前言 算上迭代器的add()方法,Vector中一共有7个添加元素的方法,5个添加单个元素的方法,2个添加多个元素的方法,接下来就一起分析它们的实...
    99+
    2024-04-02
  • Java数据结构之HashMap和HashSet源码分析
    本篇内容介绍了“Java数据结构之HashMap和HashSet源码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、认识 HashMa...
    99+
    2023-07-05
  • php数组添加元素的示例分析
    这篇文章主要介绍php数组添加元素的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!数组添加元素$arr = ['a']; //方法一 ar...
    99+
    2024-04-02
  • 基于java构造方法Vector查找元素源码分析
    (注意:本文基于JDK1.8) 前言 元素在存储到内存中,当我们需要使用在内存中存储的元素,这就涉及到在内存中查找元素,今天一起学习Vector提供了哪些查找元素的方法 ...
    99+
    2024-04-02
  • 基于java构造方法Vector修改元素源码分析
    (注意:本文基于JDK1.8) 前言 增删改查,修改元素,Vector提供了3个方法,包括迭代器中的一个,不过本文只分析Vector自身的两个修改元素的方法,迭代器中的方法将单独分...
    99+
    2024-04-02
  • 基于java构造方法Vector删除元素源码分析
    目录前言remove(int)方法分析remove(Object)方法分析removeElement(Object)方法分析removeElementAt(int)方法分析remov...
    99+
    2024-04-02
  • Java迭代器遍历list的方法及代码分析
    Java迭代器遍历list的方法是什么?动力节点小编来告诉大家。迭代器可用于遍历ArrayList。如果ArrayList中有更多元素,则hasNext()方法返回true,否则返回...
    99+
    2022-11-21
    Java 迭代器 遍历 list
  • 推荐使用For-Each而不是For循环遍历元素的原因分析
    这篇文章主要介绍了推荐使用For-Each而不是For循环遍历元素的原因分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、for循环的缺...
    99+
    2024-04-02
  • 大数组元素差异removeAll与Map效率源码对比分析
    本文小编为大家详细介绍“大数组元素差异removeAll与Map效率源码对比分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“大数组元素差异removeAll与Map效率源码对比分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一...
    99+
    2023-07-05
  • Java类加载器ClassLoader源码层面分析讲解
    目录Launcher 源码AppClassLoader 源码ExtClassLoader 源码ClassLoader 源码总结最终总结一下Launcher 源码 sun.misc.L...
    99+
    2024-04-02
  • Linux中Mint设置面板位置以及添加面板元素的示例分析
    这篇文章给大家分享的是有关Linux中Mint设置面板位置以及添加面板元素的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。刚装好Linux Mint(听说现在比Ubuntu还流行),装完之后发现面板在最上...
    99+
    2023-06-12
  • java数组算法例题代码详解(冒泡排序,选择排序,找最大值、最小值,添加、删除元素等)
    目录数组算法例题1.数组逆序2.找出数组中最大值所在下标位置3.找出数组中指定元素第一次出现的下标位置4.在数组中找出指定下标对应的元素5.找出指定元素在数组中最后一次出现位置6.找...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作