取模,但是由于取模的消耗较大,HashMap采用 h & (length-1) 的方式处理。HashMap的底层数组长度总是2的n次方,在构造函数中存在:capacity <<= 1;这样做总是能够保证HashMap的底层数组长度为2的n次方。当length为2的n次方时,h & (length - 1) 就相当于对length取模,而且速度比直接取模快得多,这是HashMap在速度上的一个优化。
为什么hashcode还要异或计算?
^异或运算:相同置0,不同置1。0和1的概率都为1/2。
js 0^1 = 1
1^1 = 0
1^0 = 1
0^0 = 0
& 与运算:两个同时为1,结果为1,否则为0。结果偏向0。
js 1&1 = 1
1&0 = 0
0&0 = 0
0&1 = 0
| 或运算: 只要有1 结果就为1。结果偏向1
js 1|1 = 1
1|0 = 1
0|0 = 0
0|1 = 1
为什么要右移16位?
保证高16位也参与计算, 我们直到int占4字节 32位,16是中位数
因为大部分情况下,都是低16位参与运算,高16位可以减少hash冲突
这会衍生一个疑惑 如果高16位都为0呢?
即使高位为0,异或特性: 0 异或任何数 都为任何数它本身 ,那就是返回key.hashCode() ,不会影响它本身。
在Java中,HashMap的插入元素流程涉及一系列步骤,包括计算哈希码、确定数组索引、处理冲突、以及可能的扩容。下面是详细的过程:
计算哈希码:
确定数组索引:
HashMap是基于Map接口的非同步实现(同步就是一个对象只能一个线程访问),它是以Key-value键值对的形式(封装成 Node (节点) 对象)存取数据的,线程不安全的集合,允许null键和null值,只能一个null键,但是可以有多个null值。
JDK7之前是数组+链表的形式(数组的每个位置都存储一个单向链表),JDK8后是数组+链表+红黑树的形式(链表的数据达到一定的阙值(8)就会转换成红黑树)。
容量:哈希表中数组的数量,默认初始容量是16(必须的2的倍数,提高计算机的执行效率)
加载因子:默认值为0.75
扩容阙值:容量 * 加载因子
非同步实现,底层通过一个modCount记录修改的次数,修改后回增加modCount值,迭代器初始时会把modCount值赋值给exceptedModCount,在迭代的过程中,如果二者的值不一样,则报异常。
ArrayList在进行元素删除操作后,通过以下方式确保元素的顺序保持一致:
元素移动:当在ArrayList中删除元素时,删除操作会导致数组中删除位置后的元素向前移动,以填补被删除元素的位置,从而保持元素的顺序一致。这确保了删除元素后,其他元素的顺序没有改变。
元素下标更新:删除操作完成后,ArrayList会更新被删除元素之后的元素的下标(索引),使得每个元素的下标仍然与其在数组中的位置对应。这样可以保证通过索引随机访问元素时仍然能够准确获取到对应位置的元素。
动态调整:如果删除操作导致数组中元素数量显著减少,ArrayList可能会考虑进行动态调整,即缩减数组的大小以节省内存空间。这样做的目的是保持ArrayList的空间利用率高效,但不会影响元素的顺序。
不重新排序:ArrayList在进行元素删除操作时,并不会重新排序数组中的元素。删除操作只会影响删除位置之后的元素,而不会改变元素在原始顺序中的相对位置。
总的来说,ArrayList通过元素移动和下标更新的方式,在进行元素删除操作后保持元素的顺序一致。这种设计保证了对ArrayList进行元素删除操作后,其他元素的顺序不会受到影响,仍然保持原始顺序。