关于hashmap的理解
首先分析第一个比较重要的方法 put 方法,源码如下
public V put(K key, V value) {
if (key == null)
return putForNullKey(value); //这里判断key是否为空,若为空则调用putForNullKey处理null值
int hash = hash(key); //根据key的hashCode计算hash值
int i = indexFor(hash, table.length);//搜索该key的hash值在table中的索引,其中table是当HashMap用于存放entry的一个数组 //这里循环遍历table中对应该索引的entry,若发现存在key与put进来的key相同则覆盖其value值
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++; //将key value 添加到 索引i处
addEntry(hash, key, value, i);
return null;
}
分析上面的源码,我们可以得到下面的结论:
当我们试图将一个key-value 调用put方法放入HashMap的时候,首先会调用key的hashCode方法算出该Entry存放的位置,若两个key的hashCode相同则在table中的存储位置相同,则先调用equals方法判断两个key是否相同,相同则覆盖,不相同则产生一个Entry链表(因为table数组中一个索引位置只能放入一个Entry,所以当有多个key的hashCode相同时,这些key就会以链表的形式存在,并且最后put进来的key在链表的最前面)
然后则是HashMap的构造方法,这里以 HashMap(int initialCapacity, 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); // Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1; this.loadFactor = loadFactor;
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
从上面的源码可以看出 ,初始容量不能为负数,若初始容量大于最大容量,则让它等于最大容量,负载因子必须大于0,并且传入的initialCapacity不是HashMap的容量大小,
实际容量大小的计算规则是大于传入的initialCapacity的最小的2的n次方,比如传入的initialCapacity是5 那么实际容量则是8 因为2的3次方大于5。
下面再分析一下HashMap的存储性能,下面的 get方法的源码
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
int hash = (key == null) ? 0 : hash(key);
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 != null && key.equals(k))))
return e;
}
return null;
}
再次强调一下table的概念,table就是当我们初始化一个HashMap时,会自动创建一个长度为capacity的Entry数组,我们把这个数组存放元素的位置叫“桶”,并且每个桶只存储一个Entry元素(也就是我们的键值对),并且当我们put一个键值对时,先计算key的hashCode来判断这个键值对会放入哪一个桶,所以若多个key的hashCode相同时,他们都要被放入一个桶里面,但是一个桶里面只能放入一个Entry(键值对),要解决这个问题先看下面的代码
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
这时Entry的构造方法,我们可以看出Entry对象包含一个Entry的引用,用来指向下一个Entry,这样就解决了hashCode相同,存放冲突的问题,所以当有多个key的hashCode相同时,就会形成一个Entry链,我们从get方法可以看出当系统通过key的hashCode找到了对应的桶的时候,会遍历这个Entry链,来找到我们要取的value的key,这个时候,若刚好这个Entry在链表的末端(也就是我们最开始put进去的Entry)那么当这个链表太长了,势必会影响我们的查询性能,这个时候就引出了loadFactor(负载因子的说法),HashMap的默认附在因子是0.75,我对负载因子的理解就是,表示HashMap在什么时候扩容,也就是说若我们初始的HashMap容量是16 负载因子是0.75,那么当有12个“桶”有了Entry时,HashMap
就会扩容,并且扩大的容量是原来容量的2倍,为什么是12呢?因为0.75x16=12。并且负载因子是可以更改的,修改它的前提是如果内存比较紧张就可以适当的增加负载因子,
若空间,内存比较充足,更关注查询效率则减少负载因子。为什么会这样呢?因为若负载因子减少了,比如说减少到了0.5,默认HashMap容量大小还是16,那么当我有8个"桶"中存放了Entry数组时我就会扩容了,该桶里的Entry链相比于之前就不会那么长,从而提升了查询性能。
今天先到这里,下次再写。
关于hashmap的理解的更多相关文章
- 关于java集合类HashMap的理解
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- 对HashMap的理解(三):ConcurrentHashMap
HashMap不是线程安全的.在并发插入元素的时候,有可能出现环链表,让下一次读操作出现死循环.避免HashMap的线程安全问题有很多方法,比如改用HashTable或Collections.sync ...
- 【大厂面试08期】谈一谈你对HashMap的理解?
摘要 HashMap的原理也是大厂面试中经常会涉及的问题,同时也是工作中常用到的Java容器,本文主要通过对以下问题进行分析讲解,来帮助大家理解HashMap的原理. 1.HashMap添加一个键值对 ...
- hashMap 深入理解
1.java 的hashMap 是通过 链地址 法来解决 hash冲突的 2.初始时是一个empty table, 第一次添加数据时检查到时空数组就会 生成指定容量的数组,也就是 在第一次使用时才初始 ...
- HashMap简单理解
1. hashmap基于哈希表的map接口实现,此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable ...
- (惊艳)hashmap的理解(映射)
第一: hashmap在内存中是长这样的,数组+链表的形式 // HashMap采用链表法解决冲突,每一个Entry本质上是一个单向链表 transient Entry[] table; 第二: p ...
- 对HashMap的理解(一):HashMap的实现
一.HashMap介绍 1. 定义HashMap实现了Map接口,继承AbstractMap类.其中Map接口定义了键映射到值的规则,而AbstractMap类提供 Map 接口的骨干实现,以最大限度 ...
- 对HashMap的理解(二):高并发下的HashMap
在分析hashmap高并发场景之前,我们要先搞清楚ReHash这个概念.ReHash是HashMap在扩容时的一个步骤.HashMap的容量是有限的.当经过多次元素插入,使得HashMap达到一定饱和 ...
- 对java中hashmap深入理解
1.HashMap的结构是怎样的? 二维结构,第一维是数组,第二维是链表 2.Get方法的流程是怎样的? 先调用Key的hashcode方法拿到对象的hash值,然后用hash值对第一维数组的长度进行 ...
随机推荐
- thymeleaf中的日期格式化
本篇介绍些thymeleaf中的日期格式化的方法: 1.用#dates.format来解决: <span th:text="${#dates.format(user.date, 'yy ...
- C#线程(二)
.cnblogs_code { background-color: #f5f5f5; font-family: Courier New !important; font-size: 12px !imp ...
- [算法题] Remove Duplicates from Sorted Array ii
题目内容 本题来源LeetCode Follow up for "Remove Duplicates": What if duplicates are allowed at mos ...
- Azkaban3.x集群部署(multiple executor mode)
介绍 Azkaban是由Linkedin公司推出的一个批量工作流任务调度器,用于在一个工作流内以一个特定的顺序运行一组工作和流程.Azkaban使用job配置文件建立任务之间的依赖关系,并提供一个易于 ...
- 在Python3.5中使用 The del.icio.us API
问题:参考<集体智慧编程>一书的第二章中访问del.icio.us网站的数据需要使用到事先编好的Python API.但是书上提供的API并不适用与Python3.5的版本. 解决方法:在 ...
- 伪验证码(含随机验证码方法)js+css
HTML----------------------------------------------<!DOCTYPE html><html><head><m ...
- VS2017 Cordova 出现错误 @ionic/app-scripts 未安装
在安装vs2017 Cordova中遇到出现错误 @ionic/app-scripts 未安装 在系统添加环境变量SASS_BINARY_PATH,将我们下载的win32-ia64-47_bindin ...
- 自写JS分页工具【基于JQ】
好久没有来逛园子,也好久没有更新博客,就像沉睡已久的人忽然被叫醒,忽然就被园友的回复惊醒了.园友提出了关于我之前一篇文章的疑问--可那已经是半年以前的博客了,加上我一直觉得分享给大家的应该是我最新的思 ...
- .Net Framework下对Dapper二次封装迁移到.Net Core2.0遇到的问题以及对Dapper的封装介绍
今天成功把.Net Framework下使用Dapper进行封装的ORM成功迁移到.Net Core 2.0上,在迁移的过程中也遇到一些很有意思的问题,值得和大家分享一下.下面我会还原迁移的每一个过程 ...
- 一篇文章学会Spring4.0
spring概述 spring 是一个开源框架. Spring 为简化企业级应用开发而生. 使用 Spring 可以使简单的 JavaBean 实现以前只有 EJB 才能实现的功能. Spring 是 ...