能够绝不夸张的说,HashMap是容器类中用的最频繁的一个,而Java也对它进行优化,在jdk1.7及之前,当将相同Hash值的对象以key的身份放到HashMap中,HashMap的性能将由O(1)降低到O(N),因此jdk1.8将相同Hash值的key以红黑树的形式进行存储。数据结构
给个人感觉是,给用户自由,可是要在限定的范围内。ide
首先介绍初始容量是什么,引用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的几回方的值:this
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给你必定的自由,可是不是绝对的自由。spa
之后写代码也能够这么作,当一个重要属性可能影响效率时,不能给使用者太大的自由度,否则用起来慢就很差了。设计
首先简单说明下列表和红黑树,对它们有了基本的认识后才能知道为何决定将列表转成红黑树:code
也就是,有一个阈值,当超过阈值时才会将列表转换成红黑树,为了提升性能HashMap仍是考虑的不少的,主要有三个属性涉及到列表和红黑树转换:对象
static final int TREEIFY_THRESHOLD = 8; static final int UNTREEIFY_THRESHOLD = 6; static final int MIN_TREEIFY_CAPACITY = 64
TREEIFY_THRESHOLD :当一个节点的数量大于此数量的时候,将会将此节点列表转换成一个树。blog
UNTREEIFY_THRESHOLD :当已是一个树的节点,在移除元素的时候,若是移除后小于这个值,则将树转换成列表。
MIN_TREEIFY_CAPACITY :当一个节点准备转换成树以前,若是HashMap桶的数量小于此,则不进行转换,而是将HashMap进行扩容。
也就是说为了一个优化,至关于将HashMap一半的代码进行了从新,为了提高当元素都放置到一个桶时性能的降低。可是对于用户来讲是透明的,用户在使用上彻底感觉不到变化,因此说优化是没有终点的,这一点我仍是挺佩服他们的。
以前讲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接口,而不去比较本身的,简单总结它的逻辑:
看完这部分,我想了想TreeMap为何没有借鉴HashMap的这种方式,而必须让key具备比较性,缘由其实很简单,HashMap的做用就是存储快速读取,而TreeMap的额外多了个目的就是排序,若是一个key不具有可比较性,而最终使用了最原始的hashCode,那排序就没有了意义,还不如使用更高效的HashMap呢。
HashMap作为最经常使用的容器类,Java已经封装的足够好了,而咱们使用的时候若是能作到如下两点也就能最大化的提升HashMap的性能:
其它暂时没有遇到什么问题。