简介
HashMap最早出现在JDK1.2中,底层基于散列算法实现。HashMap 允许 null 键和 null 值,是非线程安全类,在多线程环境下可能会存在问题。
1.8版本的HashMap数据结构:
为什么有的是链表有的是红黑树?
默认链表长度大于8时转为树
结构
Node是HhaspMap中的一个静态内部类 :
//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; //构造函数 Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } // getter and setter ... toString ... 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; } }
TreeNode 是红黑树的数据结构。
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; // needed to unlink next upon deletion boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } /** * Returns root of tree containing this node. */ final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } }
类定义
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
变量
/** * 默认初始容量16(必须是2的幂次方) */ static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; /** * 最大容量,2的30次方 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 默认加载因子,用来计算threshold */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 链表转成树的阈值,当桶中链表长度大于8时转成树 threshold = capacity * loadFactor */ static final int TREEIFY_THRESHOLD = 8; /** * 进行resize操作时,若桶中数量少于6则从树转成链表 */ static final int UNTREEIFY_THRESHOLD = 6; /** * 桶中结构转化为红黑树对应的table的最小大小 当需要将解决 hash 冲突的链表转变为红黑树时, 需要判断下此时数组容量, 若是由于数组容量太小(小于 MIN_TREEIFY_CAPACITY ) 导致的 hash 冲突太多,则不进行链表转变为红黑树操作, 转为利用 resize() 函数对 hashMap 扩容 */ static final int MIN_TREEIFY_CAPACITY = 64; /** 保存Node<K,V>节点的数组 该表在首次使用时初始化,并根据需要调整大小。 分配时, 长度始终是2的幂。 */ transient Node<K,V>[] table; /** * 存放具体元素的集 */ transient Set<Map.Entry<K,V>> entrySet; /** * 记录 hashMap 当前存储的元素的数量 */ transient int size; /** * 每次更改map结构的计数器 */ transient int modCount; /** * 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容 */ int threshold; /** * 负载因子:要调整大小的下一个大小值(容量*加载因子)。 */ final float loadFactor;
构造方法
/** * 传入初始容量大小,使用默认负载因子值 来初始化HashMap对象 */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 默认容量和负载因子 */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * 传入初始容量大小和负载因子 来初始化HashMap对象 */ public HashMap(int initialCapacity, float loadFactor) { // 初始容量不能小于0,否则报错 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); // 初始容量不能大于最大值,否则为最大值 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //负载因子不能小于或等于0,不能为非数字 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 初始化负载因子 this.loadFactor = loadFactor; // 初始化threshold大小 this.threshold = tableSizeFor(initialCapacity); } /** * 找到大于或等于 cap 的最小2的整数次幂的数。 */ static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
tableSizeFor方法详解:
用位运算找到大于或等于 cap 的最小2的整数次幂的数。比如10,则返回16
- 让cap-1再赋值给n的目的是使得找到的目标值大于或等于原值。例如二进制
0100
,十进制是4,若不减1而直接操作,答案是0001 0000
十进制是16,明显不符合预期。 - 对n右移1位:001xx…xxx,再位或:011xx…xxx
- 对n右移2位:00011…xxx,再位或:01111…xxx
- 对n右移4位…
- 对n右移8位…
- 对n右移16位,因为int最大就
2^32
所以移动1、2、4、8、16位并取位或,会将最高位的1后面的位全变为1。 - 再让结果n+1,即得到了2的整数次幂的值了。
附带一个实例: