在java jdk8中对HashMap的源码进行了优化,在jdk7中,HashMap处理“碰撞”的时候,都是采用链表来存储,当碰撞的结点很多时,查询时间是O(n)。

在jdk8中,HashMap处理“碰撞”增加了红黑树这种数据结构,当碰撞结点较少时,采用链表存储,当较大时,采用红黑树(特点是查询时间是O(logn))存储(有一个阀值控制,大于阀值,将链表存储转换成红黑树存储)

HashMap的样子就变成下图的样子:

下面对源码进行分析:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

由HashMap的类声明中,可知继承自AbstractMap抽象类,实现Map、Cloneable、Serializable接口。

public abstract class AbstractMap<K,V> implements Map<K,V>

AbstractMap抽象类实现Map接口。

观察常量:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

默认table初始容量16

static final int MAXIMUM_CAPACITY = 1 << 30;

table最大容量2的30次方

static final float DEFAULT_LOAD_FACTOR = 0.75f;

默认负载因子0.75

static final int TREEIFY_THRESHOLD = 8;

结点冲突数达到8时,就会对hash表进行调整,如果table容量小于64,那么会进行table扩容,如果不小于64,那么会将冲突数达8那个单链表调整为红黑树

static final int UNTREEIFY_THRESHOLD = 6;

如果原先是红黑树的,resize后冲突结点数少于6了,就把红黑色恢复成单链表

static final int MIN_TREEIFY_CAPACITY = 64;

如果table的容量少于64,那么即使冲突结点数达到TREEIFY_THRESHOLD后不会把该单链表调整成红黑数,而是将table扩容

Node静态类是HashMap中单链表的结点:

不难理解,就不详细讲了

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
} public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; } public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
} public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
} public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}

四个构造方法:

public HashMap(int initialCapacity, float loadFactor)

指定初始容量和负载因子

public HashMap(int initialCapacity)

指定初始容量,使用默认负载因子0.75

public HashMap()

使用默认初始容量16,默认负载因子0.75

public HashMap(Map<? extends K, ? extends V> m)

将一对Key-Value加入HashMap中

public V put(K key, V value) {
   //第四个参数是onlyIfAbsent,表示当找到同key的键值对,是否更新value的值,false是更   //新,第五个参数是evict,在afterNodeInsertion方法中使用,但这个函数是空函数
return putVal(hash(key), key, value, false, true);
}

实际调用putVal方法

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为空或者未分配空间,则resize,放入第一个K-V时总是先resize
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
    //(n-1)&hash计算K-V存的table位置,如果首节点为null,代表该位置还没放入过结点
if ((p = tab[i = (n - 1) & hash]) == null)
        //调用newNode新建一个结点放入该位置
tab[i] = newNode(hash, key, value, null);
else {//到这里,表示有冲突,开始处理冲突
Node<K,V> e; K k;
       //这时候p是指向table[i]的首个Node,判断table[i]是否与待插入节点有相同的
       //hash,key值,如果是则e=p
       if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
       //到这里说明table[i]的第一个Node与待插入Node的hash或key不同,那么要在
       //这个节点之后的链表节点或者树节点中查找
       else if (p instanceof TreeNode)//如果之后是树节点,使用树节点的插入方法
                         //插入成功后e等于null
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//说明之后是链表节点
for (int binCount = 0; ; ++binCount) {//逐个向后查找                  
            if ((e = p.next) == null) {//如果下一个节点是null,表示没有找到                          
                            //同hash或同key的节点
p.next = newNode(hash, key, value, null);//新建一个节点
                                  //放在链表的最后
             //如果冲突的节点数已经达到8个,看是否需要改变冲突节点的存储结构,             
             //treeifyBin首先判断当前hashMap的长度,如果不足64,只进行
//resize,扩容table,如果达到64,那么将冲突的存储结构为红黑树
              if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}//如果找到同hash或同key的节点,那么直接退出循环,
             //此时e等于冲突Node
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;//调整p节点,以继续查找
}
}
       //退出循环后,先判断e是否为null,为null表示已经插入成功,不为null表示有冲突
if (e != null) { // existing mapping for key
V oldValue = e.value;//保存旧冲突点的value
if (!onlyIfAbsent || oldValue == null)
         //判断是否需要修改value的值,默认是修改,如果旧冲突点value是null,一定          
          //是修改的
e.value = value;
afterNodeAccess(e);//空函数
return oldValue;
}
}
++modCount;//当插入了新节点才会运行到这里,HashMap结构调整次数+1
if (++size > threshold)//如果HashMap插入新节点后大于threshold,进行扩容
resize();
afterNodeInsertion(evict);//空函数
return null;
}
resize方法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;  //将原hash表赋值给oldTab临时变量
int oldCap = (oldTab == null) ? 0 : oldTab.length;//原hash表容量赋值给
                         //oldCap,如果第一次resize,oldCap为0
