HashMap是JDK提供的一个集合工具类,也是最常用的工具类之一。
HashMap本身可以介绍的内容很多,所以小西准备用一个系列来讲,该篇文章先通过一个简单的例子把它的部分原理讲明白,后面的文章再慢慢深入介绍其他部分。
数据结构
- 顶层是一个数组
- 当发生hash冲突时,数组中的元素会转变成链表的头部或者红黑树的顶点
\
案例
首先我们看下运行代码:
public static void main(String[] args) {
//初始化代码,插入12条数据
Map<Integer, Integer> map = new HashMap<>(20);
for(int i=0;i<12;i++) {
map.put(i, i);
}
}
当运行完步骤一后,我们看下HashMap中内部变量的情况
HashMap内部变量图
从上图我们可以看到:
- size为12,表示HashMap里面元素的个数
- 我们存入的12条数据被保存在了table变量的前12个位置
- table数组的长度为16
- 每个元素的index刚好和它的key和value相等
原理分析
问题一: 为什么table的长度是16呢?
因为我们调用的是HashMap的无参构造函数,它的默认长度为变量DEFAULT_INITIAL_CAPACITY的值。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
问题二: 如果我们调用了带参构造函数,内部变量table的长度是我们指定的值吗?
不一定。HashMap会通过以下方法将传入的initialCapacity值转换成第一个大于等于该值的2的指数倍的值。例如传入10的话会创建一个长度为16的table数组,传入20的话为32,以此类推。
/**
* Returns a power of two size for the given target capacity.
*/
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;
}
问题三: 为什么index为6的元素,它的key和value都是16呢?
HashMap首先会将key对象传入hash方法并得到一个int类型的hash值,然后传递给下面的putVal方法。(这里会将对象key的hashcode()返回值的高16位和低16位做一个异或操作,目的是为了减少哈希碰撞)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
putVal方法主要关注下面最后一行即可,它会把内部数组table的长度值减1和上面方法返回的hash值进行与操作得到该key对应相关插入到table数组的index。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
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);
再回到问题三,我们传入的key和value都是值为6的Integer对象(new Integer(6)),而Integer对象的hashCode()方法返回的就是该Integer对象的内部int值,调用hash方法返回的值也是6;再通过(n - 1) & hash得到的数组index也是6。
注:n-1=15,对应的二进制值为1111,hash的值为6对应的二进制值为0110,1111&0110=0110
感兴趣的同学还可以关注公众号“小西学JAVA”同步获取最新文章。
引用
HashMap内部结构图引用自 JDK1.8之HashMap实现原理 - 吴振照 - 博客园