码迷,mamicode.com
首页 > 其他好文 > 详细

HashMap源码分析

时间:2015-07-20 09:06:42      阅读:323      评论:0      收藏:0      [点我收藏+]

标签:

一、数据结构 Map将实际数据存储在Entry类的数组中。 代码片段:

Java代码 技术分享 技术分享
  1. transient Entry[] table;//HashMap的成员变量,存放数据  
  2.   
  3. static class Entry<K,V> implements Map.Entry<K,V> {//内部类Entry  
  4.     final K key;  
  5.     V value;  
  6.     Entry<K,V> next;//指向下一个数据  
  7.     final int hash;  
  8.   
  9.     /** 
  10.      * Creates new entry. 
  11.      */  
  12.     Entry(int h, K k, V v, Entry<K,V> n) {  
  13.         value = v;  
  14.         next = n;  
  15.         key = k;  
  16.         hash = h;  
  17.     }  
transient Entry[] table;//HashMap的成员变量,存放数据

static class Entry<K,V> implements Map.Entry<K,V> {//内部类Entry
    final K key;
    V value;
    Entry<K,V> next;//指向下一个数据
    final int hash;

    /**
     * Creates new entry.
     */
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }

执行下面代码后,可能的存储内部结构是: Map map = new HashMap(); map.put("key1","value1"); map.put("key2","value2"); map.put("key3","value3");
技术分享 执行put方法时根据key的hash值来计算放到table数组的下标,如果hash到相同的下标,则新put进去的元素放到Entry链的头部,如上图所示。put方法的源码后面详细解释。
二、属性和构造方法

Java代码 技术分享 技术分享
  1. static final int DEFAULT_INITIAL_CAPACITY = 16;//默认的初始大小,如果执行无参的构造方法,则默认初始大小为16  
  2. static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量,1073741824  
  3. static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认的负载因子,如果没有通过构造方法传入负载因子,则使用0.75。  
  4. transient Entry[] table; //存放具体键值对的Entry数组  
  5. transient int size; //HashMap的大小  
  6. int threshold;//阀值  threshold = (int)(capacity * loadFactor); 即容量*负载因子,执行put方法时如果size大于threshold则进行扩容,后面put方法将会看到  
  7. final float loadFactor; //用户设置的负载因子  
  8. transient volatile int modCount;//HashMap实例被改变的次数,这个同ArrayList  
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认的初始大小,如果执行无参的构造方法,则默认初始大小为16
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量,1073741824
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认的负载因子,如果没有通过构造方法传入负载因子,则使用0.75。
transient Entry[] table; //存放具体键值对的Entry数组
transient int size; //HashMap的大小
int threshold;//阀值  threshold = (int)(capacity * loadFactor); 即容量*负载因子,执行put方法时如果size大于threshold则进行扩容,后面put方法将会看到
final float loadFactor; //用户设置的负载因子
transient volatile int modCount;//HashMap实例被改变的次数,这个同ArrayList

构造方法一、

Java代码 技术分享 技术分享
  1. public HashMap() {  
  2.     this.loadFactor = DEFAULT_LOAD_FACTOR;  
  3.     threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);  
  4.     table = new Entry[DEFAULT_INITIAL_CAPACITY];  
  5.     init();  
  6. }  
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
    table = new Entry[DEFAULT_INITIAL_CAPACITY];
    init();
}

使用了默认的容量和默认的负载因子。 构造方法二、

Java代码 技术分享 技术分享
  1. public HashMap(int initialCapacity) {  
  2.     this(initialCapacity, DEFAULT_LOAD_FACTOR);  
  3. }  
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

使用了用户设置的初始容量和默认的负载因子。 构造方法三、

