HashMap底层简单原理

HashMap底层简单原理

首先应该明白HashMap在jk7数组加链表结构,

jk8数组加链表结构,当链表长度大于8且底层数组大小超过64时,链表转为红黑树

jk7链表采用的时头插法(多线程会产生循环链表),jk8采用的是尾插法

无论jk7还是jk8,HashMap底层数组大小都是2的阶乘倍数,扩容也是在原来基础上变成两倍。

第一种方式:

HashMap<Integer, String> map = new HashMap<>();	//空构造器
/*
	DEFAULT_LOAD_FACTOR = 0.75f 默认的加载因子(扩容阈值)
*/
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

调用默认构造只会初始化默认的加载因子为0.75f,此时不会初始化数组,初始化数组是在put操作时进行的。

当进行put操作时,会判断底层数组table是否为空,如果为空,数组长度默认为16,此时扩容阈值为12。

HashMap<Integer, String> map = new HashMap<>();	//初始化默认的加载因子为0.75
        map.put(1,"1");	//put第一个元素时,数组初始化,长度为16,放到数组下标为1的位置
        map.put(17,"17");//put第二个元素时,hash后位置和第一个元素相同,此时形成链表结构,挂在第一个元素后,下标也是1
        map.put(33,"33");//put第三个元素时,hash后位置和第一个元素相同,此时形成链表结构,挂在第二个元素后,下标也是1
        map.put(49,"49");//put第四个元素时,hash后位置和第一个元素相同,此时形成链表结构,挂在第三个元素后,下标也是1
        map.put(65,"65");//put第五个元素时,hash后位置和第一个元素相同,此时形成链表结构,挂在第四个元素后,下标也是1
        map.put(81,"81");//put第六个元素时,hash后位置和第一个元素相同,此时形成链表结构,挂在第五个元素后,下标也是1
        map.put(97,"97");//put第七个元素时,hash后位置和第一个元素相同,此时形成链表结构,挂在第六个元素后,下标也是1
        map.put(113,"113");//put第八个元素时,hash后位置和第一个元素相同,此时形成链表结构,挂在第七个元素后,下标也是1
		//put第九个元素时,hash后位置和第一个元素相同,此时链表长度从第二个元素算起已经7个,但是数组长度小于64,此时数组会扩		  容至32,元素重新hash计算其在数组中的下标位置。
        map.put(129,"129");

第二种方式(底层源码具体的实现方式,自己去看)

//此时有参构造放入的是阈值,此时底层会对这个值进行计算,得出最后数组的长度和阈值
//如果是3,计算后数组长度是4,阈值是3
//如果是5,计算后数组长度是8,阈值是6
//如果是8,计算后数组长度是8,阈值是6
//如果是9,计算后数组长度是16,阈值是12
//所以得出结论: 参数是2的倍数时,数组长度和传入参数相同,否则是该参数后面的2的最近的一个倍数
HashMap<Integer, String> map = new HashMap<>(3);	
/**
* DEFAULT_LOAD_FACTOR = 0.75f 默认的加载因子
* initialCapacity : 扩容阈值
*
*/
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
 int num = 64;
 HashMap<Integer, String> map = new HashMap<>(num);	//初始化数组长度为64
 for(int i = 1;; i++) {	//死循环
     int key = 1 + num * (i - 1);
      map.put(1 + num * i, key + "");	//向数组中添加会产生hash冲突的元素
 }
//当添加到第九个元素时,此时链表长度等于7,数组长度等于64,此时链表转为红黑树结构。

HashMap底层用了大量的位运算,位运算比用运算符的速度快非常多,因为HashMap结构经常改变,如果采用运算符的方式计算,那么速度相当的慢。

为什么hashMap数组长度是2的阶乘倍数?

​ 因为底层是一个数组,为了减少hash冲突,使元素分散均匀,在进行&(与)运算时,2的阶乘倍数的二进制数减一就全是1,此时与运算就不会改变其二进制本身的值,举例:

16的二进制减一 就是15,和一个二进制为1100 1010进行与预算,相同为1,不同为0,所以其实就是将该值对16进行取模运算,不会改变二进制低位的值,假设如果不是2的阶乘倍数,则减一后二进制就不是全是1。假设为13
      1100 1010										1100 1010
 15   0000 1111									13	0000 1100
  ---------------								----------------
      0000 1010	(不改变低位的值)					   0000 1000 (改变低位的值)
为什么hashMap扩容阈值是0.75?

​ 根据泊松分布,0.75的扩容机制,对时间和空间来说是最合理的。阈值太小,浪费空间,阈值太大,时间来不及。

为什么jk7采用头插法,jk8采用尾插法?

​ 头插在多线程时会产生循环链表的情况,尾插法不会。

头插法即插入链表的新元素替换原来位置的旧元素,旧元素放在新元素的后面,尾插法,就是新元素直接放在最后一个旧元素的后面。

hashtable 和hashMap的区别

​ hashMap允许key为null,如果key为null,则其hash值为0,默认放在首位。hashMap线程不安全。

​ hashtable不允许null值,会抛空指针。hashtable线程安全,因为put操作加了synchronized锁。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值