HashMap源码分块阅读(1)#
面试的时候经常会问起HashMap和HashSet的区别,通过百度搜出了一大堆博文,然后面试前突击一下很有效,但都是死记硬背,过后就忘了.现在觉得很有必要彻读一下HashMap的源代码,"道听途说"哪有拨开迷雾识得真面目来得印象深刻?
HashMap源码注释##
JDK中的类和类中的方法前面都有很详细的注释,其中有代码设计者对这个类的使用说明和高效率的使用方式,非常值得一读(虽然都是英文的),这里一边看一边将它翻成了中文.
- 哈希表的基于Map接口实现,该接口提供了所有的map的操作方法,并且允许空的键和值存在HashMap类和Hashtable类大致相同,但是HashMap不同步并且允许空值存在.这个类不能保证其中存储对象的顺序.更重要的是,它不能保证顺序将不随时间变化.
- 这个实现类为基本操作(get和put)提供类稳定的性能,假设hash方法能够让HashMap中的元素均匀地分布在各个桶(buclect)中,迭代集合所需的时间与HashMap实例中桶(bucket)的大小(bucket中的键值对)和数量(HashMap的容量)成正比.因此,为了让HashMap在迭代查询时能够有优秀的性能表现,最好不要把初始的容量设置得太高(或者负载因子太低)
- HashMap中的俩个参数会对它的性能产生影响.initial capacity(初始容量)和loadfactor(负载因子).capacity指的是哈希表中桶(buclect)的数量,initial capacity指hash表被创建的时候的容量.load factor是一个指标:HashMap扩容前(rehash)允许存储的条目数的最大值.如果hash表中条目数超过了loadFactor和和loadfactor的沉积,hash表会被再哈希(内部的数据结构将会被重构),哈希表内的桶(buclect)的数量会增加一倍.
- 通常默认将load factor设为0.75,这样能平衡时间开销和空间开销.较高的负载因子会降低空间开销,但是会提高查找的成本受影响最多的就是get和put方法.在设置初始容量的时候,要存储多少数据项和设置多大的负载因子都应该被考虑在内,这样就可尽可能少的执行再hash方法.如果初始容量大于由负载因子划分的最大项数,那么再哈希方法不会被执行
- 如果有很多键值对要被储存在HashMap实例中,最好还是将容量设置得足够大,这样会让键值对的存储更加高效,不然HashMaP将会随着数据的增多,容量如果不够,HashMap会进行多次的再哈希操作(数据结构改变,重新划分数据的位置将将降低性能)
- 请注意这个实现类不是同步的.如果多个线程同时访问一个hashmap , 并且至少一个线程修改map的结构,那么必须要在外部进行同步操作.(结构改变指的是删除和或者增加多个键值对,仅仅修改一个key对应的value,而这个key已经存在于map中,这不是结构性的修改),通常是通过对某些对象进行同步来实现封装视图的.
- 如果没有这样的对象,map就应该被同步方法封装,最好是在map初创的时候,这样可以防止以外的非同步访问例如 Map m = Collections.synchronizedMap(new HashMap(...));
- 这个类所有能够返回迭代数据的"集合视图方法"(得到集合内存储的数据)都是快速失败(Fail-fast)的.如果map的结构在迭代器被创建后被改变,除了remove方法,迭代器将会抛出一个ConcurrentModificationException异常.因此,面对并发修改,迭代器将会快速失败并清空,而不会冒险(将来某个时间任意且不确定的风险)
- 请注意迭代器的快速失败并不能保证一定出现,通常来讲,对于存在非同步的结构性修改的场合,不会作出任何严格的保证.快速失败(Fail-fast)将会尽最大努力迭代器抛出ConcurrentModificationException异常.因此,依赖于这个异常的正确性进行编程是不正确的,这个异常只能用来检验错误.