参考博客: https://blog.csdn.net/eson_15/article/details/51158865

hashMap中的几个关键属性

//默认初始容量是16,必须是2的幂
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //最大容量(必须是2的幂且小于2的30次方,传入容量过大会被这个值替换)
static final int MAXIMUM_CAPACITY = 1 << 30; //默认加载因子,所谓加载因子是指哈希表在其容量自动增加之前可以达到多满的一种尺度
static final float DEFAULT_LOAD_FACTOR = 0.75f; //存储Entry的默认空数组
static final Entry<?,?>[] EMPTY_TABLE = {}; //存储Entry的数组,长度为2的幂。HashMap采用拉链法实现的,每个Entry的本质是个单向链表
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; //HashMap的大小,即HashMap存储的键值对数量
transient int size; //HashMap的阈值,用于判断是否需要调整HashMap的容量
int threshold; //加载因子实际大小
final float loadFactor; //HashMap被修改的次数,用于fail-fast机制
transient int modCount;

  关于加载因子:

    我们都知道它得底层就是一个Entry数组 命名为table, put时,先得到key的hash值,key为null时直接插入到table[0]的位置,

  key不为空时,索引是怎么得到的? 哈哈,是通过table的长度和key的hash值来计算的,怎么计算的呢? 下面的 indexFor 方法   h & (length-1)

    这个方法真的得解释一下(不知道解释的对不对): 索引就是通过这个算法来计算的,如果此时table的长度较短,那么索引重复的可能性就较大,反之;

  所以加载因子的由来了,就是恒定一个平衡值,就跟hashCode方法中有一个平衡值31是一个道理.

  大佬的解释如下:

     我们主要来看看loadFactor属性,loadFactor表示Hash表中元素的填满程度。

若加载因子设置过大,则填满的元素越多,无疑空间利用率变高了,但是冲突的机会增加了,冲突的越多,链表就会变得越长,那么查找效率就会变得更低;

若加载因子设置过小,则填满的元素越少,那么空间利用率变低了,表中数据将变得更加稀疏,但是冲突的机会减小了,这样链表就不会太长,查找效率变得更高。

这看起来有点绕口,我举个简单的例子,如果数组容量为100,加载因子设置为80,即装满了80个才开始扩容,但是在装的过程中,可能有很多key对应相同的hash值,这样就会放到同一个链表中(因为没到80个不能扩容),这样就会导致很多链表都变得很长,也就是说,不同的key对应相同的hash值比数组填满到80个更加容易出现。

但是如果设置加载因子为10,那么数组填满10个就开始扩容了,10个相对来说是很容易填满的,而且在10个内出现相同的hash值概率比上面的情况要小的多,一旦扩容之后,那么计算hash值又会跟原来不一样,就不会再冲突了,这样保证了链表不会很长,甚至就一个表头都有可能,但是空间利用率很低,因为始终有很多空间没利用就开始扩容。

因此,就需要在“减小冲突”和“空间利用率”之间寻找一种平衡,这种平衡就是数据结构中有名的“时-空”矛盾的平衡。如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没什么要求的话可以将加载因子设置大一点。一般我们都使用它的默认值,即0.75。

  

public V put(K key, V value) {
if (table == EMPTY_TABLE) { //如果哈希表没有初始化(table为空)
inflateTable(threshold); //用构造时的阈值(其实就是初始容量)扩展table
}
//如果key==null,就将value加到table[0]的位置
//该位置永远只有一个value,新传进来的value会覆盖旧的value
if (key == null)
return putForNullKey(value); int hash = hash(key); //根据键值计算hash值 int i = indexFor(hash, table.length); //搜索指定hash在table中的索引 //循环遍历Entry数组,若该key对应的键值对已经存在,则用新的value取代旧的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; //并返回旧的value
}
} modCount++;
//如果在table[i]中没找到对应的key,那么就直接在该位置的链表中添加此Entry
addEntry(hash, key, value, i);
return null;
} //这个方法有点意思,也是为什么容量要设置为2的幂的原因   static int indexFor(int h, int length) {  
  return h & (length-1);   //得到索引的核心算法
}  

  table的初始化是在第一次put的时候进行的

    

//扩展table
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize); //获取和toSize最接近的2的幂作为容量
//重新计算阈值 threshold = 容量 * 加载因子
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity]; //用该容量初始化table
initHashSeedAsNeeded(capacity);
}
//将初始容量转变成2的幂 例如 number = 3,返回值为4
                number = 5,返回值为8 (也是为了给后面计算索引,减少索引重复率)
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY //如果容量超过了最大值,设置为最大值
//否则设置为最接近给定值的2的次幂数
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}

  key为null的时候的存取:

//传进key==null的Entry
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//如果table[0]处没有key为null
addEntry(0, null, value, 0);//如果键为null的话,则hash值为0
return null;
}
今天才知道put原来还有返回值的,第一次put(null,value1),返回值为null,第二次put(null,value2),返回值为value1,recordAccess方法是把value2更新到key = null中
put别的key也是一样,不过table[0]这个位置可不仅仅存 key = null哦,如果计算出来的索引值为0,那么就得说说addEntry方法了,如果现在索引为0处有了一个entry,现在put一个进行,新的entry排在第一个
老的entry挂在后面.

  

