HashMap中的put方法

public V put(K key, V value) {
//当key为null,调用putForNullKey方法,保存null与table第一个位置中,这是HashMap允许为null的原因
if (key == null)
return putForNullKey(value);
//计算key的hash值
int hash = hash(key.hashCode()); ------(1)
//计算key hash 值在 table 数组中的位置
int i = indexFor(hash, table.length); ------(2)
//从i出开始迭代 e,找到 key 保存的位置
for (Entry<K, V> e = table[i]; e != null; e = e.next) {
Object k;
//判断该条链上是否有hash值相同的(key相同)
//若存在相同,则直接覆盖value,返回旧value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value; //旧值 = 新值
e.value = value;
e.recordAccess(this);
return oldValue; //返回旧值
}
}
//修改次数增加1
modCount++;
//将key、value添加至i位置处
addEntry(hash, key, value, i);
return null;
}

key为null:

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++;
addEntry(0, null, value, 0);
return null;
}

获取Entry的第一个元素table[0],并基于第一个元素的next属性开始遍历,直到找到key为null的Entry,将其value设置为新的value值。
如果没有找到key为null的元素,则调用如上述代码的addEntry(0, null, value, 0);增加一个新的entry

void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}

hash方法:

static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}

讲解说明:

将hash函数作为已给定的hashCode的一个补充,可以提高hash函数的质量。hash质量的好坏是非常重要的,因为HashMap用2的次幂作为表的hash长度,这就容易产生冲突,因为hashCodes在低位不是不同的(hashCodes that do not differ in lower bits)。注意:Null 的key的hash值总是0,即他在table的索引为0。

       让我们通过例子来帮助我们理解一下上面的话。加入说key object的hashCode()只返回三个值:31、63和95.31、63和95都是int型,所以是32位的。

        31=00000000000000000000000000011111         63=00000000000000000000000000111111         95=00000000000000000000000001011111

      现在加入HashMap的table长为默认值16(2^4,HashMap的长度总是2的次幂)

      假如我们不用hash函数,indexFor将返回如下值:

      31=00000000000000000000000000011111 => 1111=15       63=00000000000000000000000000111111  => 1111=15       95=00000000000000000000000001011111 => 1111=15

为什么会这样?因为当我们调用indexFor函数的时候,它将执行31&15,,63&15和95&15的与操作,比如说95&15得出一下结果:

        00000000000000000000000001011111 &00000000000000000000000000001111

        也就是说(2^n-1)总会是一个1的序列,因此不管怎样,最后会执行0&1的于然后得出0.

        上面的例子,也就解释了凡是在末尾全是1111的都返回相同的index,因此,尽管我们有不同的hashcode,Entry对象却讲都被存在table中index为15的位置。

        倘若我们使用了hash函数,对于上面每一个hashcode,经过hash作用作用后如下:

        31=00000000000000000000000000011111 => 00000000000000000000000000011110         63=00000000000000000000000000111111  => 00000000000000000000000000111100         95=00000000000000000000000001011111 => 00000000000000000000000001011010

        现在在通过新的hash之后再使用indexFor将会返回:

        00000000000000000000000000011110 =>1110=14         00000000000000000000000000111100 =>1100=12         00000000000000000000000001011010 =>1010=10

        在使用了hash函数之后,上面的hashcodes就返回了不同的index,因此,hash函数对hashmap里的元素进行了再分配,也就减少了冲突同时提高了性能。

         hash操作最主要的目的就是在最显著位的hashcode的差异可见,以致于hashmap的元素能够均匀的分布在整个桶里。

         有两点需要注意:

          如果两个key有相同的hashcode,那他们将被分配到table数组的相同index上

           如果两个key不具有相同的hashcode,那么他们或许可能,或许也不可能被分配到table数组相同的index上。

indexFor方法:

static int indexFor(int h, int length) {
return h & (length-1);
}

讲解说明:

这个方法有点意思,主要作用是定位hashmap里的bucket。
-------------------------------------------------------
大家知道hashmap底层就是一个数组,然后数组里每个元素装了个链表。
这个数组元素称为bucket桶
-------------------------------------------------------
先复习一下逻辑与。 0 & 0 = 0;0 & 1 = 0;1 & 0 = 0;1 & 1 = 1;
第二个参数length始终为2的n次方,所以,
换成二进制数就是 100,1000,10000,...
(length -1)就是 11, 111,1111,... 这样的话,
第一个参数h比第二个参数小的情况下,那结果就是h。
第一个参数h比第二个参数大的情况下,如下:
例:
h=18 -> 10010
length-1=15 -> 01111 所以在第一个参数比第二个参数大的情况下等于第一参数%第二参数,取余数

addEntry

void addEntry(int hash, K key, V value, int bucketIndex) {
//获取bucketIndex处的Entry
Entry<K, V> e = table[bucketIndex];
//将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
table[bucketIndex] = new Entry<K, V>(hash, key, value, e);
//若HashMap中元素的个数超过极限了,则容量扩大两倍
if (size++ >= threshold)
resize(2 * table.length);
}

put的流程:

当我们想一个HashMap中添加一对key-value时,系统首先会计算key的hash值,然后根据hash值确认在table中存储的位置。

若该位置没有元素,则直接插入。否则迭代该处元素链表并依此比较其key的hash值。

如果两个hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),则用新的Entry的value覆盖原来节点的value。

如果两个hash值相等但key值不等 ,则将该节点插入该链表的链头(最先保存的元素放在链尾),新元素设置为Entry[0],其next指针指向原有对象,即原有对象为Entry[1]

比如, 第一个键值对A进来,通过计算其key的hash得到的i=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其i也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,i也等于0,那么C.next = B,Entry[0] = C;这样我们发现i=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起,也就是说数组中存储的是最后插入的元素。

