map集合

来源:互联网 发布:淘宝联盟怎么微信推广 编辑:程序博客网 时间:2024/06/03 19:14

HashMap 实现类


构造方法源码:

[java] view plain copy
 print?
  1. static final int DEFAULT_INITIAL_CAPACITY = 16// 默认初始化容量  
  2. static final float DEFAULT_LOAD_FACTOR = 0.75f;   
  3. final float loadFactor; // 用于计算扩容阀值  
  4.  /* The next size value at which to resize (capacity * load factor) */  
  5. int threshold; // Entry扩容阀值  
  6. // The table, resized as necessary. Length MUST Always be a power of two.  
  7. transient Entry[] table;// 存放键值对的Entry数组  
  8. public HashMap() {   
  9.     this.loadFactor = DEFAULT_LOAD_FACTOR;  
  10.     threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); // 计算扩容阀值  
  11.     table = new Entry[DEFAULT_INITIAL_CAPACITY]; // 初始化Entry<K,V>数组  
  12.     init();  
  13. }  
  14. /* 在构造方法中,初始化了一个容量为16的Entry类型的数组,并且规定了当数组元素超过75%(16*0.75f=12个)的时候开始自动扩容*/  

put方法源码: eg: map.put("a","abc");
[java] view plain copy
 print?
  1. public V put(K key, V value) {  
  2.     if (key == null)  
  3.         return putForNullKey(value);  
  4.     int hash = hash(key.hashCode()); // 获取key的hash值,然后高16位不变,低16位和高16位作异或
  5.     int i = indexFor(hash, table.length); //hash & (table.length-1),定位hash值在table数组中的索引  
  6.     // 如果table数组中i索引所在位置有元素,循环遍历该链表中的下一个元素  
  7.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  8.         Object k;  
  9.         // hash值相同并且key也相同,把value用新值替换掉  
  10.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  11.             V oldValue = e.value;  
  12.             e.value = value;  
  13.             e.recordAccess(this);  
  14.             return oldValue;  
  15.         }  
  16.     }  
  17.       
  18.     modCount++;  
  19.     // table中i索引所在位置没有元素,添加key、value到指定索引处。  
  20.     addEntry(hash, key, value, i);  
  21.     return null;  
  22. }  

addEntry()方法源码:

[java] view plain copy
 print?
  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.     // 下面两行代码将entry保存进了table数组中Entry内部链表的第一个位置。  
  3.     Entry<K,V> e = table[bucketIndex];  
  4.     table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
  5.     if (size++ >= threshold) // 需要扩容了  
  6.         resize(2 * table.length); // 重新计算数组大小  
  7. }  

由于元素的位置是通过hashcode取模数组长度而得, 现在由于需要扩容,数组长度会发生变化,
所以会在resize方法跟transfer方法中进行元素位置的重新分配。

resize()方法源码: // 重新计算数组长度
[java] view plain copy
 print?
  1. void resize(int newCapacity) {  
  2.     Entry[] oldTable = table;  
  3.     int oldCapacity = oldTable.length;  
  4.     if (oldCapacity == MAXIMUM_CAPACITY) {  
  5.         threshold = Integer.MAX_VALUE;  
  6.         return;  
  7.     }  
  8.   
  9.   
  10.     Entry[] newTable = new Entry[newCapacity];  
  11.     transfer(newTable);  
  12.     table = newTable;  
  13.     threshold = (int)(newCapacity * loadFactor); // 新的扩容阀值  
  14. }  

transfer()方法源码:// 重新分配
[java] view plain copy
 print?
  1. void transfer(Entry[] newTable) {  
  2.     Entry[] src = table;  
  3.     int newCapacity = newTable.length;  
  4.     for (int j = 0; j < src.length; j++) {  
  5.         Entry<K,V> e = src[j];  
  6.         if (e != null) {  
  7.             src[j] = null;  
  8.             do {  
  9.                 Entry<K,V> next = e.next;  
  10.                 int i = indexFor(e.hash, newCapacity);  
  11.                 e.next = newTable[i];  
  12.                 newTable[i] = e;  
  13.                 e = next;  
  14.             } while (e != null);  
  15.         }  
  16.     }  
  17. }  
get方法源码

[java] view plain copy
 print?
  1. public V get(Object key) {  
  2.     if (key == null)  
  3.         return getForNullKey();  
  4.     int hash = hash(key.hashCode()); // 还是计算key的hashcode,  
  5.     // 定位hash值在table数组中的索引,并通过equals方法定位元素在链表中的位置。  
  6.     for (Entry<K,V> e = table[indexFor(hash, table.length)];  e != null; e = e.next) {  
  7.         Object k;  
  8.         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
  9.             return e.value;  
  10.     }  
  11.     return null;  
  12. }  
对HashMap源码作一个总结:

1、Entry[]数组是HashMap类的核心结构,称为“位桶数组”,键值对(key,value)就存储在这个数组里面。put元素的时候,首先对key的hashCode值作hash运算,即高16位不变,低16位与高16位异或(这么做是为了低位不变,高位变化的情况下发生冲突),然后再和位桶数组长度减一作与运算,从而定位元素在Entry数组中的索引。如果当前索引有元素,就通过hash值和equals方法与当前索引处的链表元素一一比较(若hash值相同并且equals也相同的key,把value旧值用新值替换掉),最后将元素存进当前链表的头部位置。JDK8中,若链表长度大于8,则采用红黑数结构。如果当前索引处没有元素,直接添加。如果元素个数超过阈值,对位桶数组进行扩容(不需要重新计算各个元素hash值,只需要看看原来hash值新增的二进制位是0还是1。如果是0,索引保持不变,如果是1,索引+原来位桶数组长度)get元素的时候,同样地首先定位元素在Entry数组中的索引,然后通过hash值和equals方法定位元素在链表中的位置,取出该元素。

2、位桶数组的长度总是2的n次方,这样做的好处是:一、与运算相当于取模运算,但与运算效率要高于取模运算;二、(length-1)的二进制表示全为1,这样就会使得key的每一位都参与运算,从而元素在位桶数组中分布均匀,发生冲突概率也较小。

HashMap和HashTable区别:

  1. 继承的父类不同。

    Hashtable 继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。

  2. 线程安全性不同。

    Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步,但使用HashMap时就必须要自己增加同步处理。

  3. key和value是否允许null值。

    • 其中key和value都是对象,并且不能包含重复key,但可以包含重复的value。
    • Hashtable中,key和value都不允许出现null值。
    • HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应 的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
    • HashMap 通过putForNullKey(value),将 null 键对应的值存在table[0]的位置。
  4. hash值不同

    HashTable直接使用对象的hashCode,而HashMap需要重新计算hash值。

  5. 内部实现使用的数组初始化和扩容方式不同。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

LinkedHashMap实现类

 是HashMap的子类,使用双向链表来维护key-value对的次序

TreeMap实现类

 红黑树数据结构,每个键值对作为红黑树的一个节点,根据key对节点进行排序


各Map实现类性能分析:

由于HashTable是一个线程安全的集合,因此HashMap通常要比HashTable快。

TreeMap通常比HashMap、HashTable慢,尤其插入、删除键值对时更慢。但是TreeMap中key-value对总是处于有序状态,无须专门进行排序操作。当TreeMap被填充后,就可以调用keySet(),取得key组成的Set,然后使用toArray()方法生成key的数组,接下来使用Arrays的binarySearch()方法查询对象。如果程序需要一个总是排好序的Map时,可以考虑使用TreeMap。