1. 前言

Hashmap可以说是Java面试必问的,一般的面试题会问:

  • Hashmap有哪些特性?
  • Hashmap底层实现原理(get\put\resize)
  • Hashmap怎么解决hash冲突?
  • Hashmap是线程安全的吗?
  • ...

今天就从源码角度一探究竟。笔者的源码是OpenJDK1.7

2. 构造方法

首先看构造方法的源码

    // 默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 数组, 该数据不参与序列化
transient Entry[] table; public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
// 初始容量16,扩容因子0.75,扩容临界值12
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
// 基础结构为Entry数组
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}

由以上源码可知,Hashmap的初始容量默认是16, 底层存储结构是数组(到这里只能看出是数组, 其实还有链表,下边看源码解释)。基本存储单元是Entry,那Entry是什么呢?我们接着看Entry相关源码,

    static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next; // 链表后置节点
final int hash; /**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n; // 头插法: newEntry.next=e
key = k;
hash = h;
}
...
}

由Entry源码可知,Entry是链表结构。综上所述,可以得出:

Hashmap底层是基于数组和链表实现的

3. Hashmap中put()过程

我已经将put过程绘制了流程图帮助大家理解

先上put源码

    public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
// 根据key计算hash
int hash = hash(key.hashCode());
// 计算元素在数组中的位置
int i = indexFor(hash, table.length);
// 遍历链表,如果相同覆盖
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
} modCount++;
// 头插法插入元素
addEntry(hash, key, value, i);
return null;
}

上图中多次提到头插法,啥是 头插法 呢?接下来看 addEntry 方法

    void addEntry(int hash, K key, V value, int bucketIndex) {
// 取出原bucket链表
Entry<K,V> e = table[bucketIndex];
// 头插法
table[bucketIndex] = new Entry<>(hash, key, value, e);
// 判断是否需要扩容
if (size++ >= threshold)
// 扩容好容量为原来的2倍
resize(2 * table.length);
}

结合Entry类的构造方法,每次插入新元素的时候,将bucket原链表取出,新元素的next指向原链表,这就是 头插法 。为了更加清晰的表示Hashmap存储结构,再绘制一张存储结构图。

4. Hashmap中get()过程

get()逻辑相对比较简单,如图所示

我们来对应下get()源码

    public V get(Object key) {
// 获取key为null的值
if (key == null)
return getForNullKey();
// 根据key获取hash
int hash = hash(key.hashCode());
// 遍历链表,直到找到元素
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}

5. Hashmap中resize()过程

只要是新插入元素,即执行addEntry()方法,在插入完成后,都会判断是否需要扩容。从addEntry()方法可知,扩容后的容量为原来的2倍。

    void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
// 新建数组
Entry[] newTable = new Entry[newCapacity];
// 数据迁移
transfer(newTable);
// table指向新的数组
table = newTable;
// 新的扩容临界值
threshold = (int)(newCapacity * loadFactor);
}

这里有个transfer()方法没讲,别着急,扩容时线程安全的问题出现在这个方法中,接下来讲解数组复制过程。

6. Hashmap扩容安全问题

大家都知道结果: 多线程扩容有可能会形成环形链表,这里用图给大家模拟下扩容过程。

首先看下单线程扩容的头插法

然后看下多线程可能会出现的问题

以下是源码,你仔细品一品

    void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
// 释放旧Entry数组的对象引用
src[j] = null;
do {
Entry<K,V> next = e.next;
// 重新根据新的数组长度计算位置(同一个bucket上元素hash相等,所以扩容后必然还在一个链表上)
int i = indexFor(e.hash, newCapacity);
// 头插法(同一位置上新元素总会被放在链表的头部位置),将newTable[i]的引用赋给了e.next
e.next = newTable[i];
// 将元素放在数组上
newTable[i] = e;
// 访问下一个元素
e = next;
} while (e != null);
}
}
}

7. Hashmap寻找bucket位置

    static int indexFor(int h, int length) {
// 根据hash与数组长度mod运算
return h & (length-1);
}

由源码可知, jdk根据key的hash值和数组长度做mod运算,这里用位运算代替mod。

hash运算值是一个int整形值,在java中int占4个字节,32位,下边通过图示来说明位运算。

8. AD

如果您觉得还行,请关注公众号【当我遇上你】, 您的支持是我输出的最大动力。

同时,欢迎大家一起交流学习。

Java面试必问之Hashmap底层实现原理(JDK1.7)的更多相关文章

  1. Java面试必问之Hashmap底层实现原理(JDK1.8)

    1. 前言 上一篇从源码方面了解了JDK1.7中Hashmap的实现原理,可以看到其源码相对还是比较简单的.本篇笔者和大家一起学习下JDK1.8下Hashmap的实现.JDK1.8中对Hashmap做 ...

  2. 面试必问:HashMap 底层实现原理

    HashMap是在面试中经常会问的一点,很多时候我们仅仅只是知道HashMap他是允许键值对都是Null,并且是非线程安全的,如果在多线程的环境下使用,是很容易出现问题的. 这是我们通常在面试中会说的 ...

  3. Java面试必问通信框架NIO,原理详解

    NIO 流与块 通道与缓冲区 缓冲区状态变量 文件 NIO 实例 选择器 套接字 NIO 实例 内存映射文件 NIO与IO对比 Path Files NIO 新的输入/输出 (NIO) 库是在 JDK ...

  4. 一线大厂Java面试必问的2大类Tomcat调优

    一.前言 最近整理了 Tomcat 调优这块,基本上面试必问,于是就花了点时间去搜集一下 Tomcat 调优都调了些什么,先记录一下调优手段,更多详细的原理和实现以后用到时候再来补充记录,下面就来介绍 ...

  5. Java面试必问之线程池的创建使用、线程池的核心参数、线程池的底层工作原理

    一.前言 大家在面试过程中,必不可少的问题是线程池,小编也是在面试中被问啥傻了,JUC就了解的不多.加上做系统时,很少遇到,自己也是一知半解,最近看了尚硅谷阳哥的课,恍然大悟,特写此文章记录一下!如果 ...

  6. 深入解读大厂java面试必考基本功-HashMap集合

    课程简介 HashMap集合在企业开发中是必用的集合同时也是面试官面试率很高的集合,因为HashMap里面涉及了很多的知识点,可以比较全面考察面试者的基本功,想要拿到一个好offer,这是一个迈不过的 ...

  7. Java面试必问之-JUC

    JUC:java.util.concurrent (Java并发编程工具类) 代码:D:\JAVA\Java_Learning\Elipse_Project\workspace200301EE\JUC ...

  8. 来看看面试必问的HashMap,一次彻底帮你搞定HashMap源码

    HashMap结构 数组+链表+红黑树 链表大于8转红黑树,红黑树节点数小于6退回链表. 存放的key-value的Node节点 static class Node<K,V> implem ...

  9. 大厂面试必问!HashMap 怎样解决hash冲突?

    HashMap冲突解决方法比较考验一个开发者解决问题的能力. 下文给出HashMap冲突的解决方法以及原理分析,无论是在面试问答或者实际使用中,应该都会有所帮助. 在Java编程语言中,最基本的结构就 ...

随机推荐

  1. Okhttp教程 (1)

    1. 在build.gradle里引入okhttp库 dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testComp ...

  2. 21)PHP,杨辉三角

    代码展示: $n=; ;$i<=$n;$i++){ ;$k<=$i;$k++){ ||$k==$i){ $arr[$i][$k]=; }else{ ){ $arr[$i][$k] = $a ...

  3. 《运筹学基础及应用》习题1.1(b),1.1(c),1.2(a)

    用图解法求解下列线性规划问题,并指出问题具有惟一最优解,无穷多最优解,无界解还是无可行解. 习题1.1(b):$\max z=3x_1+2x_2$$$s.t\begin{cases}  2x_1+x_ ...

  4. 如何在Windows服务器上新建一个Powershell.ps1的定时任务

    背景: 有一些一次性的Powershell脚本,需要我们每次都手动执行一下,为了简化工作,现在我们可以使用Windows自带的计划任务,进行定时执行. 该教程是在Windows Server 2012 ...

  5. 信息熵、信息增益、信息增益率、gini、woe、iv、VIF

    整理一下这几个量的计算公式,便于记忆 采用信息增益率可以解决ID3算法中存在的问题,因此将采用信息增益率作为判定划分属性好坏的方法称为C4.5.需要注意的是,增益率准则对属性取值较少的时候会有偏好,为 ...

  6. GIL锁和进程/线程池

    GIL锁 1.GIL锁 全局解释器锁,就是一个把互斥锁,将并发变成串行,同一时刻只能有一个线程使用共享资源,牺牲效率,保证数据安全,也让程序员避免自己一个个加锁,减轻开发负担 带来的问题 感觉单核处理 ...

  7. FastDFS安装部署

    博主本人平和谦逊,热爱学习,读者阅读过程中发现错误的地方,请帮忙指出,感激不尽 服务器信息: Storage:192.168.247.20 Traker:192.168.247.21 一.搭建环境准备 ...

  8. python语法基础-基础-控制语句

    ###############    if条件控制语句    ############### # 以下实例 x 为 0-99 取一个数,y 为 0-199 取一个数,如果 x>y 则输出 x,如 ...

  9. Visual Studio 2017 Professional 下载

    可以进行Office 2013/2016 VSTO开发 安装平台:Win 7 SP1.Win 10. 下载: VisualStudio2017_Professional.rar

  10. spring:事务的5大隔离级别,7大传播行为

    一.五大隔离级别 ISOLATION_READ_UNCOMMITTED:读未提交 ISOLATION_READ_COMMITTED:读已提交 ISOLATION_REPEATABLE_READ:可重复 ...