定义
HashMap基于哈希表的Map实现,以key-value的形式存在,并允许使用 null 值和 null 键。继承AbstractMap,当中实现了Map的重要方法。类定义如下:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
复制代码
HashMap综合了ArrayList和LinkList的优点
- 使用数组存储数据,索引实现O(1) 时间复杂度,所有查找速度比较快。同ArrayList;
- 当发生碰撞时,采用链表存储数据,解决了因hash冲突;同LinkList;
- 当链表过长时,导致链表节点的查找性能下降。因此当链表长度超过 8 时, 将链表转变成红黑树,以将O(n) 时间复杂度提升至O(log n)。
默认值
/**
* 默认初始容量16,必须是2的幂
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
/**
* 最大容量
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认负载因子。当键值对的数量大于 CAPACITY * 0.75 时,就会触发扩容
* 如无参的HashMap构造器,初始化容量为16,当键值对的数量大于 16 * 0.75 = 12时,就会触发扩容
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 计数阈值。链表的元素大于8的时候,链表将转化为树
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 计数阈值。resize操作时,红黑树的节点数量小于6时使用链表来代替树
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 在转变成树之前,还会有一次判断,只有键值对数量大于 64 才会发生转换。
* 这是为了避免在哈希表建立初期,多个键值对恰好被放入了同一个链表中而导致不必要的转化
*/
static final int MIN_TREEIFY_CAPACITY = 64;
复制代码
属性
/**
* 存储数据的哈希表,默认容量16。长度总是2的幂
*/
transient Node<K,V>[] table;
/**
* Entry集合,主要用于迭代功能
*/
transient Set<Map.Entry<K,V>> entrySet;
/**
* 实际存在的Node数量,不一定等于table的长度(初始化容量),甚至可能大于它。
*/
transient int size;
/**
* HashMap结构被修改的次数,迭代过程中遵循Fail-Fast机制。
* 保证多线程同时修改map时,能及时的发现(操作前备份的count和当前modCount不相等)并抛出异常终止操作。
*/
transient int modCount;
/**
* 扩容阈值,超过(capacity * loadFactor)时,自动扩容容量为原来的二倍。
*/
int threshold;
/**
* 负载因子,可计算threshold:threshold = capacity * loadFactor
*/
final float loadFactor;
复制代码
构造器
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
复制代码
主要谈谈HashMap(int initialCapacity, float loadFactor);实例化时,内部调用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;
}
复制代码
对于求解大于等于它,且为2的整数次幂的最小值,int n = cap - 1;很关键。 假设不执行int n = cap - 1;cap = 8。按照java8的设计思想,因为8>=2^3,结果就应该是8,但结果却是16.
8的二进制:1000
n |= n >>> 1; n = n | n >>> 1; 1000 | 0100 = 1100
n |= n >>> 2; n = n | n >>> 2; 1100 | 0011 = 1111
n |= n >>> 4; n = n | n >>> 4; 1111 | 0000 = 1111
...
n |= n >>> 16; n = 1111 = 15(十进制),最后执行n + 1 = 16
欢迎大家关注我的掘金:
作者:小码君
链接:https://juejin.im/post/5bf626ffe51d456808647100