Java代码 技术分享 技术分享
  1. public HashMap(int initialCapacity, float loadFactor) {  
  2.     if (initialCapacity < 0)  
  3.         throw new IllegalArgumentException("Illegal initial capacity: " +  
  4.                                            initialCapacity);  
  5.     if (initialCapacity > MAXIMUM_CAPACITY)  
  6.         initialCapacity = MAXIMUM_CAPACITY;  
  7.     if (loadFactor <= 0 || Float.isNaN(loadFactor))  
  8.         throw new IllegalArgumentException("Illegal load factor: " +  
  9.                                            loadFactor);  
  10.   
  11.     // Find a power of 2 >= initialCapacity  
  12.     int capacity = 1;  
  13.     while (capacity < initialCapacity)  
  14.         capacity <<= 1;  
  15.   
  16.     this.loadFactor = loadFactor;  
  17.     threshold = (int)(capacity * loadFactor);  
  18.     table = new Entry[capacity];  
  19.     init();  
  20. }  
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();
}

用户传入了初始容量和负载因子,这两个值是HashMap性能优化的关键,涉及到了HashMap的扩容问题。 HashMap的容量永远是2的倍数,如果传入的不是2的倍数则被调整为大于传入值的最近的2的倍数,例如如果传入130,则capacity计算后是256。是这段代码起的作用:

Java代码 技术分享 技术分享
  1. while (capacity < initialCapacity)  
  2.         capacity <<= 1;  
while (capacity < initialCapacity)
        capacity <<= 1;

构造方法四、

Java代码 技术分享 技术分享
  1. public HashMap(Map<? extends K, ? extends V> m) {  
  2.     this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,  
  3.                   DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);//计算Map的大小  
  4.     putAllForCreate(m);//初始化  
  5. }  
  6.   
  7. private void putAllForCreate(Map<? extends K, ? extends V> m) {  
  8.     for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {//通过entryset进行遍历  
  9.         Map.Entry<? extends K, ? extends V> e = i.next();  
  10.         putForCreate(e.getKey(), e.getValue());  
  11.     }  
  12. }  
public HashMap(Map<? extends K, ? extends V> m) {
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                  DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);//计算Map的大小
    putAllForCreate(m);//初始化
}

private void putAllForCreate(Map<? extends K, ? extends V> m) {
    for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {//通过entryset进行遍历
        Map.Entry<? extends K, ? extends V> e = i.next();
        putForCreate(e.getKey(), e.getValue());
    }
}

根据传入的map进行初始化。
三、关键方法 1)put

Java代码 技术分享 技术分享
  1. public V put(K key, V value) {  
  2.     if (key == null)  
  3.         return putForNullKey(value);//单独处理,总是放到table[0]中  
  4.     int hash = hash(key.hashCode());//计算key的hash值,后面介绍性能的时候再说这个hash方法。  
  5.     int i = indexFor(hash, table.length);//将hash和length-1取&来得到数组的下表  
  6.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  7.         Object k;  
  8.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  9.             V oldValue = e.value;  
  10.             e.value = value;  
  11.             e.recordAccess(this);  
  12.             return oldValue;  
  13.         }  
  14.     }//如果这个key值,原来已经则替换后直接返回。  
  15.     modCount++;  
  16.     addEntry(hash, key, value, i);  
  17.     return null;  
  18. }  
  19. void addEntry(int hash, K key, V value, int bucketIndex) {  
  20. Entry<K,V> e = table[bucketIndex];  
  21.        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);//如果table[bucketIndex]中已经存在Entry则放到头部。  
  22.        if (size++ >= threshold)//如果大于了阀值,则扩容到原来大小的2倍。  
  23.            resize(2 * table.length);  
  24.    }  
  25.   
  26. void resize(int newCapacity) {  
  27.     Entry[] oldTable = table;  
  28.     int oldCapacity = oldTable.length;  
  29.     if (oldCapacity == MAXIMUM_CAPACITY) {  
  30.         threshold = Integer.MAX_VALUE;  
  31.         return;  
  32.     }  
  33.   
  34.     Entry[] newTable = new Entry[newCapacity];  
  35.     transfer(newTable);//赋值到新的table中,注意转移后会重新hash,所以位置可能会跟之前不同,目的是均匀分不到新的table中。  
  36.     table = newTable;  
  37.     threshold = (int)(newCapacity * loadFactor);  
  38. }  
