最近在重温java源码,一边看一边总结下,并且分享下自己的心得,共同学习,欢迎指点。这一篇说下HashMap的源码中的一些注意点,争取把原理讲透彻。
问题
字段:
1:table,hashMap中存储数据的变量
transient Entry[] table;
至于为什么设置为transient详见:【java源码系列】之ArrayList详解中有提到
2:threshold,loadFactor
3:modCount 记录hashMap的修改次数
当在迭代器中修改这个线程不安全的对象的时候,会抛出ConcurrentModificationException异常,这就是fail-fast策略。实现原理就是用modCount,判断modCount的值和期望的count值,如果不相等,则说明在iterator期间有对hashMap进行修改。
方法以及问题探究
1.hashCode()和equals()
2.key和value在hashmap中存储结构,解决冲撞问题
在hashmap中,数据的存储是通过Map.Entry<K,V>这个结构。K和V用的泛型,分别对key和value添加存取器,并且重写了hashCode和equals。
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
table可以抽象为:
如上图所示,当hashmap进行hash散表发现冲撞,会采用链表来解决问题,在统一个bucket上创建一个链来存储hash相同的元素。
3.put和get方法原理以及扩容问题
get和put方法是主要详解的,hashmap中大部分核心内容都在这里,通过分析这两个方法,会解决上面大部分问题,按照平时使用顺序来分析,首先分析put方法。
首先hashmap会对提供的key的hashCode做一个额外的hash,以防止质量很差的hashCode算法导致hashMap频繁冲撞,降低效率。
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
在hashMap中,散表的方法是:
static int indexFor(int h, int length) {
return h & (length-1);
}
其中h为经过额外hash之后的值。由于length永远为2的n次方,所以length-1的二进制的值都为1。由此可以推断出hashMap散表的方法就是取h二进制(length二进制位数)个低位,将其转为10进制就是这个key所在的桶的位置。所以hashMap在对hashCode做二次hash的时候尤其会低hashCode值的低位进行特殊处理。在hashMap中,length永远为2的n次方,这是在内部实现里强制约定的,它会强制把length转为大于你给定值的最小符合约定的大小。
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
这是在hashmap构造函数中的一段代码。
通过以上的额外针对hashCode的hash和散表,可以拿到key在桶中对应的位置,在这里分两种情况,插入和修改。修改很简单,找到对应的key,进行覆盖。注意:这里用的hash和equals进行判断key值的,因为,hash值是可以相同的,但是key的地址判断和equals方法对不同的对象是不同的;而插入,是每一次新的对象都会放到table里面,将next指向上一个table里对象。插入的时候有这么一段代码:
if (size++ >= threshold)
resize(2 * table.length);
其中threshold为table的capcity*负载因子。也就是说,当table桶的使用大于threshold的时候,要进行resize(2*table.length)方式进行扩容。resize方法也没有什么神秘的:判断size是否是int所能承受的最大值,如果不是,那么创建一个新的容量的table,并将老的数据按照新的table的容量进行hash。重新插入。如果是,则将threshold设为int最大值,等于负载因子为1。
了解了put的原理就会发现两个问题:一个是扩容时候的效率问题,另一个是扩容时候多线程的问题。为了尽量避免这些问题,用hashmap的时候不要存储庞大数量的数量。面对多线程并发有很多解决办法,如在使用hashMap程序断上加synchronized;创建hashmap用Collections.synchronizedMap来创建;或者使用ConcurrentHashMap来代替hashmap等等。这种选择应该根据具体情况而定。
get的方法很简单了,但是要注意一点,在判断key的问题上。上面刚才有提到用hash,地址,equals三个条件进行判断。
ok,hashMap这一篇就到这里,通过分析,开始提出的问题也都有了答案,使用hashMap注意点也就这么多了。如果还有什么问题,欢迎提出。