不知道有没有遇到过面试官让你手写HashMap的。虽然我写不出来,但是有关于hashmap的几处关键点,还是应该记录一下,起码做到心中有个大体的思路。
HashMap的数据结构
数组+链表
Node源码
//Node是单向链表,它实现了Map.Entry接口
static class Node<k,v> implements Map.Entry<k,v> {
final int hash;
final K key;
V value;
Node<k,v> next;
//构造函数Hash值 键 值 下一个节点
Node(int hash, K key, V value, Node<k,v> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + = + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为true
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
hash函数
为什么要先介绍hash函数,因为hash函数决定了key存取的位置。比如我们要调用map.put("a","value1"),那我们要把“a”放在数组的哪个位置呢。通过hash函数可以帮我们计算一个散列的值(-2147483648到2147483648之间),但是我们当前的数组只有16个,越界了啊。所以需要通过对hash值取余,hashmao中的取余操作:
hash & (table.length-1)
这样做会比hash % (table.length)更加高效,但是取余只有低位参与运算,就容易产生碰撞,所以这时候就需要进行扰动,减少碰撞,使key的分布更加均匀。所以hashmap中的hash算法是这样的:
static final int hash(Object key) {//hash算法
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
首先int h = key.hashCode() h是一个4字节32位的数,接着是异或上h >>> 16 最后得到了新的hash值。
h是个32位的数,那么它右移16位后,高位(前16位)位0,而x ^ 0 = x。所以整个上面一句话的作用就是保留高位,低位和高位异或,这样子能达到的目标就是扰乱了低位,且低位具有高位的一部分属性。
扩容
当hashmap的容量超过 扩容阈值 = table数组容量 * 负载因子(默认是0.75)的时候开始扩容,table数组变成原来的2倍,旧k-v重新散列在新数组中。至于扩容后的容量为什么是原来的2倍,这里面有解释,先mark一下blog.csdn.net/weixin_4451…
红黑树
无论hash函数多么均匀,但是还是会产生碰撞,所以当hash冲突的时候,需要在链表后面追加元素,当链表的长度超过8个的时候,为了提高搜索效率会将链表转变位红黑树,一种近似平衡的二叉树。这时候hashmap的结构是这样的。
参考链接: