首先分析第一个比较重要的方法 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的理解的更多相关文章

  1. 关于java集合类HashMap的理解

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  2. 对HashMap的理解(三):ConcurrentHashMap

    HashMap不是线程安全的.在并发插入元素的时候,有可能出现环链表,让下一次读操作出现死循环.避免HashMap的线程安全问题有很多方法,比如改用HashTable或Collections.sync ...

  3. 【大厂面试08期】谈一谈你对HashMap的理解?

    摘要 HashMap的原理也是大厂面试中经常会涉及的问题,同时也是工作中常用到的Java容器,本文主要通过对以下问题进行分析讲解,来帮助大家理解HashMap的原理. 1.HashMap添加一个键值对 ...

  4. hashMap 深入理解

    1.java 的hashMap 是通过 链地址 法来解决 hash冲突的 2.初始时是一个empty table, 第一次添加数据时检查到时空数组就会 生成指定容量的数组,也就是 在第一次使用时才初始 ...

  5. HashMap简单理解

    1. hashmap基于哈希表的map接口实现,此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable ...

  6. (惊艳)hashmap的理解(映射)

    第一: hashmap在内存中是长这样的,数组+链表的形式 // HashMap采用链表法解决冲突,每一个Entry本质上是一个单向链表 transient Entry[] table; 第二:  p ...

  7. 对HashMap的理解(一):HashMap的实现

    一.HashMap介绍 1. 定义HashMap实现了Map接口,继承AbstractMap类.其中Map接口定义了键映射到值的规则,而AbstractMap类提供 Map 接口的骨干实现,以最大限度 ...

  8. 对HashMap的理解(二):高并发下的HashMap

    在分析hashmap高并发场景之前,我们要先搞清楚ReHash这个概念.ReHash是HashMap在扩容时的一个步骤.HashMap的容量是有限的.当经过多次元素插入,使得HashMap达到一定饱和 ...

  9. 对java中hashmap深入理解

    1.HashMap的结构是怎样的? 二维结构,第一维是数组,第二维是链表 2.Get方法的流程是怎样的? 先调用Key的hashcode方法拿到对象的hash值,然后用hash值对第一维数组的长度进行 ...

随机推荐

  1. Maven导出Project依赖的jar包

    Maven导出Project依赖的jar包 从Maven仓库中导出jar包: mvn dependency:copy-dependencies 会导出到Project的targed/dependenc ...

  2. 成为Java顶尖程序员 ,看这11本书就够了(转)

    学习的最好途径就是看书",这是我自己学习并且小有了一定的积累之后的第一体会.个人认为看书有两点好处: 1.能出版出来的书一定是经过反复的思考.雕琢和审核的,因此从专业性的角度来说,一本好书的 ...

  3. javascript基础知识3#引用类

    引用类 引用类型的只是引用类型的一个实例,在ecmascript当中,引用类型是一种数据结构用于将数据和功能组织在一起,也常被称做类. object类型 构造函数[var o = new object ...

  4. 如何在Linux上使用VIM进行.Net Core开发

    对于在Linux上开发.Net Core的程序员来说, 似乎都缺少一个好的IDE. Windows上有Visual Studio, Mac上有Visual Studio for Mac, 难道Linu ...

  5. python--DenyHttp项目(1)--socket编程:服务器端进阶版socketServer

    在网上看到了SocketServer模块,于是Server端简化: #coding:utf-8 import socketserver class MyTCPHandler(socketserver. ...

  6. /etc/shadow,/etc/passwd,/etc/shadow,/etc/passwd文件的内容解释

    1.1 /etc/passwd文件内容格式           该目录存储的是操作系统用户信息,该文件为所有用户可见 用户名: 密码 : uid  : gid :用户描述:主目录:登陆shell 举个 ...

  7. ecshop中的些assign_dynamic(’')

    很多做电子商务站的朋友都问我,在ecshop中,里面有个 assign_dynamic('index');这个到底是什么作用来的,这个其实是ecshop中的模板技术,动态处理一些局部信息更新而不被缓存 ...

  8. Appium实现的技巧

    截图功能: //截屏并保存至本地             File screen = driver.getScreenshotAs(OutputType.FILE);             File ...

  9. Java设计模式学习笔记,二:工厂模式

    工厂模式,主要实现了创建者和调用者的分离. 分类:1.简单工厂模式:2.工厂方法模式:3.抽象工厂模式. 核心:实例化对象时,用工厂方法代替new操作. 一.简单工厂模式 也叫静态工厂模式,工厂类中实 ...

  10. angular基础

    =>是es6语法中的arrow function 举例: (x) => x + 6 相当于 function(x){ return x + 6; } >>> 是无符号移位 ...