HashMap的实现假定hash函数各个元素正分布在各桶之间Q可为基本操作(如get()和set()Q提供稳定的性能QP代集合视图所需的时间与HashMap 实例?#8220;定w”Q桶的数量)及其大小Q键-值映关pLQ的和成比例。所以,如果q代性能很重要,则不要将初始定w讄得太高(或将加蝲因子讄得太低)?Q注Q这D同HashSet很相|其实HashSet是基本HashMap实现的,所以在性能的控制上也一P
HashMap 的实例有两个参数影响其性能Q初始容量和加蝲因子。容量是哈希表中桶的数量Q初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一U尺度。当哈希表中的条目数出了加载因子与当前定w的乘U时Q通过调用 rehash Ҏ容量翻倍。HashMap的默认初始容量是16Q默认加载因子是0.75Q这P当HashMap里的元素过16*0.75?2Ӟ调用rehashҎQ定w增长一倍?/p>
通常Q默认加载因?(.75) 在时间和I间成本上寻求一U折街加载因子过高虽然减了I间开销Q但同时也增加了查询成本Q在大多?nbsp;HashMapcȝ操作中,包括 get ?nbsp;put 操作Q都反映了这一点)。在讄初始定w时应该考虑到映中所需的条目数及其加蝲因子Q以便最大限度地降低 rehash 操作ơ数。如果初始容量大于最大条目数除以加蝲因子Q则不会发生 rehash 操作?/p>
如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动?rehash 操作以增大表的容量来_使用_大的初始定w创徏它将使得映射关系能更有效地存储?nbsp;
注意Q此实现不是同步的?/strong> 如果多个U程同时讉K此映,而其中至一个线E从l构上修改了该映,则它必须 保持外部同步。(l构上的修改是指d或删除一个或多个映射关系的操作;仅改变与实例已经包含的键兌的g是结构上的修攏V)q一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在q样的对象,则应该?nbsp;Collections.synchronizedMap Ҏ?#8220;包装”该映。最好在创徏时完成这一操作Q以防止Ҏ进行意外的不同步访问,如下所C:
Map m = Collections.synchronizedMap(new HashMap(...));
由所有此cȝ“集合视图Ҏ”所q回的P代器都是快速失败的Q在q代器创Z后,如果从结构上Ҏ进行修改,除非通过q代?/p>
自n?remove ?add ҎQ其他Q何时间Q何方式的修改QP代器都将抛出 ConcurrentModificationException 。因此,面对q发
的修改,q代器很快就会完全失败,而不冒在来不确定的旉L发生不确定行为的风险?
注意QP代器的快速失败行Z能得C证,一般来_存在不同步的q发修改Ӟ不可能作ZQ何坚决的保证。快速失败P代器最?/p>
努力抛出 ConcurrentModificationException 。因此,~写依赖于此异常E序的方式是错误的,正确做法是:q代器的快速失败行为应
该仅用于程序错?em style="font-style: italic; ">?/em> HashMap的构造函敎ͼ
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}可以看出Q当生成HashMap对象Ӟ如果initialCapacity的数值比较大Ӟcapacity 会被赋于{于或大于initialCapacity 的|然后
创徏一个有capacity 个Entry对象的table数组Q所以initialCapacity的数g能太大,否则会生成比较大的数ltableQ占用比较大的应用内
存,造成内存费?/p>
HashMap的默认构造函敎ͼ
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}默认构造函数里的初始容量ؓ16Q加载因子ؓ0.75Q这样会生成含有16*0.75=12个Entry对象的数ltableQ当HashMap里的元素?/p>
量超q?2个时Q会HashMap的容量翻1倍,生成包含32个Entry对象的数l?/p>
putҎQ?/span>
public V put(K key, V value) {
K k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, k, value, i);
return null;
}
K k = maskNull(key);q行是判断key是否为nullQ如果ؓnullQ则q回一个静态的KQObjectQ对象做为键|q就是HashMapZ么允许null键存在的原因?/span>
int hash = hash(k);
int i = indexFor(hash, table.length);
q连l的两步是 HashMap 最牛的地方Q研I完我都汗颜了,其中 hash 是通过 key q个Object?hashcode q行 hashQ然后通过 indexFor 获得在Object table的烦引倹{?/span>
tableQ?Q不要惊Ӟ其实HashMap也神不到哪里去,它就是用 table 来放的。最牛的是?hash 能正的q回索引。其中的hash法Q我跟JDK的作?Doug 联系q,他徏议我看看《The art of programing vol3》可恨的是,我之前就一直在找,我都找不刎ͼ他这样一提,我就更加急了Q可惜口袋空I啊Q!Q?br />
不知道大家有没有留意 put 其实是一个有q回的方法,它会把相同键值的 put 覆盖掉ƈq回旧的|如下Ҏd说明?HashMap 的结构,其实是一个表加上在相应位|的Entry的链表:
for (Entry e = table; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
Object oldvalue = e.value;
e.value = value; //把新的D予给对应键倹{?br />
e.recordAccess(this); //I方法,留待实现
return oldvalue; //q回相同键值的对应的旧的倹{?br />
}
}
modCount++; //l构性更改的ơ数
addEntry(hash, k, value, i); //d新元素,关键所在!
return null; //没有相同的键D?br />
}
我们把关键的Ҏ拿出来分析:
void addEntry(int hash, Object key, Object value, int bucketIndex) {
table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]);
因ؓ hash 的算法有可能令不同的键值有相同的hash码ƈ有相同的table索引Q如QkeyQ?#8220;33”和keyQObject g的hash都是Q?901334Q那它经qindexfor之后的烦引一定都为iQ这样在new的时候这个Entry的next׃指向q个原本?tableQ再有下一个也如此QŞ成一个链表,和put的@环对定e.next获得旧的倹{到q里QHashMap的结构,大家也十分明白了吧?
if (size++ >= threshold) //q个threshold是能实际容U的?br />
resize(2 * table.length); //出q个定w׃Object table重构
所谓的重构也不,是Z个两倍大的tableQ我在别的论坛上看到有h说是两倍加1Q把我骗了)Q然后再一个个indexforq去Q注意!Q这是效率Q!如果你能让你的HashMap不需要重构那么多ơ,效率会大大提高!
说到q里也差不多了,get比put单得多,大家Q了解putQget也差不了多少了。对于collections我是认ؓQ它是适合q泛的,当不?全适合Ҏ的,如果大家的程序需要特D的用途,自己写吧Q其实很单。(作者是q样跟我说的Q他q徏议我用LinkedHashMap,我看了源码以后发 玎ͼLinkHashMap其实是l承HashMap的,然后override相应的方法,有兴的同hQ自己looklookQ徏?Object tableQ写相应的算法,ok啦?br />
举个例子吧,?VectorQlist 啊什么的其实都很单,最多就多了的同步的声明Q其实如果要实现像Vector那种Q插入,删除不多的,可以用一个Object table来实玎ͼ按烦引存取,d{?br />
如果插入Q删除比较多的,可以Z个Object tableQ然后每个元素用含有nextl构的一个table存,如果要插入到iQ但是i已经有元素,用nextqv来,然后sizeQ+Qƈ在另一个table记录其位|?/span>