HashMap的源码分析
hashMap的底层实现是 数组+链表 的数据结构,数组是一个Entry<K,V>[] 的键值对对象数组,在数组的每个索引上存储的是包含Entry的节点对象,每个Entry对象是一个单链表结构,维护这下一个Entry节点的引用;有点绕,用个图来展示吧:

Entry<K,V>[] 数组部分保存的是首个Entry节点;Entry节点包含一个 K值引用 V值引用 以及 引用下一个Entry 节点的next引用;
Entry节点的java代码实现如下:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key; //key 引用
V value; //value 引用
Entry<K,V> next; //下一个Entry 节点的引用
}
下面再看下HashMap 对象的java实现代码:
包含的属性有:
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{ /**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 /**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30; /**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f; /**
* An empty table instance to share when the table is not inflated.
*/
static final Entry<?,?>[] EMPTY_TABLE = {}; /**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; /**
* The number of key-value mappings contained in this map.
*/
transient int size; /**
* The next size value at which to resize (capacity * load factor).
* @serial
*/
// If table == EMPTY_TABLE then this is the initial capacity at which the
// table will be created when inflated.
int threshold; /**
* The load factor for the hash table.
*
* @serial
*/
final float loadFactor;
}
比较重要的属性是:
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; 表明这是一个 Entry<K,V>[] 的数组类型;
下面看其无参的构造器:
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
进入以下的构造方法:
public HashMap(int initialCapacity, float loadFactor) { //initialCapacity:16 loadFactor 0.75f
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY) //MAXIMUM_CAPACITY 1073741824 false
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor)) false
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor); this.loadFactor = loadFactor; //赋值给loadFactor=0.75
threshold = initialCapacity; //赋值给threshold=16 当为16是自动扩容
init();
}
下面再看看put(E e)的方法:
public V put(K key, V value) { //如插入 key="city" value="shanghai"
if (table == EMPTY_TABLE) { //true
inflateTable(threshold); //参数为16
}
if (key == null) // false
return putForNullKey(value);
int hash = hash(key); //返回key值的hash码; 比如返回为 337
int i = indexFor(hash, table.length); //将hash 取模与16 获得的结果为 1
for (Entry<K,V> e = table[i]; e != null; e = e.next) { //遍历 Entry[1] 中的链表节点对象 包含 原先有的节点和新增进去的节点
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //当Entry中包含 相同的hash码的key 并且key和要添加的key相等即可以是否重复 则进入以下逻辑:新节点替换重复的节点
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
下面是 inflateTable(threshold) 方法的源码;
/**
* Inflates the table.
*/
private void inflateTable(int toSize) { //toSize 16
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize); //capacity=16 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); // threshold=16*0.75
table = new Entry[capacity]; //创建 Entry[] 数组长度为16
initHashSeedAsNeeded(capacity); //这个方法可以暂时不用深究
}
下面是 roundUpToPowerOf2(int i) 源码
private static int roundUpToPowerOf2(int number) { // number=16
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY //fase 返回 16
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; //number=16>1 返回 16
}
put 方法的源码分析完了之后,接下来再看一下get(Object key) 的方法; 源码:
public V get(Object key) { //如 key="name"
if (key == null) //false
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) { //key=name
if (size == 0) { //false
return null;
}
int hash = (key == null) ? 0 : hash(key); //例如 返回hash=337
for (Entry<K,V> e = table[indexFor(hash, table.length)]; //indexFor(hash, table.length)上面分析过 返回值为 1;遍历 table[1] 中的节点
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) //如果存在key的hash码相等,并且对象也相等则返回 对应的Entry 节点
return e;
}
return null; //否则返回null
}
到此,hashMap 的源码基本分析完毕了,通过源码分析我们知道HashMap的底层是 数组+链表结构来存数数据的,添加节点存储的位置是根据 key 取hash值 再取模于数组长度:返回的数值就是Entry接在在数组的哪个位置;这种方式的存储方式减少了存储的时间和空间的复杂度;
知道了hashMap是由 数组+链表 的数据结构存储数据后,我们也很容易明白hashMap 的遍历方式:
HashMap的源码分析的更多相关文章
- HashMap的源码分析与实现 伸缩性角度看hashmap的不足
本文介绍 1.hashmap的概念 2.hashmap的源码分析 3.hashmap的手写实现 4.伸缩性角度看hashmap的不足 一.HashMap的概念 HashMap可以将其拆分为Hash散列 ...
- Java——HashMap底层源码分析
1.简介 HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap 最多只允许一条记录的key为 nu ...
- Java中HashMap的源码分析
先来回顾一下Map类中常用实现类的区别: HashMap:底层实现是哈希表+链表,在JDK8中,当链表长度大于8时转换为红黑树,线程不安全,效率高,允许key或value为null HashTable ...
- HashMap方法源码分析
本文将分析put(),resize(),get()和remove()方法的源码 putval()方法 大致步骤:计算key的hash值:根据hash值计算数组下标:判断下标处是否有节点,无节点则直接插 ...
- Java源码——HashMap的源码分析及原理学习记录
学习HashMap时,需要带着这几个问题去,会有很大的收获: 一.什么是哈希表 二.HashMap实现原理 三.为何HashMap的数组长度一定是2的次幂? 四.重写equals方法需同时重写hash ...
- HashMap LinkedHashMap源码分析笔记
MapClassDiagram
- Java HashMap实例源码分析
引言 HashMap在键值对存储中被经常使用,那么它到底是如何实现键值存储的呢? 一 Entry Entry是Map接口中的一个内部接口,它是实现键值对存储关键.在HashMap中,有Entry的实现 ...
- HashMap的源码分析(一)
1.hashMap的关键值 DEFAULT_INITIAL_CAPACITY:默认初始容量16,∈(0,1<<30),实际大小为2的整数次幂: DEFAULT_LOAD_FACTOR:默认 ...
- HashMap从源码分析数据结构
1. HashMap在链表中存储的是键值对 2. 数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突.那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法 ...
随机推荐
- java学习--"==”和 equals
== 比较的是两个值是否相等 这里的值有可能是基础类型的值,也有可能是指向对象的引用 equals方法如何比较,要看这个类的equals方法是如何定义的 基类Object类的equals方法比较的是 ...
- Fiddler 抓包工具怎么使用?怎么在Android手机端的APP抓包
https://blog.csdn.net/loner_fang/article/details/83309266 参考这个人的微博上有fiddler主要功能使用的步骤. 序章 Fiddler是一个蛮 ...
- 群晖上使用kvm(qemu)笔记[原创]
1.今日偶然逛github里发现一个项目:https://github.com/bsdcpp/synoKVM 下载spk后手工安装,马上可以使用 2.新建XP实例后发现xp的安装盘不认识qemu的vi ...
- MySQL 8.0 新增SQL语法对窗口函数和CTE的支持
尝试了一下MySQL 8.0的部分新特性. 如果用过MSSQL或者是Oracle中的窗口函数(Oracle中叫分析函数), 然后再使用MySQL 8.0之前的时候,就知道需要在使用窗口函数处理逻辑的痛 ...
- 使用jQuery+huandlebars判断类型的helper
兼容ie8(很实用,复制过来,仅供技术参考,更详细内容请看源地址:http://www.cnblogs.com/iyangyuan/archive/2013/12/12/3471227.html) & ...
- 关于 web 页面 占满全屏
页面一般可以分成三部分,头部,底部,中间内容部分. 一般不用考虑中间高度部分,因为可以靠内容撑开,然后让底部到达底部.但是当中间内容太少时,底部就会顶不到底部. 方法1.中间部分给一个最小高度(min ...
- 1. Go安装
和任何语言一样,开始使用之前都要先安装好他的开发/编译环境. Go是由谷歌开发的一个开源的编译型的静态语言,编译型语言最大的优点就是效率高运行速度快. Go语言支持Linux,Windows,Mac等 ...
- Android 发送邮件以及定时发送邮件的实现
本文以腾讯企业邮箱为例,展示如何发送邮件 及相关问题 选择腾讯企业邮箱是因为腾讯企业邮箱一般都是开启了smtp服务 项目地址:https://gitee.com/bimingcong/MySendE ...
- vue js校验金钱、数字
// 校验保留两位小数金额 export function isMoney(money) { var reg = /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1} ...
- Quartz.NET快速入门指南
最近,在工作中遇到了 Quartz.net 这个组件,为了更好的理解项目代码的来龙去脉,于是决定好好的研究一下这个东西.确实是好东西,既然是好东西,我就拿出来分享一下.万丈高楼平地起,我们也从入门开始 ...