【jdk源码3】HashMap源码学习
可以毫不夸张的说,HashMap是容器类中用的最频繁的一个,而Java也对它进行优化,在jdk1.7及以前,当将相同Hash值的对象以key的身份放到HashMap中,HashMap的性能将由O(1)下降到O(N),所以jdk1.8将相同Hash值的key以红黑树的形式进行存储。
一、简单理解
1.1 初始容量的设计
给我的感受是,给用户自由,但是要在限定的范围内。
首先介绍初始容量是什么,引用Java API中的介绍:
HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
也就是说,我们可以有远见知道HashMap中将要存入多少数据,而相应的设置初始容量,减少rehash的次数,因为每次rehash将会对HashMap进行一次重构,影响性能,因此HashMap的构造方法中提供了对初始容量的设置:
HashMap(int initialCapacity) :构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor) :构造一个带指定初始容量和加载因子的空 HashMap。
但是HashMap在设计的时候,已经考虑到要rehash,以及根据Hash值在固定数量的桶中查询数据,加上对数字的移位运算最高效,所以桶的数量被设计为2的几次方,但是用户输入初始容量是任意的,HashMap是怎么处理的呢?它是取比输入值-1大的且最近的2的几次方的值:
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
也就是说最终初始容量不一定安装你输入的来,就像一开始说的那样,HashMap给你一定的自由,但是不是绝对的自由。
以后写代码也可以这么做,当一个重要属性可能影响效率时,不能给使用者太大的自由度,不然用起来慢就不好了。
1.2 列表转树的条件
首先简单说明下列表和红黑树,对它们有了基本的认识后才能知道为什么决定将列表转成红黑树:
- 所占空间:红黑树>列表
- 性能:红黑树>列表,但是当元素数量特别少时,列表的性能还是大于红黑树的
也就是,有一个阈值,当超过阈值时才会将列表转换成红黑树,为了提高性能HashMap还是考虑的很多的,主要有三个属性涉及到列表和红黑树转换:
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64
TREEIFY_THRESHOLD :当一个节点的数量大于此数量的时候,将会将此节点列表转换成一个树。
UNTREEIFY_THRESHOLD :当已经是一个树的节点,在移除元素的时候,如果移除后小于这个值,则将树转换成列表。
MIN_TREEIFY_CAPACITY :当一个节点准备转换成树之前,如果HashMap桶的数量小于此,则不进行转换,而是将HashMap进行扩容。
也就是说为了一个优化,相当于将HashMap一半的代码进行了重新,为了提升当元素都放置到一个桶时性能的下降。但是对于用户来说是透明的,用户在使用上完全感受不到变化,所以说优化是没有终点的,这一点我还是挺佩服他们的。
1.3 元素的大小比较
之前讲TreeMap的时候说过,放入TreeMap的key必须具备可比较性,要么本身实现Comparable接口,要么传入key的比较器,因为如果key不能比较大小,就没办法构建一棵树。而我们在放入HashMap的key时,却没有对此有要求,它是怎么实现的呢。
首先判断key的类型是否是Comparable,如果是,就通过自身的比较方法进行比较。
如果key不是Comparable,那么就通过key本身的Hash值进行比较,即便子类重写了hashCode方法,也会用最原始的,实际上是用了System的一个方法:
System.identityHashCode(Object x)
这个方法,返回给定对象的哈希码,该代码与默认的方法 hashCode() 返回的代码一样,无论给定对象的类是否重写 hashCode()。
也就是说最终总是能比较出大小,当然如果还一样,说明key是一样的,覆盖即可。
其中判断key的类型是否是Comparable中有一段代码如下:
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks
return c;
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
说实话我一开始没有想到会这么复杂,后来我认真研究了一下发现一个类即便实现了Comparable接口,也有可能比较的是其他类:
class Dog implements Comparable<Object>{
public String name; public Dog(String name) {
this.name = name;
}
@Override
public int compareTo(Object o) {
// TODO Auto-generated method stub
return 0;
}
}
没想到判断的这么严谨,因为印象中很少有类实现Comparable接口,而不去比较自己的,简单总结它的逻辑:
- 首先是一个Comparable
- Comparable必须是一个参数化类型,就是说指定比较类型
- 参数化有一个参数,且是它本身
看完这部分,我想了想TreeMap为什么没有借鉴HashMap的这种方式,而必须让key具有比较性,原因其实很简单,HashMap的作用就是存储快速读取,而TreeMap的额外多了个目的就是排序,如果一个key不具备可比较性,而最终使用了最原始的hashCode,那排序就没有了意义,还不如使用更高效的HashMap呢。
二、问题及总结
HashMap做为最常用的容器类,Java已经封装的足够好了,而我们使用的时候如果能做到以下两点也就能最大化的提高HashMap的性能:
- 自定义对象做为key时,重写hashCode和equals方法
- 如果能预见存入HashMap元素的数量,在初始化的时候指定
其它暂时没有遇到什么问题。
【jdk源码3】HashMap源码学习的更多相关文章
- JDK源码解析---HashMap源码解析
HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的,只是 ...
- JAVA源码分析-HashMap源码分析(一)
一直以来,HashMap就是Java面试过程中的常客,不管是刚毕业的,还是工作了好多年的同学,在Java面试过程中,经常会被问到HashMap相关的一些问题,而且每次面试都被问到一些自己平时没有注意的 ...
- JDK 1.8之 HashMap 源码分析
转载请注明出处:http://blog.csdn.net/crazy1235/article/details/75579654 构造函数 Node hash put treeifyBin get re ...
- jdk1.7源码之-hashMap源码解析
背景: 笔者最近这几天在思考,为什么要学习设计模式,学些设计模式无非是提高自己的开发技能,但是通过这一段时间来看,其实我也学习了一些设计模式,但是都是一些demo,没有具体的例子,学习起来不深刻,所以 ...
- JAVA源码分析-HashMap源码分析(二)
本文继续分析HashMap的源码.本文的重点是resize()方法和HashMap中其他的一些方法,希望各位提出宝贵的意见. 话不多说,咱们上源码. final Node<K,V>[] r ...
- 【源码】HashMap源码及线程非安全分析
最近工作不是太忙,准备再读读一些源码,想来想去,还是先从JDK的源码读起吧,毕竟很久不去读了,很多东西都生疏了.当然,还是先从炙手可热的HashMap,每次读都会有一些收获.当然,JDK8对HashM ...
- JDK 1.6 HashMap 源码分析
前言 前段时间研究了一下JDK 1.6 的 HashMap 源码,把部份重要的方法分析一下,当然HashMap中还有一些值得研究得就交给读者了,如有不正确之处还望留言指正. 准备 需要熟悉数组 ...
- Java HashMap源码分析(含散列表、红黑树、扰动函数等重点问题分析)
写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...
- JDK8中的HashMap源码
背景 很久以前看过源码,但是猛一看总感觉挺难的,很少看下去.当时总感觉是水平不到.工作中也遇到一些想看源码的地方,但是遇到写的复杂些的心里就打退堂鼓了. 最近在接手同事的代码时,有一些很长的pytho ...
- 金三银四助力面试-手把手轻松读懂HashMap源码
前言 HashMap 对每一个学习 Java 的人来说熟悉的不能再熟悉了,然而就是这么一个熟悉的东西,真正深入到源码层面却有许多值的学习和思考的地方,现在就让我们一起来探索一下 HashMap 的源码 ...
随机推荐
- Googel 浏览器 模拟发送请求工具--Advanced REST Client
Advanced REST Client是 Chrome 浏览器下的一个插件,通过它可以发送 http.https.WebSocket 请求.在 Chrome 商店下搜索 Advanced REST ...
- c++中堆、栈、自由存储区和常量存储区(转)
代码段 --text(code segment/text segment)text段在内存中被映射为只读,但.data和.bss是可写的.text段是程序代码段,在AT91库中是表示程序段的大小,它是 ...
- [开源项目]Shell4Win,一个在Windows下执行shell命令的解释器
背景 顺利拿到心目中的理想offer之后,心里的负担一下减轻了很多,希望利用还没毕业之前这段难得的悠闲时间做一点有意义的事情.于是希望能做一个长久以来都想做的开源项目,就是题中提到的Windows下的 ...
- 玩转mongodb(八):分布式计算--MapReduce
MongoDB提供了MapReduce的聚合工具来实现任意复杂的逻辑,它非常强大,非常灵活.MapReduce使用JavaScript作为“查询语言”,能够在多台服务器之间并行执行.它会将一个大问题拆 ...
- php安装扩展模块后,重启不生效的原因及解决办法
在lnmp运维环境中,我们经常会碰到有些php依赖的扩展模块没有安装,这就需要后续添加这些扩展模块.在扩展被安装配置后,往往会发现php-fpm服务重启后,这些扩展并没有真正加载进去!下面就以一个示例 ...
- python 使用 matplotlib.pyplot来画柱状图和饼图
导入包 import matplotlib.pyplot as plt 柱状图 最简柱状图 # 显示高度 def autolabel(rects): for rect in rects: height ...
- Spark入门——什么是Hadoop,为什么是Spark?
#Spark入门#这个系列课程,是综合于我从2017年3月分到今年7月份为止学习并使用Spark的使用心得感悟,暂定于每周更新,以后可能会上传讲课视频和PPT,目前先在博客园把稿子打好.注意:这只是一 ...
- WCF 学习总结1 -- 简单实例
从VS2005推出WCF以来,WCF逐步取代了Remoting, WebService成为.NET上分布式程序的主要技术.WCF统一的模型整合了以往的 WebService.Remoting.MSMQ ...
- Jquery特殊属性
val():获取或设置元素的值,主要用于input. 参数:string 字符串 设置元素的值: 不写参数:获取元素的值: 其实这个属性 我们也可以用attr操作,但是没有这个方便 添加类 ...
- 【MongoDB-query查询条件】
在上一篇中简要使用了C# 对MongoDB进行数据操作,这里补充一些MongoDB query查询条件文档: Query.All("name", "a",&qu ...