目录
目录
准备用HashMap存1w条数据,构造时传10000还会触发扩容吗?
准备用HashMap存1000条数据,还可以用1000初始化吗?
Map
已经被社会教育了千万遍,将HashMap的整体进行回顾与学习,有问题的请私信指教,共同学习与成长。以下将会进行简称HashMap(HM)
可以简单的将其看成是一个容器,存储的数据不是简单的String或者Integer,是有一点结构的数据。Java7中是Entry<K,V>,Java8中是Node<K,V>结构。
容器的作用就是对数据进行存储,存储就会有容量的概念,固定长度或者动态变化(根据数据内容),
case:
1、两个非常常用的不可变长度的集合:Collections.singletonList 和 Arrays.asList.
2、ArrayList 则是根据阈值动态扩容
扩容机制
-
HashMap默认使用扩容因子0.75.
(扩容因子的含义类似于数组数据饱满度,数组长度为10,则达到0.75的饱满度即存储8个数据时会触发自动扩容)。
扩容原理时:创建新的数组,将原数组的数据放到新的数组上。扩容成功后,数组长度是原来的2倍。
很多人会疑惑为什么使用0.75?
case:
使用1时,数据的出现hash冲突是很大的,这样会造成产生的红黑树就比较复杂,在进行数据查询时,效率降低。(时间浪费)
使用0.5时,数据出现hash冲突的机会是降低了,链表及红黑树相对简单,数据查询效率提升。但是数据只填充了一半就触发了扩容,对数据的内存造成了浪费。(空间浪费)
对于时间与空间进行折中处理,使用0.75 。
-
HashMap的初始数组的长度为16.
(2的n次幂) 分配过小防止频繁扩容,分配过大浪费资源。
//HashMap 进行put操作时,需要计算数据的下标。计算方法如下
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列(降低hash冲突)。因为&和|都会使得结果偏向0或者1 ,并不是均匀的概念,所以用^。
-
链表转化为红黑树链表长度超过8,当数据元素小于6时恢复链表
链表长度超过8,另一次含义就是发生了8次的hash冲突(hash冲突时,才会存储到链表上)。【链表长度达到8个元素的概率为0.00000006,几乎是不可能事件,再变大没啥意义。】
红黑树退化为链表阈值为6?
中间有个差值7可以防止链表和树之间频繁的转换。若设置为小于8进行链表、红黑树转化。如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。
线程不安全
HashMap
的线程不安全主要体现在下面两个方面: 不安全
1.在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况。【rehash操作】
2.在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况。
put数据流程
java8
java7
经典题:
准备用HashMap存1w条数据,构造时传10000还会触发扩容吗?
// 预计存入 1w 条数据,初始化赋值 10000,避免 resize。
HashMap<String,String> map = new HashMap<>(10000)
// for (int i = 0; i < 10000; i++)
解析:
HashMap中有个非常巧妙的设计,初始化的时候有个tableSizeFor()方法,其
作用:(不考虑大于最大容量的情况)是返回大于输入参数且最近的2的整数次幂的数。比如10,则返回16。
目的:HashMap内部的数组大小强制为2的幂次方,这样在根据key的hash值通过按位与非常效率的找到key在数组中的位置。
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
固题目中的初始化为10000时进行tableSizeFor处理之后是 2 的 14 次幂 16384,默认扩容因子0.75,则达到16384*0.75 = 12288时才会触发扩容,很明显10000<12288,所以不会触发扩容操作。
准备用HashMap存1000条数据,还可以用1000初始化吗?
根据上面的分析,1000经过tableSizeFor处理之后是 2的10次幂 1024,再经过扩容因子0.75后是,1024*0.75 = 768 ,很明显 768< 1000, 此时便会触发扩容操作。
是否需要扩容分两步走:
1、tableSizeFor() 处理之后的结果(2的次幂)
2、扩容因子的阈值限定
1.8中做了哪些优化优化?
https://www.bilibili.com/read/cv6114752/
数组+链表改成了数组+链表或红黑树
链表的插入方式从头插法改成了尾插法
扩容的时候1.7需要对原数组中的元素进行重新hash定位在新数组的位置,1.8采用更简单的判断逻辑,位置不变或索引+旧容量大小;
- 在插入时,1.7先判断是否需要扩容,再插入,1.8先进行插入,插入完成再判断是否需要扩容;