1.HashMap 的底层是数组
// 参考代码
HashMap<String, String> map = new HashMap<String, String>();
map.push("张三","测试数据1");
map.push("李四","测试数据2");
// 底层的数据类型简单展示,当然实际上远远没有这么简单,这里是方便理解
[<张三, 测试数据1>, <李四, 测试数据2>]
2.底层原理
1. 首先会根据'张三'这个 key 计算出它的 Hash 值;
2. 拿到 Hash 值对数组的长度进行取模(数组是有固定长度的),定位到将要存入的数组中的指定位置;
[<>, <>, <>, <>, <张三, 测试数据1>, <>, <李四, 测试数据2>, <>]
3. 取值原理也是先计算处 Hash 值后进行取模,拿到指定下标获取元素;
4. 实际上 JDK 中 HashMap 的原理要比以上的逻辑复杂一些,还有一些防止 Hash 重复,数组扩容的操作等等;
3.JDK 1.8中对Hash算法和寻址算法的优化
// 代码示例
map.push("张三", "测试数据");
// 1.在 JDK 1.8 中对 “张三” 这个 key 计算 Hash 值是有一定的优化的
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (key.hashCode())^(h >>> 16);
}
// 比如说: 存在一个 key 的 hash 值
// 1111 1111 1111 1111 1111 1010 0111 1100
// 右移16位(h >>> 16):
// 0000 0000 0000 0000 1111 1111 1111 1111
// 将两个 hash 进行异或运算(对比是否一样)得出新的 hash 值:
// 1111 1111 1111 1111 0000 0101 1000 0011
// 将转换后的异或 hash(32位) 值转int(32位)进行返回;
// 2. 寻址算法优化: n 数组长度
(n -1) & hash
// 假设数组的长度为 16 位,(n -1) & hash 算出的 hash 值如下:
// 0000 0000 0000 0000 0000 0000 0000 1111
// 没有经过优化 key 的 Hash 值如下:
// 1111 1111 1111 1111 1111 1010 0111 1100
// 优化后的新 hash 值:
// 1111 1111 1111 1111 0000 0101 1000 0011
// 取模的运算性能比较差,(n -1) & hash 运算的效果和取模是一样的, 但是性能比取模高的多;
// 如果使用没有优化过的 hash 值进行 & 运算,两个值的区别很小,容易出现多个 key 算出的位置是一样的情况,因此要进行异或运算
4.总结
1.Hash 算法的优化: 对每个 hash 值,在他的低 16 位中, 让他的高低 16 位都进行异或运算, 让他的低 16 位同时保持了 高 16 位的特征,尽量避免了一些 hash 值后续出现的冲突,即多个不同的 key 存入同一个数组位置;
2. 寻址算法的优化: 用 & 运算代替了取模运算,提高了运算的性能;