public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);//单独处理,总是放到table[0]中
    int hash = hash(key.hashCode());//计算key的hash值,后面介绍性能的时候再说这个hash方法。
    int i = indexFor(hash, table.length);//将hash和length-1取&来得到数组的下表
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }//如果这个key值,原来已经则替换后直接返回。
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
       table[bucketIndex] = new Entry<K,V>(hash, key, value, e);//如果table[bucketIndex]中已经存在Entry则放到头部。
       if (size++ >= threshold)//如果大于了阀值,则扩容到原来大小的2倍。
           resize(2 * table.length);
   }

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable);//赋值到新的table中,注意转移后会重新hash,所以位置可能会跟之前不同,目的是均匀分不到新的table中。
    table = newTable;
    threshold = (int)(newCapacity * loadFactor);
}

2)get方法

Java代码 技术分享 技术分享
  1. public V get(Object key) {  
  2.     if (key == null)  
  3.         return getForNullKey();  
  4.     int hash = hash(key.hashCode());  
  5.     for (Entry<K,V> e = table[indexFor(hash, table.length)];//找到数组的下表,进行遍历  
  6.          e != null;  
  7.          e = e.next) {  
  8.         Object k;  
  9.         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
  10.             return e.value;//找到则返回  
  11.     }  
  12.     return null;//否则,返回null  
  13. }  
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode());
    for (Entry<K,V> e = table[indexFor(hash, table.length)];//找到数组的下表,进行遍历
         e != null;
         e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
            return e.value;//找到则返回
    }
    return null;//否则,返回null
}

3)remove方法

Java代码 技术分享 技术分享
  1. public V remove(Object key) {  
  2.     Entry<K,V> e = removeEntryForKey(key);  
  3.     return (e == null ? null : e.value);  
  4. }  
  5.   
  6. final Entry<K,V> removeEntryForKey(Object key) {  
  7.     int hash = (key == null) ? 0 : hash(key.hashCode());  
  8.     int i = indexFor(hash, table.length);  
  9.     Entry<K,V> prev = table[i];  
  10.     Entry<K,V> e = prev;  
  11.   
  12.     while (e != null) {//Entry链未遍历完则一直遍历  
  13.         Entry<K,V> next = e.next;  
  14.         Object k;  
  15.         if (e.hash == hash &&  
  16.             ((k = e.key) == key || (key != null && key.equals(k)))) {  
  17.             modCount++;  
  18.             size--;  
  19.             if (prev == e)//如果是第一个,则将table[i]执行e.next  
  20.                 table[i] = next;  
  21.             else //否则将前一个的next指向e.next  
  22.                 prev.next = next;   
  23.             e.recordRemoval(this);  
  24.             return e;  
  25.         }  
  26.         prev = e;//未找到则继续往后遍历  
  27.         e = next;  
  28.     }  
  29.   
  30.     return e;  
  31. }  
public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.value);
}

final Entry<K,V> removeEntryForKey(Object key) {
    int hash = (key == null) ? 0 : hash(key.hashCode());
    int i = indexFor(hash, table.length);
    Entry<K,V> prev = table[i];
    Entry<K,V> e = prev;

    while (e != null) {//Entry链未遍历完则一直遍历
        Entry<K,V> next = e.next;
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)//如果是第一个,则将table[i]执行e.next
                table[i] = next;
            else //否则将前一个的next指向e.next
                prev.next = next; 
            e.recordRemoval(this);
            return e;
        }
        prev = e;//未找到则继续往后遍历
        e = next;
    }

    return e;
}

4)HashMap的遍历方法

