JDK1.8 HashMap中put源码分析

一、存储结构

     在JDK1.8之前,HashMap采用桶+链表实现,本质就是采用数组+单向链表组合型的数据结构。它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap通过key的hashCode来计算hash值,不同的hash值就存在数组中不同的位置,当多个元素的hash值相同时(所谓hash冲突),就采用链表将它们串联起来(链表解决冲突),放置在该hash值所对应的数组位置上。结构图如下:

    图中,紫色部分代表哈希表,也称为哈希数组,数组中每个元素都是一个单链表的头结点,链表是用来解决冲突的,如果不同的key映射得到了数组的同一位置处,就将其放入单链表。

 

    在JDK1.8中,HashMap的存储结构已经发生变化,它采用数组+链表+红黑树这种组合型数据结构。当hash值发生冲突时,会采用链表或者红黑树解决冲突。当同一hash值的结点数小于8时,则采用链表,否则,采用红黑树。这个重大改变,主要是提高查询速度。它的结构图如下:

 

二、put方法

之所以先介绍存储结构,是为了更好的理解put方法。

public put(K key, V value) {    return putVal(hash(key), key, value, false, true);}

put方法调用了putVal方法,那我们再来看看它。

/*Parameters:    hash hash for key    key the key    value the value to put    onlyIfAbsent if true, don‘t change existing value    evict if false, the table is in creation mode.Returns:    previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {    Node<K,V>[] tab; Node<K,V> p; int n, i;    // 如果table为空,或者还没有元素时,则扩容    if ((tab = table) == null || (n = tab.length) == 0)        n = (tab = resize()).length;    // 如果首结点值为空,则创建一个新的首结点。    // 注意:(n - 1) & hash才是真正的hash值,也就是存储在table位置的index。在1.6中是封装成indexFor函数。    if ((p = tab[i = (n - 1) & hash]) == null)        tab[i] = newNode(hash, key, value, null);    else {    // 到这儿了,就说明碰撞了,那么就要开始处理碰撞。            Node<K,V> e; K k;            // 如果在首结点与我们待插入的元素有相同的hash和key值,则先记录。            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))                e = p;            else if (p instanceof TreeNode) // 如果首结点的类型是红黑树类型,则按照红黑树方法添加该元素                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);            else {  // 到这一步,说明首结点类型为链表类型。                    for (int binCount = 0; ; ++binCount) {                        // 如果遍历到末尾时,先在尾部追加该元素结点。                        if ((e = p.next) == null) {                            p.next = newNode(hash, key, value, null);                            // 当遍历的结点数目大于8时,则采取树化结构。                            if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                                treeifyBin(tab, hash);                                break;                        }                        // 如果找到与我们待插入的元素具有相同的hash和key值的结点,则停止遍历。此时e已经记录了该结点                        if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))                            break;                        p = e;                    }                }            // 表明,记录到具有相同元素的结点            if (e != null) { // existing mapping for key                V oldValue = e.value;                if (!onlyIfAbsent || oldValue == null)                    e.value = value;                afterNodeAccess(e);  // 这个是空函数,可以由用户根据需要覆盖                return oldValue;            }        }    ++modCount;    // 当结点数+1大于threshold时,则进行扩容    if (++size > threshold)        resize();    afterNodeInsertion(evict); // 这个是空函数,可以由用户根据需要覆盖    return null;}

 

参考:

1、JDK1.8HashMap原理和源码分析(java面试收藏)

2、Java类集框架之HashMap(JDK1.8)源码剖析

时间: 2024-10-12 09:27:24

JDK1.8 HashMap中put源码分析的相关文章

面试(1)-HashMap原理与源码分析(JDK1.8)

1.HashMap介绍 HashMap为Map接口的一个实现类,实现了Map所有的操作.HashMap除了允许key.value为null值和非线程安全外,其他实现几乎和HashTable一致.HashMap使用散列存储的方式保存kay-value键值对,因此其不支持数据保存的顺序.如果想要使用有序容器可以使用LinkedHashMap.在性能上当HashMap中保存的key的哈希算法能够均匀的分布在每个bucket中的时候,HashMap在基本的get和set操作的的时间复杂度都是O(n).在

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Java中ArrayList源码分析

一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保证容量能容纳所有数据. 1.1.ArrayList 的继承与实现接口 ArrayList继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口. public class  ArrayList<E> ex

Spark中决策树源码分析

1.Example 使用Spark MLlib中决策树分类器API,训练出一个决策树模型,使用Python开发. """ Decision Tree Classification Example. """from __future__ import print_functionfrom pyspark import SparkContextfrom pyspark.mllib.tree import DecisionTree, DecisionT

Mybatis中selectKey源码分析

刚回答了一个问题这样一个问题,mybatis不能正常返回主键增加值  下面通过源码分析一下selectKey都具体实现:关于Mybatis 基于注解Mapper源码分析 可以看一下具体解析过程. @Insert("insert into table2 (name) values(#{name})") @SelectKey(statement="call identity()", keyProperty="nameId", before=false

深入理解 Node.js 中 EventEmitter源码分析(3.0.0版本)

events模块对外提供了一个 EventEmitter 对象,即:events.EventEmitter. EventEmitter 是NodeJS的核心模块events中的类,用于对NodeJS中的事件进行统一管理,使用events可以对特定的API事件进行添加,触发和移除等.我们可以通过 require('events')来访问该模块. 比如如下代码: // 引入 events 模块 const events = require('events'); console.log(events)

浏览器中的源码分析功能

前言:我们一直在用浏览器,你是否真正玩过他?NO! 在IE浏览器中,打开一个网页,右击菜单栏中有一个查看源代码功能,不过这个源代码太长了,不易分析. 之后的浏览器增加了一项功能,叫做审查元素功能. 在谷歌浏览器中,打开一个网页,右击菜单栏中有一项是检查:在搜狗浏览器中,打开一个网页,右击菜单栏有一项是审查元素:其他浏览器笔者未尝试过. 一.审查元素简介 以最常用的百度搜索界面为例,打开审查元素,如下图所示: 右侧出现审查元素界面,也就是网页的Html代码元素 其中包括以下模块 Elements(

HashMap底层实现(源码分析)

一.数据结构 Map将实际数据存储在Entry类的数组中. 代码片段: Java代码     transient Entry[] table;//HashMap的成员变量,存放数据 static class Entry<K,V> implements Map.Entry<K,V> {//内部类Entry final K key; V value; Entry<K,V> next;//指向下一个数据 final int hash; /** * Creates new en

django中CBV源码分析

前言:Django的视图处理方式有两种: FBV(function base views) 是在视图里基于函数形式处理请求. CBV(class base views)是在视图里基于类的形式处理请求. Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就错失了(继承.封装.多态).所以Django在后来加入了Class-Based-View.可以让我们用类写View.这样做的优点主要下面两种: 提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承) 可以