Java的Hashset以及其底层的HashMap分析

Hashset

原理概述:

在这里插入图片描述

源码:

public void test1(){
    HashSet hashSet=new HashSet();
    hashSet.add(1);
    hashSet.add("da");//3197
    hashSet.add(3197);
}

HashSet hashSet=new HashSet();

原理为利用hashmap
在这里插入图片描述

LoadFactor:加载因子

加载因子是表示Hsah表(table=node[])中元素的填满的程度.

若:加载因子越大,填满的元素越多,好处是,空间利用率高了,

但冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.

冲突的机会越大,则查找的成本越高.反之,查找的成本越小.因而,查找时间就越小.

因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷.

设置加载因子默认值

在这里插入图片描述

hashSet.add(1);

此时e(添加的值)为key,present为value存入hashmap

在这里插入图片描述

PRENSENT为

在这里插入图片描述

进入put

在这里插入图片描述

进入hash(key)

在这里插入图片描述

进入hashcode

当key为int时转为Integer,hashcode直接存为该值1

在这里插入图片描述

当key为string类型时:

在这里插入图片描述

&

既是位运算符又是逻辑运算符,&的两侧可以是int,也可以是boolean表达式,当&两侧是int时,要先把运算符两侧的数转化为二进制数再进行运算,而短路与(&&)的两侧要求必须是布尔表达式。举例如下:

(1)如果字符是&,则两个整数进行二进制位的与运算

(2)如果字符是 |,则两个整数进行二进制位的或运算

(3)如果字符是^,则两个整数进行二进制位异或运算

(4)如果是其他字符,则固定输出信息:ERROR

12&5 的值是多少?答:12转成二进制数是1100(前四位省略了),5转成二进制数是0101,则运算后的结果为0100即4 这是两侧为数值时;输出格式:

类似3(0011) & 4(0100) = 0

其中,运算符号&的前后都有一个空格,等号的前后也都有一个空格。
上面表示3和4做二进制的与运算,结果是0。

输入样例:

3的二进制是0011,4的二进制是0100,二者与运算的结果是0。

进入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;
    //辅助变量
    if ((tab = table) == null || (n = tab.length) == 0)//给tab与n赋值
        //当table(当前的hashmap存量)为空(即为第一次存时),进行扩容
        n = (tab = resize()).length;
    //为当前table开辟空间,下面详解
    if ((p = tab[i = (n - 1) & hash]) == null)
     /* (n-1)&hash 得到给此值在该table中得位置
       	(若n此时tab得容量为16(10000)则n-1为1111,
        当hash为3(0011),则i=1111&0011=0011=3
        
        若hash为17,则i=01111&10001=1
        
        若hash为18,则i=01111&10011=2,以此类推
        以这种方式确定该数值应该放在的位置,使该tab上能找到“合适”的索引位置装该值
        */
        //根据key转化得到的hash值赋值给索引值i,并且查看该索引位置是不是为空
        tab[i] = newNode(hash, key, value, null);
    //为空,则直接为创建一个node赋值添加
    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;//修改次数
    if (++size > threshold(12)//(size为所有添加进这个hash对象中元素的总数)
        //如果size+1后,大与预备增容大小,则resize开始增容
        resize();
    afterNodeInsertion(evict);
    //留个子类去实现使用,现在为空
    return null;
}//至此添加第一个元素成功

resize(赋容量(增容))

先拿出oldtable的容量

在这里插入图片描述

当oldcap大于0时(既不是第一次赋值时),

先判断有没有超过MAXIMUM_CAPACITY,若有如上

若没有,

将newcap=oldcap*2,并且得小于MAXIMUM_CAPACITY,oldcap得大于初始扩容的大小16

并将newThr=2*oldThr,

DEFAULT_INITIAL_CAPACITY;//默认的初始化大小为16

newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
//开始扩容的时候为0.75*16

在这里插入图片描述

如果小于或等于0,

将默认初始化值(16)直接赋给newcap

将下次开始的准备扩容的长度newthr赋值为(int)(16*0.75)

解释:hashmap的扩容不会等到容量用完才扩。这样会产生一定层度的堵塞,所以会使容量未用完时的一个Thr值就开始扩容

最后:

threshold = newThr;//赋值给threshold

并返回newtable,完成赋容量

当赋值出现哈希冲突时:

  hashSet.add("da");//3197
  hashSet.add(3197);
进入putval的else
 else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            //判断是否为相同的值的添加,hash值相同并且key相同(完全相同),且不为null
            e = p;//当相同,用原来的赋给e
        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) // 当链表数再增加,会大于8时
                        treeifyBin(tab, hash);//转换为红黑树
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;//在链表中找到完全相同的值也会break
                p = e;
            }
        }
        if (e != null) { // 当e被赋值后
            V oldValue = e.value;//取出e的value
            if (!onlyIfAbsent || oldValue == null)//onlyIfAbsent为一开始默认赋值默认为false
                //就是默认新值去覆盖旧值,或者原来的的value为null或者不存在,也会新值去覆盖旧值
                e.value = value;
            afterNodeAccess(e);
            return oldValue;//相当于赋值了,一个旧值
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值