Collection-Set

本文详细解析了Java中的Set接口及其实现类HashSet的底层实现。HashSet基于HashMap实现,保证无序且不允许重复元素。在添加元素时,通过hashCode和equals方法确定元素位置,当链表达到一定长度时会转换为红黑树以优化查找效率。文章通过代码分析阐述了元素插入过程中的哈希计算、冲突解决以及树化条件。
摘要由CSDN通过智能技术生成
1. set 接口节本介绍

**

  1. 无序 (添加和取出的顺序不一致),取出的顺序是固定的,没有索引

  2. 不允许重复元素/对象,所以做多包含一个null

  3. JDK API中Set接口的实现类有

  4. 底层是[数组+链表+红黑树]来实现的,类似下图的结构
    在这里插入图片描述

  5. 遍历用迭代和增强for循环

2. hashSet 的底层

Set hashSet = new HashSet();

  1. hashSet底层构造函数中 new 了一个HashMap();
public HashSet(){
	map = new HashMap<>();
}
  1. 添加一个元素时,先得到hash值, 会转成 -> 索引
  2. 找到存储数据表table,看这个索引位置是否已经存放的有元素(hash值+equals)
  3. 如果没有,直接加入
  4. 如果有,调用equals 比较,如果相同,就放弃添加,如果不同,则添加到最后
  5. 在java8 中,如果一条链表的元素元素个数到达 TREEIFY_THRESHOLD (默认8),并且table的大小>= MIN_TREEIFY_CAPACITYMIN(默认64)就会进行树化(红黑树)
  • 代码分析(以下代码为例)
Set hashSet = new HashSet();
hashSet.add("java");
hashSet.add("php");
hashSet.add("java");
System.out.println("set="+hashSet);
    1. 执行 HashSet()
public HashSet(){
	map = new HashMap<>();
}
  • 2.执行hashSet.add("java)
public boolean add(E e){ // e:"java"
	return map.put(e,PRESENT) == null; // map:"{}" e:"java"
}

注意 put方法中PRESENT 源代码为

private static final Object PRESENT  =  new Object()

实际上没有任何意义,起到一个占位的作用

  • 3.执行 put()方法

       该方法会执行hash(key) 得到key对应的hash值 算法 h = (h = key.hashCode()) ^ (h >>> 16) 
    
public V put(K key V value) { // key:"java" value:Object@550  此时的value 为 PRESENT
	return putVal(hash(key) , key,value,false ,true);
}
  • 4.执行hash(key)方法
static final int hash(Object key){ //key:"java"
    int h;
    return (key == null) ? 0: (h = key.hashCode()) ^ (h >>> 16); //key:"java"
}
  • 5.执行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 是HashMap的一个数组, 数组中存放的Node[]
    //if 语句表示 如果当前 table 是 null 或者 大小=0
    // 就是第一次扩容,扩容到16个空间
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //根据key,得到hash  然后去计算该key应该存放到table表的那个索引位置
    //并把这个位置的对象,赋给 P
    //判断p 是否为null 
    //如果p为null,表示还没有存放元素,就创建一个Node (key="java",value=PRESENT)
    //放在该位置 tab[i] =  newNode(hash, key, value, null);
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        //一个开发技巧提示:在需要辅助变量或局部变量的时候,在创建
        Node<K,V> e; K k;
        //如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
        //并且满足下面两个条件之一:
        //准备加入的key 和 p指向的Node节点的key 是同一个对象
        //p指向的Node节点的key的equlas() 和 准备加入的key 比较后相同
        //就不能加入
        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循环比较
            // 一次和该链表的每一个元素比较后,都不相同,则加入到该链表的最后
            	//注意 在把元素添加到链表后, 立即判断 该链表是否已经达到8个节点, 就调用treeifyBin() 对当前这个链表进行
            	//进行树化(转成红黑树)
            	//注意 在转成红黑树时,要进行判断,如果该table数组的大小<64 
            	//判断条件[if(tab == null) || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize();]
            	//如果上面条件成立时,先table数组扩容
            	//只有上面条件不成立时,才进行转成红黑树
            // 一次和该链表的每一个元素比较过程中,如果有相同情况,就直接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)
        resize();
    afterNodeInsertion(evict);
    return null;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值