1、hashmap存储原理
jdk1.8 对 hashmap 底层的实现进行了优化,例如引入了红黑树的数据结构和扩容的优化。
HashMap的底层是:数组+红黑树+链表
在 JDK1.7 中HashMap 的数据是存储在 类型为 Entry 的一个 table 数组 中的,在JDK1.8 中使用的是 Node,Node 是 Map.Entry 的一个子类。
1)Entry
Entry 是 HashMap 的一个静态内部类:Entry 中封装了 key 和 value
key 和 Value 是已知的,next 指的是指向下一个链表结点,那 hash 是什么呢?
Node.hash 是指 key 对应的 hash 值。
2)Node
Node是Entry的一个子类:
Node 类中的这个 hash,是 key 的 hash 值经过一次扰动后得到的 哈希值。
Hash 会发生碰撞,有相同 key 的会放置到链表中。Hash 碰撞带来的问题:相同 key 的链表的长度会很长,但是 get(key) 请求,在找节点时,时间复杂度会很大。
2、HashMap涉及到的数据结构
散列就是来整合数组和链表的优势和劣势的,在散列表的数组中保存的数据是链表。
1)数组
数组的缺点:不能扩展空间
数组的优点:根据index查找的快
2)链表
链表的缺点:只保留head节点,但是想要访问最后一个元素,只能从头元素开始遍历
链表的优点:链表可以扩展空间
3)平衡二叉树
它是一棵空树,或者它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
4)红黑树
红黑树的提出,是为了解决相同 key 的链表的长度会很长,从而带来的链化问题,提高查找效率。
一种含有红黑结点,并且能自平衡的二叉查找树。
它具备的性质:
1)每个结点要么是黑色,要么是红色;
2)根节点是黑色;
3)每个叶子结点是黑色;
4)每个红色结点的两个子结点一定都是黑色,红色结点不能和红色结点直接相连;
5)任意一个结点到叶子结点的路径都包含数量相同的黑结点。
3、hash算法
哈希算法是一类算法的统称,也叫作散列算法。
f(data)=key;
- 输出数据长度固定:输入任意长度的 data 数据,经过哈希算法处理后输出一个定长的数据 key,该输出就是散列值(把任意长度的输入,通过Hash算法变成固定长度的输出)。
- 单向特征:无法由 key 逆推出 data,即哈希算法是一种单向密码机制,即它是一个从明文到密文的不可逆映射,只有加密过程而没有解密过程。
- 常用的哈希算法:MD4、MD5、SHA-1
4、负载因子
比如我们存储70个元素,但我们可能为这70个元素申请了100个元素的空间,70/100=0.7,这个数字称为负载因子。
5、路由寻址公式
(table.length - 1) & node.hash()
假设 n=table.length
首先要明白 tab[(n-1) & hash] 中的 (n-1) & hash 即 路由寻址公式 的妙处:
(1)保证不会发生数组越界。数组的长度的二进制形式是 1000…000,那么 n-1 的二进制形式就是 0111…111,这个值和hash值进行与操作,结果一定不会比数组的长度值大,即不会发生数组越界。
(2)保证元素尽可能地均匀分布。假设此时
n=16; //0x10000
n-1=15; //0x1111
假设现在有两个元素要插入,一个哈希值是8,二进制为 1000; 一个哈希值是9,二进制为1001。
见 7 中的 hash() 函数,
假如table.length-1为15即0x1111,那么使参与“&”运算的不止是hashCode的低16位,使其也包含高位的值(hashCode的高16位 异或 hashCode的低16位,然后进行与运算)。
6、HashMap中较为重要的几个常量
1)static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 ,即1左移4位,缺省table大小
2)static final int MAXIMUM_CAPACITY = 1 << 30; //table最大长度,即数组最大长度
3)static final float DEFAULT_LOAD_FACTOR = 0.75f; //缺省负载因子大小,0.75是科学家精确计算后的
4)static final int TREEIFY_THRESHOLD = 8; //树化阈值,当发生哈希碰撞后会形成链表,链表长度到8后,可能形成一个树了
5)static final int UNTREEIFY_THRESHOLD = 6