Java中容器主要分为两大类:Collection和Map,让我们来看一下Map下主要的继承类和实现类。本篇主要学习HashMap源码。
与新的集合实现不同,HashTable是同步的。如果不需要线程安全的实现,使用HashMap代替HashTable。如果需要线程安全且高度并发地实现,则使用ConcurrentHashMap替代HashTable。如若非要用HashMap,则可以用 Collections.synchronizedMap(Map map) 。
那么HashMap底层是如何存储数据的呢?
简单地来说,在jdk1.7之前采用的是链地址法进行存储数据,即链表和数组结合的方法。jdk1.7源码如下:
//HashMap数据存储数据
transient Entry[] table;
//HashMap的数据都是存储在Entry数组中的。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/**
* Creates new entry.
* 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)"
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
}
HashMap是一个用于存储Key-Value键值对的集合,每一个键值对也叫做Entry。这些个键值对(Entry)分散存储在一个数组当中,当插入的Entry越来越多的时候,就会发生冲突,HashMap数组的每一个元素不只是一个Entry对象,也是一个链表的头结点,每一个Entry对象通过Next指针指向下一个节点,当发生冲突时,只需要插入到对应得链表位置即可。
HashMap初始值默认是16,原因是为了服务于从key到index映射的Hash算法。为了实现一个尽量均匀分布的Hash算法,传统的取模运算固然简单,但是效率低下,为了提高效率,采用位运算。计算公式是:index = HashCode(Key) & (Length - 1),其中,length是HashMap的长度。
如果 hash 值冲突较多的情况下,链表的长度就会越来越长,此时通过单链表来寻找对应 Key 对应的 Value 的时候就会使得时间复杂度达到 O(n),因此在 JDK1.8 之后,当链表长度超过8时,就会在添加元素的同时将原来的单链表转化为红黑树。利用红黑树可以提高HashMap的性能。
我们一起看一下HashMap的put方法。
public V put(K key, V value) {
// 对key的hashCode()做hash
return putVal(hash(key), key, value, false, true);
}
putVal的第四个参数的含义是如果是true不会改变当前的value。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果table为null则需要resize
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//此处发生哈希碰撞
Node<K,V> e; K k;
//如果节点key存在且key相同则直接覆盖value
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果当前的p是红黑树,则直接在树中插入键值对
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//判断链表长度是否大于8,如果大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
我们再来一起看一下HashMap的get方法。
public V get(Object key) {
Node<K,V> e;
//通过getNode去寻找key对应的value,判断是否为null,不是就返回对应的value值。
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果第一个节点的hash就是那么直接返回
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
//如果是红黑树的一个实例,那么久返回红黑树的节点
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}