Java代码 技术分享 技术分享
  1. Map map = new HashMap();  
  2. map.put("key1","value1");  
  3. map.put("key2","value2");  
  4. map.put("key3", "value3");  
  5. for(Iterator it = map.entrySet().iterator();it.hasNext();){  
  6.     Map.Entry e = (Map.Entry)it.next();  
  7.     System.out.println(e.getKey() + "=" + e.getValue());  
  8. }  
  9. System.out.println("-----------------------------------------");  
  10. for(Iterator it = map.keySet().iterator();it.hasNext();){  
  11.     Object key = it.next();  
  12.     Object value = map.get(key);  
  13.     System.out.println(key+"="+value);  
  14. }  
  15. System.out.println("-----------------------------------------");  
  16. System.out.println(map.values());  
  17.   
  18. 输出为:  
  19. key3=value3  
  20. key2=value2  
  21. key1=value1  
  22. -----------------------------------------  
  23. key3=value3  
  24. key2=value2  
  25. key1=value1  
  26. -----------------------------------------  
  27. [value3, value2, value1]  
Map map = new HashMap();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3", "value3");
for(Iterator it = map.entrySet().iterator();it.hasNext();){
    Map.Entry e = (Map.Entry)it.next();
    System.out.println(e.getKey() + "=" + e.getValue());
}
System.out.println("-----------------------------------------");
for(Iterator it = map.keySet().iterator();it.hasNext();){
    Object key = it.next();
    Object value = map.get(key);
    System.out.println(key+"="+value);
}
System.out.println("-----------------------------------------");
System.out.println(map.values());

输出为:
key3=value3
key2=value2
key1=value1
-----------------------------------------
key3=value3
key2=value2
key1=value1
-----------------------------------------
[value3, value2, value1]

四、性能相关 1)hash 如果总计算到相同的数组下标,则得进行Entry的遍历来取值和存放值,必然会影响性能。 所以HashMap提供了hash方法,来解决key的hashCode方法质量不高问题。

Java代码 技术分享 技术分享
  1. public V put(K key, V value) {  
  2. ...  
  3. int hash = hash(key.hashCode());  
  4. ...  
  5. }  
  6.   
  7. static int hash(int h) {  
  8.     // This function ensures that hashCodes that differ only by  
  9.     // constant multiples at each bit position have a bounded  
  10.     // number of collisions (approximately 8 at default load factor).  
  11.     h ^= (h >>> 20) ^ (h >>> 12);  
  12.     return h ^ (h >>> 7) ^ (h >>> 4);  
  13. }  
public V put(K key, V value) {
...
int hash = hash(key.hashCode());
...
}

static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

2)初始容量和负载因子 因为put的时候可能需要做扩容,扩容会导致性能损耗,所以如果可以预知Map大小的话,可以设置合理的初始大小和负载因子来避免HashMap的频繁扩容导致的性能消耗。

Java代码 技术分享 技术分享
  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2. ntry<K,V> e = table[bucketIndex];  
  3.       table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
  4.       if (size++ >= threshold)  
  5.           resize(2 * table.length);  
  6.   }  
 void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
       table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
       if (size++ >= threshold)
           resize(2 * table.length);
   }

五、同Hashtable的区别 1)HashMap允许key和value都可以为null,Hashtable都不可以为空。 HashMap的put方法,代码片段:

Java代码 技术分享 技术分享
  1. if (key == null)  
  2.         return putForNullKey(value);  
if (key == null)
        return putForNullKey(value);

Hashtable的put方法,代码片段:

Java代码 技术分享 技术分享
  1. if (value == null) {  
  2.     throw new NullPointerException();  
  3. }  
  4. Entry tab[] = table;  
  5.     int hash = key.hashCode();  
if (value == null) {
    throw new NullPointerException();
}
Entry tab[] = table;
	int hash = key.hashCode();

2)HashMap是非线程安全的,Hashtable是线程安全的。 Hashtable的put和get方法均为synchronized的。 六、ConcurrentHashMap ConcurrentHashMap是Doug Lea写的线程安全的HashMap实现,将HashMap默认划分为了16个Segment,减少了锁的争用,另外通过写时加锁读时不加锁减少了锁的持有时间,优雅的解决了高并发下锁的高竞争问题。

【java框架源码下载】

HashMap源码分析

标签:

原文地址:http://www.cnblogs.com/tianxia12/p/4660418.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!