• Set之HashSet


    set接口常用方法

     注意

    1. 添加的顺序和取出的顺序不一致,但取出的顺序是固定的,第二次取得和第一次取得一样
    2. set中不允许有重复值,但可以有null

    hashSet源码解读:

    1)底层其实是hashmap来实现

    2)添加的时候add的真正含义,源码解读

     分析HashSet的添加元素底层是如何实现(hash()+equals())

    先说结论:

    1)HashSet的底层是一个HashMap

    2)添加一个元素的时候,会先得到hash值->转化成索引值

    3)找到存储数据表,看这个索引位置是否已经存放的有元素

    4)如果没有,直接加入

    5)如果有,调用equals比较 (String比较的是内容,不同类实现不同),如果相同,就放弃添加,如果不相同,则添加到最后

    6)在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8)【树化条件1】,并且table的大小 >= MIN_TREEIFY_CAPCITY(默认64)【树化条件2】,就会进化成红黑树

    案例追踪源码

    代码

     1)执行HashSet

     2)进入add方法

     3)执行put方法

    key不管怎么变化,value都是固定不变的

     value 是2中的PRESENT

     hash(key)方法

    4)执行putVal方法

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    HashMap.Node[] tab; // 辅助结点
    int n;
        // table是hashmap中的一个数组,类型是node[]
    // if 语句表示如果当前table是空或者大小=0
    // 第一次扩容到16
        if ((tab = this.table) == null || (n = tab.length) == 0) {  
    n = (tab = this.resize()).length;
    // resize()函数解读

              

              

              // newThr 是临界值,到12的时候我们就要进行扩容了。而不是傻傻的等到16

        }

    Object p;
    int i;
    // 1)根据key,得到hash,去计算该key应该存到table表的哪个索引位置
    // 并把这个对象赋给p
    // 2)判断p 是否为null
    // 2.1)如果p为null,表示还没有存放过元素,就创建一个node(key="java",value=PRESENT)
    // 2.2)就放在该位置tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
        if ((p = tab[i = n - 1 & hash]) == null) {
    // 传进来hash,为了和下一个传进来的对比,如果相等,往后走
    tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
    } else {
    Object e;
    Object k;
    if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
    e = p;
    } else if (p instanceof HashMap.TreeNode) {
    e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
    } else {
    int binCount = 0;

    while(true) {
    if ((e = ((HashMap.Node)p).next) == null) {
    ((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
    if (binCount >= 7) {
    this.treeifyBin(tab, hash);
    }
    break;
    }

    if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
    break;
    }

    p = e;
    ++binCount;
    }
    }

    if (e != null) {
    V oldValue = ((HashMap.Node)e).value;
    if (!onlyIfAbsent || oldValue == null) {
    ((HashMap.Node)e).value = value;
    }

    this.afterNodeAccess((HashMap.Node)e);
    return oldValue;
    }
    }

    ++this.modCount;
    // 判断当前数据是否大于12,如果大于12,再扩容
    if (++this.size > this.threshold) {
    this.resize();
    }

    // 这个是hashMap留给子类的一个空函数,为了让子类能有更多实现
    this.afterNodeInsertion(evict);
    // 返回null 代表add成功
    return null;
    }


    第二次的添加php同上

    第三次添加相同java走的源码:
    // 此时3的位置已经有java了
    if ((p = tab[i = n - 1 & hash]) == null) {
    tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
    } else { // 走这里
    Object e;
    Object k;
    // 下面的p

            

                      

            // 如果当前索引位置对应链表的第一个元素和准备添加的key的hash值一样
    // 并且满足下面两个条件之一
    // 1)准备加入的key和p指向的node结点的key是同一个对象
    // 2)p指向的node结点的key的equals()和准备加入的key比较后相同(eauals()重写是程序员自己决定的)
    // 就不能加入
            if (((HashMap.Node)p).hash == hash &&  
    ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
    e = p;
    }
    // 再判断p是不是一颗红黑树
    // 如果是一颗红黑树,就调用putTreeVal(),来进行添加
    else if (p instanceof HashMap.TreeNode) {
    e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
    // 如果table对应索引的位置,已经是一个链表,就使用while循环比较
    // 1)依次和该链表的每一个元素比较后,都不相同,则加入到链表的最后
    // 注意在把元素添加到链表后,立即判断,该链表是否已经达到8个结点
    // 就调用treeifBin()对当前这个链表进行树化(转成红黑树)
    // 注意,在转成红黑树的时候,要进行判断,判断条件如下
    // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPCITY(64))
    // resize();
    // 如果上面条件成立,先table扩容
    // 只有上面条件不成立时,才转化成红黑树
    // 2)依次和该链表的元素比较的过程中,如果有相同的,就直接break

                          


    } else {
    int binCount = 0;
    while(true) {
    if ((e = ((HashMap.Node)p).next) == null) { // 为空
    ((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
    if (binCount >= 7) {
    this.treeifyBin(tab, hash); // 直接扔进去
    }
    break;
    }

    if (((HashMap.Node)e).hash == hash // 如果不为空,比较要加入的和当前存在链表中的结点是否相同
    && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
    break;
    }

    p = e;
    ++binCount;
    }
    }

    // 上面代码p和e的解读

             


    if (e != null) {
    V oldValue = ((HashMap.Node)e).value;
    if (!onlyIfAbsent || oldValue == null) {
    ((HashMap.Node)e).value = value;
    }

    this.afterNodeAccess((HashMap.Node)e);
    return oldValue;
    }
    }
  • 相关阅读:
    用户登陆显示cpu、负载、内存信息
    递归算法总结
    Java算法之递归打破及在真实项目中的使用实例
    史上最简单,一步集成侧滑(删除)菜单,高仿QQ、IOS。
    仿饿了么购物车下单效果
    一起来写个酷炫的水波纹浪啊浪界面
    一行实现QQ群组头像,微信群组,圆角等效果. 并支持url直接加载图片
    使用 CoordinatorLayout 实现复杂联动效果
    这交互炸了(三) :不看后悔!你一定没见过这样的闪屏
    这交互炸了(二):爱范儿是如何让详情页缩小为横向列表的
  • 原文地址:https://www.cnblogs.com/YXBLOGXYY/p/14787847.html
Copyright © 2020-2023  润新知