Java HashSet:底层工作原理与实现机制

简介: 本文介绍了Java中HashSet的工作原理,包括其基于HashMap实现的底层机制。通过示例代码展示了HashSet如何添加元素,并解析了add方法的具体过程,包括计算hash值、处理碰撞及扩容机制。

前言

在Java集合框架中,`HashSet` 是一个非常常用的数据结构,它提供了高效的元素存储和查找功能。作为一种集合,`HashSet` 允许我们存储不重复的元素,并且在平均情况下可以实现常数时间复杂度的插入和查找。这一切的背后,`HashSet` 依赖于 `HashMap` 来实现其底层机制。本文将深入探讨 `HashSet` 的工作原理,包括其如何通过计算哈希值来管理元素、如何处理碰撞以及在存储容量不足时的扩容机制。通过示例代码,我们将逐步解析 `add` 方法的具体实现过程,帮助读者更好地理解这一重要数据结构的运作方式。


public class HashSet_ {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("jack");
        hashSet.add("tom");
        hashSet.add("jack");
    }
}

HashSet底层走的是HashMap()

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

执行add()方法 e就是你传入进的值,PRESENT是一个静态object()空方法,目的就是为了站位能够用到put()方法

public boolean add(E e) {
  return map.put(e, PRESENT)==null;//如果这里返回空就代表e这个值在这个并没有
}

传入的key就是上面的e,然后value就是PRESENT

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true); //如果这里返回空就代表成功了
}

求Key的hash值里面会到hashCode()方法,由于算法有点复杂就不追了,这个方法会得到key的hash值,但是这个hash值并不等价于hashCode,因为他里面还有算法

这个算法的设计可以理解为就是为了让key尽量得到不一样的hash值

static final int hash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了一些辅助变量,一开始都是初始值
    //table就是HashMap里面一个数组类型是Node
    if ((tab = table) == null || (n = tab.length) == 0)
        //这里跳转到resize()方法n=16
        n = (tab = resize()).length; 
        //(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置
      //并把这个位置的对象,赋给 p
        //判断p是否为null如果为null那么就表示他还没有存放元素,就创建一个Node就放在tab[i]
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    //代表修改了一次
    ++modCount;
  /*size表示存入了几个值,如果size大于了他的threshold,也就是临界值大小那么他就会跳入
    resize()这个方法进行扩容;*/
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);//这是HashMap里面一个方法
    return null;//这里返回空就代表成功了
}
final Node<K,V>[] resize() {
    // 把table赋值给一个Node类型的OldTab数组
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //threshold是HashMap定义的一个int类型初始值为0
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        //DEFAULT_INITIAL_CAPACITY是HashMap里面一个静态不可改量为16 
        //static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
        //这里节解释一个<<4的意思就是1*2*2*2*2就是1*2的4次方为16
        newCap = DEFAULT_INITIAL_CAPACITY; //newCap为16
         // static final float DEFAULT_LOAD_FACTOR = 0.75f
        //这里也是是HashMap里面一个静态不可改量为0.75,他也是加载因子
        //newThr是一个临界值就是当你16个空间用了12个他就会开始扩展,就是为了防止突然大量的添加
        //内容,如果没有他就会突然卡住,这个也类似与一个缓冲
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//newThr为12
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;//12
    @SuppressWarnings({"rawtypes","unchecked"})
    //newTab数组大小为16
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //table这时已经变为16了
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;//返回newTab[16]
}
相关文章
|
5月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
166 0
|
5月前
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
243 0
|
5月前
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
262 4
|
5月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
195 24
|
6月前
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
223 2
|
6月前
|
XML JSON Java
Java 反射:从原理到实战的全面解析与应用指南
本文深度解析Java反射机制,从原理到实战应用全覆盖。首先讲解反射的概念与核心原理,包括类加载过程和`Class`对象的作用;接着详细分析反射的核心API用法,如`Class`、`Constructor`、`Method`和`Field`的操作方法;最后通过动态代理和注解驱动配置解析等实战场景,帮助读者掌握反射技术的实际应用。内容翔实,适合希望深入理解Java反射机制的开发者。
581 13
|
6月前
|
算法 Java 索引
说一说 Java 并发队列原理剖析
我是小假 期待与你的下一次相遇 ~
|
6月前
|
安全 Java 编译器
JD-GUI,java反编译工具及原理: JavaDecompiler一个Java反编译器
Java Decompiler (JD-GUI) 是一款由 Pavel Kouznetsov 开发的图形化 Java 反编译工具,支持 Windows、Linux 和 Mac Os。它能将 `.class` 文件反编译为 Java 源代码,支持多文件标签浏览、高亮显示,并兼容 Java 5 及以上版本。JD-GUI 支持对整个 Jar 文件进行反编译,可跳转源码,适用于多种 JDK 和编译器。其原理基于将字节码转换为抽象语法树 (AST),再通过反编译生成代码。尽管程序可能带来安全风险,但可通过代码混淆降低可读性。最新版修复了多项识别错误并优化了内存管理。
3336 1
|
6月前
|
存储 算法 安全
Java中的对称加密算法的原理与实现
本文详细解析了Java中三种常用对称加密算法(AES、DES、3DES)的实现原理及应用。对称加密使用相同密钥进行加解密,适合数据安全传输与存储。AES作为现代标准,支持128/192/256位密钥,安全性高;DES采用56位密钥,现已不够安全;3DES通过三重加密增强安全性,但性能较低。文章提供了各算法的具体Java代码示例,便于快速上手实现加密解密操作,帮助用户根据需求选择合适的加密方案保护数据安全。
431 58