java集合HashMap、HashTable、HashSet详解
一、Set和Map关系
Set代表集合元素无序,集合元素不可重复的集合,Map代表一种由多个key-value组成的集合,map集合是set集合的扩展只是名称不同,对应如下
二、HashMap的工作原理
public V put(K paramK, V paramV) { //如果key为空,调用putForNullKey方法 if (paramK == null) return putForNullKey(paramV); //根据key的keyCode计算Hash值 int i = hash(paramK.hashCode()); //搜索指定hash值的对应在table中的索引 int j = indexFor(i, this.table.length); //如果j索引处的Entry不为空,通过循环遍历localEntry元素的下一个元素 for (Entry localEntry = this.table[j]; localEntry != null; localEntry = localEntry.next) { Object localObject1; //找到指定key与放入key相等(hash值相同,通过equals比较返回true) if ((localEntry.hash == i) && ((((localObject1 = localEntry.key) == paramK) || (paramK .equals(localObject1))))) { Object localObject2 = localEntry.value; localEntry.value = paramV; localEntry.recordAccess(this); return localObject2; } } //如果j索引Entry为null,此处没有Entry this.modCount += 1; //将key、value添加到i索引处 addEntry(i, paramK, paramV, j); return null; } void addEntry(int paramInt1, K paramK, V paramV, int paramInt2) { //获取指定bucketIndex索引处Entry Entry localEntry = this.table[paramInt2]; //将新创建的Entry放入bucketIndex索引处,并让新的Entry指向原来的Entry this.table[paramInt2] = new Entry(paramInt1, paramK, paramV, localEntry); //如果map中的key-value数量超过 if (this.size++ >= this.threshold) //table对象的长度扩充到2倍 resize(2 * this.table.length); }
put方法三种情况,如图:
get()方法:当HashMap的每个bucket里存储的Entry只是单个Entry,即没有通过指针产生Entry链时,此时HashMap具有最好的性能。当程序通过key取出对应value时,系统先计算出该key的hashCode()返回值,再根据该hashCode返回值找出该key在table数组中的索引,然后取出该索引处的Entry,最后返回该key对应的value值。get源码如下:
public V get(Object paramObject) { //如果key为空,调用getForNullKey取出对应的value if (paramObject == null) return getForNullKey(); //根据key的hashCode值计算hash码 int i = hash(paramObject.hashCode()); //直接取出table数组中指定索引处的值 Entry localEntry = this.table[indexFor(i, this.table.length)]; while (localEntry != null) { Object localObject; //如果该Entry的key与被搜索key相同 if ((localEntry.hash == i) && ((((localObject = localEntry.key) == paramObject) || (paramObject .equals(localObject))))) return localEntry.value; //搜索该Entry链的下一个 localEntry = localEntry.next; } return null; } 从代码看出,HashMap的每个bucket里只有一个Entry,HashMap可以根据索引快速取出该bucket里的Entry。
在发生Hash冲突的情况下,单个bucket里存储的不是一个Entry,而是一个Entry链,系统只能按顺序遍历每个Entry,直到找到想搜索的Entry。
HashMap有两个参数影响其性能:
1. 初始容量和加载因子。默认初始容量是16,加载因子是0.75。容量是哈希表中桶(Entry数组)的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用rehash 方法将容量翻倍。
2. 加载因子过高虽然减少了空间开销,但同时也增加了查询成本(加载因子是表示Hsah表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地降低rehash 操作次数。如果初始容量大于最大条目数除以加载因子(实际上就是最大条目数小于初始容量*加载因子),则不会发生 rehash 操作。
3.HashMap存放的元素越来越多,到达临界值(阀值)threshold时,就要对Entry数组扩容,这是Java集合类框架最大的魅力,HashMap在扩容时,新数组的容量将是原来的2倍,由于容量发生变化,原有的每个元素需要重新计算bucketIndex,再存放到新数组中去,也就是所谓的rehash。HashMap默认初始容量16,加载因子0.75,也就是说最多能放16*0.75=12个元素,当put第13个时,HashMap将发生rehash,rehash的一系列处理比较影响性能,所以当我们需要向HashMap存放较多元素时,最好指定合适的初始容量和加载因子,否则HashMap默认只能存12个元素,将会发生多次rehash操作。
三、HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,主要的区别有:线程安全性,同步(synchronization),以及速度。HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。