HashMap的get操作:

public V get(Object key) {
if (key == null)
return getForNullKey();
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)))//-------------------1----------------
return e.value;
}
return null;
}

当key为null时,调用getForNullKey()

private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}

当key不为null时,先根据hash函数得到hash值,在更具indexFor()得到i的值,循环遍历链表,如果有:key值等于已存在的key值,则返回其value。

=======================================================================================================

JDK8的讲解图:

HashMap的put方法

=====================================================================

JDK1.8以后对hashmap进行了大量的优化

JDK8 因为对自己改造过的哈希大量冲突时的红黑树有信心,所以简单一些,只是把高16位异或下来。

  static int hash(int h) {
return h ^ (h >>> 16);
}

所以即使Key比较均匀无哈希冲突,JDK8也比JDK7略快的原因大概于此。

存在哈希冲突的情况,比如两个哈希值取模后落在同一个桶上,或者两条不同的key有相同的哈希值。
JDK7的做法是建一条链表,后插入的元素在上面,一个个地执行上面的判断。
而JDK8则在链表长度达到8,而且桶数量达到64时,建一棵红黑树,解决严重冲突时的性能问题。

http://www.cnblogs.com/chenssy/p/3521565.html

http://blog.csdn.net/zhangerqing/article/details/8193118

http://tech.meituan.com/java-hashmap.html

http://calvin1978.blogcn.com/articles/hashmap.html

http://www.importnew.com/7099.html

HashMap put,get操作的更多相关文章

  1. 多个线程对hashmap进行put操作的异常

    多个线程对hashmap进行put操作的异常 Exception in thread "Thread-0" java.lang.ClassCastException: java.u ...

  2. 面试连环炮系列(七):HashMap的put操作做了什么

    HashMap的put操作做了什么? HashMap的是由数组和链表构成的,JDK7之后加入了红黑树处理哈希冲突.put操作的步骤是这样的: 根据key值计算出哈希值作为数组下标.如果数组的这个位置是 ...

  3. jdk1.8 HashMap红黑树操作详解-putTreeVal()

    以前也看过hashMap源码不过是看的jdk1.7的,由于时间问题看的也不是太深入,只是大概的了解了一下他的基本原理:这几天通过假期的时间就对jdk1.8的hashMap深入了解了下,相信大家都是对红 ...

  4. 并发HashMap的put操作引起死循环

    今天研读Java并发容器和框架时,看到为什么要使用ConcurrentHashMap时,其中有一个原因是:线程不安全的HashMap, HashMap在并发执行put操作时会引起死循环,是因为多线程会 ...

  5. Java HashMap的put操作(Java1.6)

    https://www.cnblogs.com/skywang12345/p/3310835.html // 存储数据的Entry数组,长度是2的幂. // HashMap是采用拉链法实现的,每一个E ...

  6. Java HashMap的put操作(Java1.8)

    https://www.cnblogs.com/JzedyBlogs/p/10208295.html 写得非常好: 这个是Java1.8 ------------------------------- ...

  7. 你是否听说过 HashMap 在多线程环境下操作可能会导致程序死循环?

    作者:炸鸡可乐 原文出处:www.pzblog.cn 一.问题描述 经常有些面试官会问,是否了解过 HashMap 在多线程环境下使用时可能会发生死循环,导致服务器 cpu 100% 的线上故障? 关 ...

  8. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  9. java--HashMap多线程并发问题分析

    并发问题的症状 多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题.后来,我们的程序性能有问题,所以需要变成多 ...

随机推荐

  1. 【iHMI43 4.3寸液晶模块】demo例程(版本1.00)发布

    ============================== 技术论坛:http://www.eeschool.org 博客地址:http://xiaomagee.cnblogs.com 官方网店:h ...

  2. Android 通用流行框架大全

    1. 缓存 DiskLruCache    Java实现基于LRU的磁盘缓存 2.图片加载 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库 Picas ...

  3. 2014 top100

    下面是 TOP 100 的软件列表: 1. JFinal JFinal 是基于 Java 语言的极速 WEB + ORM 框架,其核心设计目标是开发迅速.代码量少.学习简单.功能强大.轻量级.易扩展. ...

  4. svchost.exe是什么?为什么一直在运行

    原文:http://www.howtogeek.com/howto/windows-vista/what-is-svchostexe-and-why-is-it-running/ 自己简单翻译了下,图 ...

  5. 深入说明HDR

    http://wenku.baidu.com/link?url=xBdq0VRVi2t0x9uis3XfU_0mKf2eK0e6y_1hiSo7IWSWyUE8yAwaTJ60ZlxTzQf91VPf ...

  6. lua学习笔记

    工作需要,上周对lua赶进度似地学习了一遍,主要参考<lua中文教程>一书,中间参考一些<lua游戏开发实践>,首先说说这两本书,后者不适合初学,里面是对一个游戏脚本系统进行粗 ...

  7. jquery循环绑定事件

    <html> <head> <title></title> <script type="text/javascript" sr ...

  8. Static Random-Access Memory Dynamic Random-Access Memory

    COMPUTER ORGANIZATION AND ARCHITECTURE DESIGNING FOR PERFORMANCE NINTH EDITION RAM technology is div ...

  9. 纯css下拉菜单的制作

    通过观察下拉菜单的过程,发现有两种状态,一种是鼠标没有hover时,一种是鼠标hover时. 主菜单hover时,显示子菜单:主菜单没有hover时,不显示子菜单 <!DOCTYPE html& ...

  10. java CyclicBarrier 2

    //Listing 6-2. Using a Cyclic Barrier to Decompose a Task into Subtasks import java.util.concurrent. ...