int oldThr = threshold;//原扩容阀值赋给oldThr临时变量
int newCap, newThr = 0;
if (oldCap > 0) {//如果不是第一次resize
if (oldCap >= MAXIMUM_CAPACITY) {//如果原table容量已经达到最大值
threshold = Integer.MAX_VALUE;
return oldTab;//无法继续扩容,只能返回原table
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)//扩容将原容量*2
newThr = oldThr << 1; // double threshold,新扩容阀值*2
}
    //原容量为0,但原阀值不为0,那么新容量为原阀值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
    //如果原容量和原阀值都为0,用默认值进行初始化
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {//如果新阀值为0
float ft = (float)newCap * loadFactor;//临时变量存储计算出的新阀值
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);//重新设置新阀值
}
threshold = newThr;//阀值被重新赋值
@SuppressWarnings({"rawtypes","unchecked"})
     //使用新容量创建一个新hash表
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;//旧hash表被新创建的表替换
if (oldTab != null) {//如果旧hash表不为null
for (int j = 0; j < oldCap; ++j) {//遍历旧hash表,目的是重新分配所有
                          //结点到新hash表中
Node<K,V> e;
if ((e = oldTab[j]) != null) {//如果oldTab[j]不为null,取出第一个                           //结点赋值给e
oldTab[j] = null;//oldTab[j]赋值为null
if (e.next == null)//如果只有一个结点,表示之前没有碰撞
newTab[e.hash & (newCap - 1)] = e;//把该结点存入新表
else if (e instanceof TreeNode)//如果e是树结点,表示要移动一棵                              //树
              //调整这棵树
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { //到这里表示e后面带着个单链表,需要遍历单链表,将每个结点重                //新计算在新表的位置,并进行搬运
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;//记录下一个结点
              //新表是旧表的两倍容量,实例上就把单链表拆分为两队,
              //e.hash&oldCap为偶数一队,e.hash&oldCap为奇数一对
                if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {//lo队不为null,放在新表原位置
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {//hi队不为null,放在新表j+oldCap位置
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;//返回调整好的新表
}

treeifBin方法是冲突结点达到8个,判断是要进行扩容还是链表转换成红黑色

final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
    //table长度还未达到64,仅对table resize扩容
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
    //否则要将单链表转换成红黑树
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {//该循环将单链表的结点替换成TreeNode树结点,并构建双向链表,为构建红黑          //树作准备
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);//转换成红黑树
}
}

如果想了解Java 8 的HashMap对比Java 7的HashMap,性能提升,请看http://it.deepinmind.com/%E6%80%A7%E8%83%BD/2014/04/24/hashmap-performance-in-java-8.html

java.util.HashMap源码分析的更多相关文章

  1. 【Java】HashMap源码分析——常用方法详解

    上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...

  2. 【Java】HashMap源码分析——基本概念

    在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...

  3. Java中HashMap源码分析

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

  4. java.util.Collection源码分析和深度讲解

    写在开头 java.util.Collection 作为Java开发最常用的接口之一,我们经常使用,今天我带大家一起研究一下Collection接口,希望对大家以后的编程以及系统设计能有所帮助,本文所 ...

  5. java.util.Hashtable源码分析

    Hashtable实现一个键值映射的表.任何非null的object可以用作key和value. 为了能存取对象,放在表里的对象必须实现hashCode和equals方法. 一个Hashtable有两 ...

  6. java.util.AbstractStringBuilder源码分析

    AbstractStringBuilder是一个抽象类,是StringBuilder和StringBuffer的父类,分析它的源码对StringBuilder和StringBuffer代码的理解有很大 ...

  7. java.util.Dictionary源码分析

    Dictionary是一个抽象类,Hashtable是它的一个子类. 类的声明:/** The <code>Dictionary</code> class is the abs ...

  8. java.util.HashSet源码分析

    public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java. ...

  9. java.util.Map源码分析

    /** * An object that maps keys to values. A map cannot contain duplicate keys; * each key can map to ...

随机推荐

  1. hdoj 2 括号配对问题【数组模拟实现+STL实现】

    栈遵循先进后出的原则 括号配对问题 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 现在,有一行括号序列,请你检查这行括号是否配对.   输入 第一行输入一个数N(0 ...

  2. 如何使用iframe实现隐藏的CSRF

    1.攻击者在“页面1”中http://www.b.com/indexb.html中写下如下代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1. ...

  3. Oracle Tnsping慢

    http://www.linuxidc.com/Linux/2014-02/96167.htm http://www.askmaclean.com/archives/dns%E8%AE%BE%E7%B ...

  4. js、jQuery操作input大全 不断完善

    工作中经常用到的,不断更新中... jquery获取单选按钮的值:$("input[name='stageName']:checked").val(); 删除select某几个op ...

  5. 【C语言】编写一个函数实现n^k,使用递归实现

    #include <stdio.h> int fuc(int x,int n) { if(n!=1) return x*fuc(x,n-1); return 1; } int main() ...

  6. 垂死挣扎还是涅槃重生 -- Delphi XE5 公布会归来感想

    Delphi 是一个基本上被我遗忘的工具, 要不是在使用RapidSql , 我是收不到Embarcadero 公司发出的邀请来參加Delphi XE5的公布会的. 有人可能要问为什么是Embarca ...

  7. css z-index详解

    写css z-index的时候经常会出现很多莫名其妙的问题,下面对z-index属性做彻底的剖析,本文参考了<一个css中z-index的用法>,并做了很多demo,方便了解z-index ...

  8. 【转】针对iOS VS. Android开发游戏的优劣——2013-08-25 17

    http://game.dapps.net/gamedev/experience/8670.html 问题:如果你正在一个新工作室开发一款新的平板/手机游戏,你会选择iOS还是Android? 回答: ...

  9. Android(java)学习笔记141:各种边距设置

    1. android:layout_paddingLeft 内边距,对谁用,指的是谁的内部内容边距 2. android:layout_marginLeft 外边距,对谁用,指的是谁距离外层容器的边距 ...

  10. javascript进击(一)简介

    javascript是属于网络的脚本语言(javascript与java就像老婆与老婆饼,并没有关系) 页面静态效果:HTML+CSS 为页面添加动态效果:javascript JavaScript ...