基于JDK版本:1.8
一、核心概念
数据结构:从jdk1.8之后,HashMap采用数组+链表+红黑树(Red-Black Tree)。因为这个特性,HashMap的查询时间复杂度理论上为O(1),但实际上是:O(1)+O(n)+O(logn),因为链表长度大于(TREEIFY_THRESHOLD = 8 )会自动转成红黑树,所以当数据Key的哈希值冲突时,时间复杂度将会达到O(logn)。
key和value可以为null,但key只能存在一个null,位于组数的0位置:因为hash(key)方法,当key=null ,返回0。存储对象时,存储为key的对象需要重写equals()方法。
线程不安全,多线程高并发情况下,容易形成 循环链表。高并发场景可以使用Collections.synchronizedMap(Map map) 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。
HashMap初始容量为16,超过:扩容阈值(threshold) = 哈希表容量 * 加载因子(load factor) 进行扩容, 每次扩容2的倍数。且保证容量为2^n个数 。
HashMap JDK1.8 中扩容后,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap。在 1.7的时候扩容后,链表的节点顺序会倒置,1.8则不会出现这种情况。
hash方法的区别:HashMap 会对 key 的 hashCode 返回值做进一步扰动函数处理。
1.7 中扰动函数使用了 4次位运算 + 5次异或运算;
1.8 中降低到 1次位运算 + 1次异或运算;;
扰动处理后的 hash 与 哈希表数组length -1 做位与运算得到最终元素储存的哈希桶角标位置。
HashMap数据结构
二、关键参数
//默认初始化容量,保证为2的倍数。
static final int DEFAULT_INITIAL_CAPACITY = 1 <4; // aka 16
//最大容量
static final int MAXIMUM_CAPACITY = 1 <30;
//默认加载因子:如果因子太小,则会频繁扩容,因子太大,则键冲突多,转成链表或树。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//触发转成树时链表(桶)的临界阈值,大于2,小于8.
static final int TREEIFY_THRESHOLD = 8;
//转成链表时树的个数临界阈值
static final int UNTREEIFY_THRESHOLD = 6;
//转成树时数组的个数,至少为4*TREEIFY_THRESHOLD,才能避免扩容与转换成树冲突。
//在treeifyBin()方法内有判断
static final int MIN_TREEIFY_CAPACITY = 64;
//加载因子,如果未指定使用的是,DEFAULT_LOAD_FACTOR——无参构造方法
final float loadFactor;
//库容阈值 = 容量 * 加载因子
int threshold;
//存储哈希桶的数组,哈希桶中装的是一个单链表或一颗红黑树,长度一定是 2^n
transient Node[] table;//所有键值对的Set集合 区分于 table 可以调用 entrySet()得到该集合transient Set> entrySet;//HashMap中存储的键值对的数量注意这里是键值对的个数而不是数组的长度(table.length)transient int size;操作数记录 为了多线程操作时 Fast-fail 机制transient int modCount;
三、主要方法
构造方法
主要决定两个参数:loadFactor和threshold。
其中threshold通过方法:tableSizeFor(initialCapacity)得到
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;
}该方法永远返回一个 <