Java 集合HashSet 源码解析

Java 集合 HashSet 源码解析

HashSet

介绍

  1. HashSet实现了Set接口

  2. HashSet实际上是HashMap

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

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

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

构造方法

HashSet() 构造一个新的空集合; 背景HashMap实例具有默认初始容量(16)和负载因子(0.75)。
HashSet(Collection<? extends E> c) 构造一个包含指定集合中的元素的新集合。
HashSet(int initialCapacity) 构造一个新的空集合; 背景HashMap实例具有指定的初始容量和默认负载因子(0.75)。
HashSet(int initialCapacity, float loadFactor) 构造一个新的空集合; 背景HashMap实例具有指定的初始容量和指定的负载因子。

方法

booleanadd(E e) 将指定的元素添加到此集合(如果尚未存在)。
voidclear() 从此集合中删除所有元素。
Objectclone() 返回此 HashSet实例的浅层副本:元素本身不被克隆。
booleancontains(Object o) 如果此集合包含指定的元素,则返回 true
booleanisEmpty() 如果此集合不包含元素,则返回 true
Iterator<E>iterator() 返回此集合中元素的迭代器。
booleanremove(Object o) 如果存在,则从该集合中删除指定的元素。
intsize() 返回此集合中的元素数(其基数)。

底层机制

添加元素的底层实现(hash()+equals())
  1. 先获取元素的哈希值(hashCode())
  2. 对哈希值进行运算,得出一个索引值,即要存放在哈希表中的位置下标
  3. 如果该位置上没有其他元素,则直接存放,如果已经有元素,则元素要进行equls判断,如果相等不再添加,不相等则以链表方式添加
  4. 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认8),且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
  5. 当 HashMap 中的 size >= threshold 时 或 单条链表长度超过8且table.size<64,HashMap 就要扩容。

执行过程(插入多个数据)

add(“java”) 第一次添加元素

//1 初始化 HashSet()
public HashSet() {
    map = new HashMap<>();
}
//2 执行 add()
public boolean add(E e) {
    return map.put(e, PRESENT)==null;//put返回的为空代表成功
}
//3 执行HashMap的 put()
private static final Object PRESENT = new Object();//PRESENT只是随意的一个对象 HashSet所有键值对的值都指向它
public V put(K key, V value) {//HashMap的put
    return putVal(hash(key), key, value, false, true);
}
    //3.1 执行HashMap的 hash(key) 得到hash值 算法 (h = key.hashCode()) ^ (h >>> 16)
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//>>>无符号右移16位
    }
//4 执行HashMap的 putVal()
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为空,则第一次扩容,扩容到16
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;//-------->5.1 resize()
    //(1)根据key得到hash 去计算该key一个 存放到table表的哪个索引位置 (3)
    //并把这个位置的对象 赋给 p
    //(2)判断p 是否为null
    //(2.1) 如果p为null 表示还没有存放过元素 就创建一个Node (key="java",value=PRESENT)
    //(2.2) 就放在该位置 tab[i] = newNode(hash,key,value,null)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
  	/************************/
    ++modCount;//修改了一次
    if (++size > threshold)//如果当前数据总数超过 扩容因子数12 则扩容
        /*resize();*/
    afterNodeInsertion(evict);//void afterNodeInsertion(boolean evict) { } 空的,啥也不干
    return null;//返回null表示成功
}
    //4.1 执行HashMap的 resize()
	final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
     	/************************/
        else {               //走这里
            newCap = DEFAULT_INITIAL_CAPACITY;//1 << 4 = 16 默认16
            //临界因子*默认大小 = 0.75*16 = 12 16个使用了12个就需要扩容了
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//12
        }
        /************************/
        threshold = newThr;//12
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;//16大小的空数组
      /************************/
        return newTab;
    }

add(“php”) 第二次添加元素

//1 执行 add()
public boolean add(E e) {
    return map.put(e, PRESENT)==null;//put返回的为空代表成功
}
//2 执行HashMap的 put()
private static final Object PRESENT = new Object();//PRESENT只是随意的一个对象 HashSet所有键值对的值都指向它
public V put(K key, V value) {//HashMap的put
    return putVal(hash(key), key, value, false, true);
}
    //2.1 执行HashMap的 hash(key) 得到hash值 算法 (h = key.hashCode()) ^ (h >>> 16)
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//>>>无符号右移16位
    }
//3 执行HashMap的 putVal()
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)根据key得到hash 去计算该key一个 存放到table表的哪个索引位置 (9)
    //并把这个位置的对象 赋给 p
    //(2)判断p 是否为null
    //(2.1) 如果p为null 表示还没有存放过元素 就创建一个Node (key="php",value=PRESENT)
    //(2.2) 就放在该位置 tab[i] = newNode(hash,key,value,null)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
  /************************/
    ++modCount;//修改了一次
    /************************/
    return null;//返回null表示成功
}

add(“java”)第三次添加元素(重复元素)

/*******************************/
// 执行HashMap的 putVal()
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
  	/************************/ //p是tab[3]!=nll
    else {
            Node<K,V> e; K k;
        	/*
        		如果当前索引位置对应链表的第一个元素和准备添加的key的hash值相同
        		并且满足 下面两个条件之一
                (1)准备加入的key和p.key是同一个对象
                (2)key不为null且内容相同 key.equals(p.key) [equls方法相同即可]
                
        	*/
            if (p.hash == 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循环比较
                /* 直接与p后面的元素比较(p在前面已经比较过,无需再次比较)
                	(1)依次和链表的每一个元素比较后 都不相同 则加入到该链表的最后
                		注意在链表后添加元素后,立即判断,该链表是否已经达到8个节点
                		如果此时table表大小超过64则 对当前这个链表进行树化(转换为红黑树)
                		否则调用resize()库容table表
                	(2)依次和链表的每一个元素比较过程中没如果有相同情况,则直接break 
                	
                */
                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;//修改了一次
    if (++size > threshold)//如果当前数据总数超过 扩容因子数12 则扩容
        /*resize();*/
    afterNodeInsertion(evict);//void afterNodeInsertion(boolean evict) { } 空的,啥也不干
    return null;//返回null表示成功
}
    //treeifyBin
	final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        //转换红黑树要求:table表大小要大于等于64
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();//table表大小不够 则先扩容table表
        /**********************/
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值