摘要

JDK1.8相较于1.7对HashMap做了很大的优化,比如加入了新数据结构红黑树、Hash算法的优化和扩容的优化。

本篇结合这些区别,探索HashMap的结构实现和功能原理。

存储结构-字段

从数据结构来看,HashMap是数组+链表+红黑树实现的,如图所示:

HashMap中重要的几个属性(JDK 1.8):

 static final int MAXIMUM_CAPACITY = 1 << 30;        // 所能容纳的Node极限数量
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 负载因子
static final int TREEIFY_THRESHOLD = 8; // 当链表长度超过8时,将链表结构转为红黑树结构
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // Node[] table的初始化长度length(默认值是16)

功能实现-方法

1.确定哈希桶数组索引位置

不管是增加、删除、查找键值对,定位到哈希桶数组的位置都是很关键的第一步。HashMap的数据结构是基于数组和链表(和红黑树),所以元素分布的越均匀,尽量使得每个位置上都只有一个元素(没有链表),那么我们用hash算法获取元素的时候,马上就可以得到,不需要遍历链表。HashMap定位索引位置,直接决定了hash方法的离散性能。下面看它是怎么做的:

static final int hash(Object key) {
int h;
// 第一步:h = key.hashCode() 取key的hashCode值
// 第二步:h ^ (h >>> 16) 高位参与运算
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
对于任意给定的对象,key的hashCode()返回值是一定的不变的。
为什么用这个算法?
key.hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值。

Java中'&'与、'|'或、'^'异或、'<<'左移位、'>>'右移位

2.分析HashMap的put方法

 public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
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是否为空或为null,否则执行resize()进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 根据Key计算的hash值得到插入的数组索引i,如果tab[i]==null,直接新建节点,添加至数组上
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 判断tab[i]的首个元素是否和要取的key一样,如果相同直接覆盖value
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 判断tab[i]是否为TreeNode,即是否为红黑树,如果是,在树中处理
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 否则,遍历tab[i]上的链表
else {
for (int binCount = 0; ; ++binCount) {
// 如果链表的长度大于8,那么将链表转红黑树,在红黑树中执行插入操作
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 如果发现链表中有相同的key(hash相同、key值也相同)
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;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

  ① 判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

  ② 根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

  ③ 判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

  ④ 判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

  ⑤ 遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

  ⑥ 插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

JDK1.8 重识HashMap的更多相关文章

  1. JDK1.7中HashMap死环问题及JDK1.8中对HashMap的优化源码详解

    一.JDK1.7中HashMap扩容死锁问题 我们首先来看一下JDK1.7中put方法的源码 我们打开addEntry方法如下,它会判断数组当前容量是否已经超过的阈值,例如假设当前的数组容量是16,加 ...

  2. iOS开发——项目篇—高仿百思不得姐 05——发布界面、发表文字界面、重识 bounds、frame、scrollView

    加号界面(发布模块) 一.点击加号modal出发布模块,创建控件,布局控件1)使用xib加载view,如果在viewDidLoad创建控件并设置frame 那么self.view 的宽高 拿到的是xi ...

  3. 基于JDK1.8版本的hashmap源码笔记(二)

    这一篇是接着上一篇写的, 上一篇的地址是:基于JDK1.8版本的hashmap源码分析(一)     /**     * 返回boolean类型的值,当集合中包含key的键值,就返回true,否则就返 ...

  4. 重识linux-SSH中的SFTP

    重识linux-SSH中的SFTP 1 SFTP也是使用SSH的通道(port 22) 2 SFTP是linux系统自带的功能 3 连接上主流的ftp软件都支持sftp协议 比如flashfxp,fi ...

  5. 重识linux-linux系统服务相关

    重识linux-linux系统服务相关 1 tcp wrappers 特殊功能  应用级防火墙 2 系统开启的服务查看 top,ps 命令 3 查看系统启动的服务 1) 找到目前系统开启的网络服务 n ...

  6. 重识linux-守护进程,系统服务,daemons

    重识linux-守护进程,系统服务,daemons 1分类 分为 单独的守护进程 和超级守护进程 2命名 服务的名称被创建之后,被挂上linux使用,通常在服务的名称之后会加上一个d,例如at和cro ...

  7. 重识linux-常见压缩和解压缩命令

    重识linux-常见压缩和解压缩命令 1 compress 目前已经很少使用,知道有个  不重点学习 2 gzip和zcat 目前应用最广泛 gzip [-cdtv#] 文件名 zcat 文件名.gz ...

  8. 重识linux-关于selinux

    重识linux-关于selinux 1 selinux是一个内核模块,有美国国家安全局研发,主要在基因redhat分支的系统上实现,当初的设计是未了避免用户资源的误用, 而SELINUX使用的是MAC ...

  9. 重识linux-循环执行的例行性工作调度

    重识linux-循环执行的例行性工作调度 1 用户的设置 1)/etc/cron.allow  可以使用的账号,在这个文件内 2)/etc/cron.deny 不可以的放在这个文件里面 allow的优 ...

随机推荐

  1. 新建MVC3 编译出现 System.Web.Mvc.ModelClientValidationRule

    我在vs2010 新建一个Mvc3项目,编译报一下错误: 错误 1 'c:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\A ...

  2. 【转】IT业给世界带来的危机

    IT业给世界带来的危机 昨天写了文章之后,回忆起这几年在湾区的经历,觉得自己是一个很不幸的人.然而就在今天,我的自怜奇妙的转换成了另一种感情,因为我看到了更不幸的人…… 正在女朋友 Cinny 的父母 ...

  3. 从语句 char* p="test" 说起

    我相信,使用C/C++多年的人对下面这个字符串赋值语句都不会陌生吧.                 char* p = "test";   同时,我也相信,各位在使用这种语句后吃 ...

  4. 【SDOI2014】【BZOJ3529】数表

    Description 有一张N×m的数表,其第i行第j列(1 < =i < =礼.1 < =j < =m)的数值为 能同一时候整除i和j的全部自然数之和.给定a,计算数表中不 ...

  5. eclipse jdk版本设置

    1 Java Build Path(项目的编写环境配置): 在项目上单击右键,properties -> Java Build Path -> Libraries,选择JRE System ...

  6. spring注解方式实现定时器

    1.Spring的配置: <beans xmlns:task="http://www.springframework.org/schema/task" xsi:schemaL ...

  7. Android Shell命令dumpsys

    dumpsys命令可以显示手机中所有应用程序的信息,并且也会给出现在手机的状态. 直接执行adb shell dumpsys KEY 会显示以下所有信息. KEY的可选名称 SurfaceFlinge ...

  8. 转:Math: Math.atan() 与 Math.atan2() 计算两点间连线的夹角

    我们可以使用正切操作将角度转变为斜率,那么怎样利用斜率来转换为角度呢?可以利用斜率的反正切函数将他转换为相应的角度.as中有两个函数可以计算反正切,我们来看一下. 1.Math.atan() Math ...

  9. The Linux Command Line,Script

    https://stackoverflow.com/questions/5891342/modify-conf-file-with-shell-script http://www.grymoire.c ...

  10. iOS开发transform的使用

    // //  ViewController.m //  18-transform的使用 #import "ViewController.h" @interface ViewCont ...