HashMap也是一个储存价值对的常用方法,她的底层由哈希表实现(数组),用拉链法解决哈希碰撞(链表)。
它继承于AbstractMap,实现了Map<K,V>, Cloneable, Serializable接口。
jdk1.8引入了红黑树,来解决链表长度过长导致的查询速度下降问题,当碰撞过多,链表过长,这时我们将链表用红黑树来替代,以降低查询效率,所以在hashMap中同时有链表节点(Node)的结构,也有树节点(TreeNode)的结构
Node & TreeNode
普通节点Node:hash值,key,value,只想下一个节点的指针
树节点TreeNode:
成员变量:父节点,左孩子,右孩子,前一个键值对,颜色。hash, K key, V val, Node<K,V> next
方法:取跟节点root(),根据哈希值和key取树节点,将链表树化,将树链表化,向树添加一个节点,删除一个节点,split截取等
1 | // 普通节点 |
成员变量
从成员变量就可以看出来大概的设计。
和ArrayDeque类似,数组长度是不大于2的30次方的2的次幂,当数组被填充75%以上时进行数组扩展。
当链表的长度达到8就转为红黑树
树的大小到6就转回链表
当整个hashmap的容量超过64之后才能转成树
1 | // capacity初始值,为16,必须为2的次幂 |
方法
增加一个元素
既然是hash,就需要选一个hash函数来将元素散列。
这里的hash函数如下,这里用到的方法很简单,就是把key与其高16位异或。这是一个权衡了速度,散裂度等因素之后的一个这种方法
1 | static final int hash(Object key) { |
添加过程如下:
添加元素先判断是不是空表,是则初始化。
根据hash来查看
tab[i = (n - 1) & hash])
是不是空值,是空值则直接插入不是的话,判断
tab[i = (n - 1) & hash])
的key是不是将要插入的元素的key,是则修改值。判断
tab[i = (n - 1) & hash])
是不是树节点if (p instanceof TreeNode)
,如果是,则将该节点添加进红黑树e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
若不是树结构,则说明要使用链表结构,将元素添加到链表尾部。添加过程需要向后循环,若发现节点数大于
TREEIFY_THRESHOLD
则需要从链表变成树结构,调用treeifyBin(tab, hash);
,若果发现key相同则修改元素的值。添加后看容量是否到达了75%,是的话就扩充容量,还有一些别的插入后的操作
扩容
判断是否超过了最大容量,是的话返回原来的表。否则扩大二倍,若扩大二倍后超过了最大容量则返回最大的容量表。
扩容结束后需要将原来的表复制到新的表。
总结
设计的key对象一定要实现hashCode方法,并尽可能保证均匀少重复。
如果可以预先估计数量级,可以指定initial capacity,以减少rehash的过程。
虽然HashMap引入了红黑树,但它的使用是很少的,如果大量出现红黑树,说明数据本身设计的不合理,我们应该从数据源寻找优化方案。