//向HashMap中添加Entry
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length); //扩容2倍
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
} createEntry(hash, key, value, bucketIndex);
}
//创建一个Entry
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];//先把table中该位置原来的Entry保存
//在table中该位置新建一个Entry,将原来的Entry挂到该Entry的next
table[bucketIndex] = new Entry<>(hash, key, value, e);
//所以table中的每个位置永远只保存一个最新加进来的Entry,其他Entry是一个挂一个,这样挂上去的
size++;
}

  

HashMap的简单源码分析(看了大佬的源码,基于1.7) put方法的更多相关文章

  1. Tomcat源码分析一:编译Tomcat源码

    Tomcat源码分析一:编译Tomcat源码 1 内容介绍 在之前的<Servlet与Tomcat运行示例>一文中,给大家带来如何在Tomcat中部署Servlet应用的相关步骤,本文将就 ...

  2. Android源码分析(十一)-----Android源码中如何引用aar文件

    一:aar文件如何引用 系统Settings中引用bidehelper-1.1.12.aar 文件为例 源码地址:packages/apps/Settings/Android.mk LOCAL_PAT ...

  3. AQS源码分析看这一篇就够了

      好了,我们来开始今天的内容,首先我们来看下AQS是什么,全称是 AbstractQueuedSynchronizer 翻译过来就是[抽象队列同步]对吧.通过名字我们也能看出这是个抽象类 而且里面定 ...

  4. 第九篇:Spark SQL 源码分析之 In-Memory Columnar Storage源码分析之 cache table

    /** Spark SQL源码分析系列文章*/ Spark SQL 可以将数据缓存到内存中,我们可以见到的通过调用cache table tableName即可将一张表缓存到内存中,来极大的提高查询效 ...

  5. JDK1.8源码分析03之idea搭建源码阅读环境

    序言:上一节说了阅读源码的顺序,有了一个大体的方向,咱们就知道该如何下手.接下来,就要搭建一个方便阅读源码及debug的环境.有助于跟踪源码的调用情况. 目前新开发的项目, 大多数都是基于JDK1.8 ...

  6. 基于个人理解的springAOP部分源码分析,内含较多源码,慎入

    本文源码较多,讲述一些个人对spring中AOP编程的一个源码分析理解,只代表个人理解,希望能和大家进行交流,有什么错误也渴求指点!!!接下来进入正题 AOP的实现,我认为简单的说就是利用代理模式,对 ...

  7. 4 weekend110的textinputformat对切片规划的源码分析 + 倒排索引的mr实现 + 多个job在同一个main方法中提交

    好的,现在,来weekend110的textinputformat对切片规划的源码分析, Inputformat默认是textinputformat,一通百通. 这就是今天,weekend110的te ...

  8. Java容器类源码分析之Iterator与ListIterator迭代器(基于JDK8)

    一.基本概念 迭代器是一个对象,也是一种设计模式,Java有两个用来实实现迭代器的接口,分别是Iterator接口和继承自Iterator的ListIterator接口.实现迭代器接口的类的对象有遍历 ...

  9. 第十篇:Spark SQL 源码分析之 In-Memory Columnar Storage源码分析之 query

    /** Spark SQL源码分析系列文章*/ 前面讲到了Spark SQL In-Memory Columnar Storage的存储结构是基于列存储的. 那么基于以上存储结构,我们查询cache在 ...

随机推荐

  1. 开启GitHub模式,now!

    (原文地址为:http://www.karottc.com/blog/2014/06/15/current-doing/) 最近看到了一篇文章,该文章的作者将自己连续177天在github上commi ...

  2. 边缘检测sobel算子

    sobel算子 - sophia_hxw - 博客园http://www.cnblogs.com/sophia-hxw/p/6088035.html #1,个人理解 网上查了很多资料,都说sobel算 ...

  3. linux连接mysql命令

    连接MYSQL: 格式: mysql -h主机地址 -u用户名 -p用户密码 1.例1:连接到本机上的MYSQL 找到mysql的安装目录,一般可以直接键入命令mysql -uroot -p,回车后提 ...

  4. 用Java自定义一个定时器

    1.先定义一个监听类: import java.util.Date; import java.util.Timer; import javax.servlet.ServletContextEvent; ...

  5. Android 中替代 sharedpreferences 工具类的实现

    Android 中替代 sharedpreferences 工具类的实现 背景 想必大家一定用过 sharedpreferences 吧!就我个人而言,特别讨厌每次 put 完数据还要 commit. ...

  6. 认识tornado(一)

    tornado 源码包中 demos 目录下包含一些示例程序,就从最简单的 helloworld.py 来看一个 tornado 应用程序的代码结构. 完整的实例程序如下: 01 #!/usr/bin ...

  7. java高精度hdu4043

    可以推出公式 f[n]=f[n-1]+f[n-1]*2*(n-1) f[1]=1; 数据量很大,最后又要进行gcd操作,java里竟然自带了一个gcd的函数,为了避免求大数取余和大数除法操作,还是用j ...

  8. 170223、Tomcat部署时war和war exploded区别以及平时踩得坑

    war和war exploded的区别 在使用IDEA开发项目的时候,部署Tomcat的时候通常会出现下边的情况: 是选择war还是war exploded 这里首先看一下他们两个的区别: war模式 ...

  9. 170122、Netty 长连接服务

    推送服务 还记得一年半前,做的一个项目需要用到 Android 推送服务.和 iOS 不同,Android 生态中没有统一的推送服务.Google 虽然有 Google Cloud Messaging ...

  10. matplotlib图像中文乱码(python3.6)

    方法一:(在代码中添加如下代码) import matplotlib #指定默认字体 matplotlib.rcParams['font.sans-serif'] = ['SimHei'] matpl ...