Java中HashMap的源码分析
先来回顾一下Map类中常用实现类的区别:
HashMap:底层实现是哈希表+链表,在JDK8中,当链表长度大于8时转换为红黑树,线程不安全,效率高,允许key或value为null
HashTable:底层实现是哈希表+链表,线程安全,效率低,不允许key或value为null(现在不推荐使用)
TreeMap:底层实现是红黑树,即可按照键的大小排序,如果是自定义的类,可以通过实现Comparator接口重写compare方法
为了更好的理解,建议先对数据结构中链表和哈希表有一个认识^ ^
通俗来说哈希表就是通过一个哈希函数将当前元素的关键字映射到数组中的一个存储位置。
即 存储位置 = f(关键字),其中f即为哈希函数。
因为存储空间有线,我们这个函数不可能做到输入任意元素都能得到不同的存储位置,所以当两个不同的元素通过哈希函数得到了相同的存储地址时,就出现了哈希冲突,这里的链表就是为了解决哈希冲突存在的,我们可以将相同地址的元素以链表的形式存储在相应的位置。HashMap就是运用了这种链表+数组的方式。即如下图所示。
然后我们来一点一点分析HashMap的源码。
HashMap的主干是一个Node数组,每一个Node包含一个key-value键值对。
// table即为HashMap的主干数组,该数组长度为2的次幂 transient Node<K,V>[] table;
我们可以再来看一下Node的源码。可以看到每个Node都是由一个hash值、k-v键值对、next引用(指向下一个Node)构成。
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; // 下面的不是很关键,我们主要关注Node的结构 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; } 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; } }
了解了HashMap的内部结构,我们就先来看看简单一点的get方法。
从这里可以看到get函数很简单,就是通过获取所需要查询元素的哈希值找到节点,然后返回节点的value,所以其中关键就是getNode函数。
public V get(Object key) { Node<K,V> e; 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) { 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; }