博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【jdk源码3】HashMap源码学习
阅读量:4548 次
发布时间:2019-06-08

本文共 3664 字,大约阅读时间需要 12 分钟。

  可以毫不夸张的说,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{  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元素的数量,在初始化的时候指定

  其它暂时没有遇到什么问题。

转载于:https://www.cnblogs.com/yiwangzhibujian/p/7171839.html

你可能感兴趣的文章
nmon for linux
查看>>
H5 EventSource 实现web页面推送功能demo
查看>>
Android JNI 学习(十):String Operations Api & Other Apis
查看>>
AutoMapper
查看>>
ecshop绕过验证码暴力破解
查看>>
数组和字符串的API使用
查看>>
201671010118 2016-2017-2《Java程序设计》 第十一周学习心得
查看>>
Get Sauce(状压DP)
查看>>
Office2007 升级到 office2010
查看>>
Python+Selenium 自动化实现实例-Xpath捕捉元素的几种方法
查看>>
SpringBoot整合Hibernate
查看>>
PPT1 例2
查看>>
。。。。。
查看>>
extern外部方法使用C#简单例子
查看>>
血液循环结构
查看>>
SQL Server统计数据库中表个数、视图个数、存储过程个数
查看>>
swift Reflection(字典转模型)变量继承本类类名解决办法
查看>>
设计模式:观察者模式
查看>>
转换排列Qt中使用OpenCV显示图片时,Mat结构转换为QImage结构的问题
查看>>
继承接口面向对象的7个设计原则
查看>>