HashMap源代码剖析

大部分思路都是一样的 。仅仅是一些细节不一样。源代码中都标了出来。jdk容器源代码还是挺简单的。

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{

  //容量默认值
    static final int DEFAULT_INITIAL_CAPACITY = 16;
//最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;

  //装载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

  //数据域
    transient Entry[] table;

  //元素个数
    transient int size;

   //阈值
    int threshold;

  //装在因子
    final float loadFactor;
  //volatile不会再线程私有的地方保留副本。直接写入主存,而且防止JVM重排序
    transient volatile int modCount;

//构造函数
    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);

        //选取比initialCapacity大的最小2的幂次
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }

    //构造函数
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

//默认构造函数
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
    }
    void init() {
    }

//关键 使hash分布跟均匀  冲突更少
    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);
    }

   //与hashTable不同这里进行与运算
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    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 key的value
    private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
//得到key
    final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
//key相等要 hash和equals同一时候满足
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        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;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

    private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

    private void putForCreate(K key, V value) {
        int hash = (key == null) ?

0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);

        /**
         * Look for preexisting entry for key.  This will never happen for
         * clone or deserialize.  It will only happen for construction if the
         * input Map is a sorted map whose ordering is inconsistent w/ equals.
         */
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                e.value = value;
                return;
            }
        }

        createEntry(hash, key, value, i);
    }

    private void putAllForCreate(Map<? extends K, ?

extends V> m) {
        for (Iterator<? extends Map.Entry<?

extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
            Map.Entry<? extends K, ? extends V> e = i.next();
            putForCreate(e.getKey(), e.getValue());
        }
    }

//扩容
    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 = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }
//转移元素
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        int numKeysToBeAdded = m.size();
        if (numKeysToBeAdded == 0)
            return;

        if (numKeysToBeAdded > threshold) {
            int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
            if (targetCapacity > MAXIMUM_CAPACITY)
                targetCapacity = MAXIMUM_CAPACITY;
            int newCapacity = table.length;
            while (newCapacity < targetCapacity)
                newCapacity <<= 1;
            if (newCapacity > table.length)
                resize(newCapacity);
        }

        for (Iterator<?

extends Map.Entry<?

extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
            Map.Entry<? extends K, ?

extends V> e = i.next();
            put(e.getKey(), e.getValue());
        }
    }

    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<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] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

    final Entry<K,V> removeMapping(Object o) {
        if (!(o instanceof Map.Entry))
            return null;

        Map.Entry<K,V> entry = (Map.Entry<K,V>) o;
        Object key = entry.getKey();
        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<K,V> next = e.next;
            if (e.hash == hash && e.equals(entry)) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

    public void clear() {
        modCount++;
        Entry[] tab = table;
        for (int i = 0; i < tab.length; i++)
            tab[i] = null;
        size = 0;
    }

    public boolean containsValue(Object value) {
	if (value == null)
            return containsNullValue();

	Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (value.equals(e.value))
                    return true;
	return false;
    }

    private boolean containsNullValue() {
	Entry[] tab = table;
        for (int i = 0; i < tab.length ; i++)
            for (Entry e = tab[i] ; e != null ; e = e.next)
                if (e.value == null)
                    return true;
	return false;
    }

    public Object clone() {
        HashMap<K,V> result = null;
	try {
	    result = (HashMap<K,V>)super.clone();
	} catch (CloneNotSupportedException e) {
	    // assert false;
	}
        result.table = new Entry[table.length];
        result.entrySet = null;
        result.modCount = 0;
        result.size = 0;
        result.init();
        result.putAllForCreate(this);

        return result;
    }

    static class Entry<K,V> implements Map.Entry<K,V> {
        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;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
	    V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return (key==null   ?

0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that‘s already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }

    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);
    }

    void createEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        size++;
    }

    private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;	// next entry to return
        int expectedModCount;	// For fast-fail
        int index;		// current slot
        Entry<K,V> current;	// current entry

        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
	    current = e;
            return e;
        }

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }

    }
//直接从 HashIterator的返回值改一下
    private final class ValueIterator extends HashIterator<V> {
        public V next() {
            return nextEntry().value;
        }
    }

    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }

    private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
        public Map.Entry<K,V> next() {
            return nextEntry();
        }
    }

    // Subclass overrides these to alter behavior of views‘ iterator() method
    Iterator<K> newKeyIterator()   {
        return new KeyIterator();
    }
    Iterator<V> newValueIterator()   {
        return new ValueIterator();
    }
    Iterator<Map.Entry<K,V>> newEntryIterator()   {
        return new EntryIterator();
    }

    // Views

    private transient Set<Map.Entry<K,V>> entrySet = null;

    public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ?

ks : (keySet = new KeySet()));
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

    public Collection<V> values() {
        Collection<V> vs = values;
        return (vs != null ? vs : (values = new Values()));
    }

    private final class Values extends AbstractCollection<V> {
        public Iterator<V> iterator() {
            return newValueIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsValue(o);
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

    public Set<Map.Entry<K,V>> entrySet() {
	return entrySet0();
    }

    private Set<Map.Entry<K,V>> entrySet0() {
        Set<Map.Entry<K,V>> es = entrySet;
        return es != null ?

es : (entrySet = new EntrySet());
    }

    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return newEntryIterator();
        }
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<K,V> e = (Map.Entry<K,V>) o;
            Entry<K,V> candidate = getEntry(e.getKey());
            return candidate != null && candidate.equals(e);
        }
        public boolean remove(Object o) {
            return removeMapping(o) != null;
        }
        public int size() {
            return size;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }
    private void writeObject(java.io.ObjectOutputStream s)
        throws IOException
    {
	Iterator<Map.Entry<K,V>> i =
	    (size > 0) ? entrySet0().iterator() : null;

	// Write out the threshold, loadfactor, and any hidden stuff
	s.defaultWriteObject();

	// Write out number of buckets
	s.writeInt(table.length);

	// Write out size (number of Mappings)
	s.writeInt(size);

        // Write out keys and values (alternating)
	if (i != null) {
	    while (i.hasNext()) {
		Map.Entry<K,V> e = i.next();
		s.writeObject(e.getKey());
		s.writeObject(e.getValue());
	    }
        }
    }

    private static final long serialVersionUID = 362498820763181265L;

    private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
	// Read in the threshold, loadfactor, and any hidden stuff
	s.defaultReadObject();

	// Read in number of buckets and allocate the bucket array;
	int numBuckets = s.readInt();
	table = new Entry[numBuckets];

        init();  // Give subclass a chance to do its thing.

	// Read in size (number of Mappings)
	int size = s.readInt();

	// Read the keys and values, and put the mappings in the HashMap
	for (int i=0; i<size; i++) {
	    K key = (K) s.readObject();
	    V value = (V) s.readObject();
	    putForCreate(key, value);
	}
    }

    // These methods are used when serializing HashSets
    int   capacity()     { return table.length; }
    float loadFactor()   { return loadFactor;   }
}

几点总结

1、首先要清楚HashMap的存储结构,例如以下图所看到的:

图中,紫色部分即代表哈希表。也称为哈希数组,数组的每一个元素都是一个单链表的头节点。链表是用来解决冲突的,假设不同的key映射到了数组的同一位置处,就将其放入单链表中。

2、首先看链表中节点的数据结构:

[java] view
plain
copy

  1. // Entry是单向链表。
  2. // 它是 “HashMap链式存储法”相应的链表。
  3. // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数
  4. static class Entry<K,V> implements Map.Entry<K,V> {
  5. final K key;
  6. V value;
  7. // 指向下一个节点
  8. Entry<K,V> next;
  9. final int hash;
  10. // 构造函数。
  11. // 输入參数包含"哈希值(h)", "键(k)", "值(v)", "下一节点(n)"
  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. }
  18. public final K getKey() {
  19. return key;
  20. }
  21. public final V getValue() {
  22. return value;
  23. }
  24. public final V setValue(V newValue) {
  25. V oldValue = value;
  26. value = newValue;
  27. return oldValue;
  28. }
  29. // 推断两个Entry是否相等
  30. // 若两个Entry的“key”和“value”都相等,则返回true。
  31. // 否则,返回false
  32. public final boolean equals(Object o) {
  33. if (!(o instanceof Map.Entry))
  34. return false;
  35. Map.Entry e = (Map.Entry)o;
  36. Object k1 = getKey();
  37. Object k2 = e.getKey();
  38. if (k1 == k2 || (k1 != null && k1.equals(k2))) {
  39. Object v1 = getValue();
  40. Object v2 = e.getValue();
  41. if (v1 == v2 || (v1 != null && v1.equals(v2)))
  42. return true;
  43. }
  44. return false;
  45. }
  46. // 实现hashCode()
  47. public final int hashCode() {
  48. return (key==null   ? 0 : key.hashCode()) ^
  49. (value==null ? 0 : value.hashCode());
  50. }
  51. public final String toString() {
  52. return getKey() + "=" + getValue();
  53. }
  54. // 当向HashMap中加入元素时。绘调用recordAccess()。
  55. // 这里不做不论什么处理
  56. void recordAccess(HashMap<K,V> m) {
  57. }
  58. // 当从HashMap中删除元素时,绘调用recordRemoval()。
  59. // 这里不做不论什么处理
  60. void recordRemoval(HashMap<K,V> m) {
  61. }
  62. }

它的结构元素除了key、value、hash外,还有next,next指向下一个节点。另外。这里覆写了equals和hashCode方法来保证键值对的独一无二。

3、HashMap共同拥有四个构造方法。构造方法中提到了两个非常重要的參数:初始容量和载入因子。这两个參数是影响HashMap性能的重要參数,当中容量表示哈希表中槽的数量(即哈希数组的长度),初始容量是创建哈希表时的容量(从构造函数中能够看出。假设不指明,则默觉得16)。载入因子是哈希表在其容量自己主动添加之前能够达到多满的一种尺度,当哈希表中的条目数超出了载入因子与当前容量的乘积时,则要对该哈希表进行
resize 操作(即扩容)。

以下说下载入因子。假设载入因子越大,对空间的利用更充分。可是查找效率会减少(链表长度会越来越长)。假设载入因子太小,那么表中的数据将过于稀疏(非常多空间还没用。就開始扩容了),对空间造成严重浪费。假设我们在构造方法中不指定。则系统默认载入因子为0.75,这是一个比較理想的值。普通情况下我们是无需改动的。

另外。不管我们指定的容量为多少,构造方法都会将实际容量设为不小于指定容量的2的次方的一个数,且最大值不能超过2的30次方

4、HashMap中key和value都同意为null。

5、要重点分析下HashMap中用的最多的两个方法put和get。先从比較简单的get方法着手,源代码例如以下:

[java] view
plain
copy

  1. // 获取key相应的value
  2. public V get(Object key) {
  3. if (key == null)
  4. return getForNullKey();
  5. // 获取key的hash值
  6. int hash = hash(key.hashCode());
  7. // 在“该hash值相应的链表”上查找“键值等于key”的元素
  8. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  9. e != null;
  10. e = e.next) {
  11. Object k;
  12. /推断key是否同样
  13. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  14. return e.value;
  15. }
  16. 没找到则返回null
  17. return null;
  18. }
  19. // 获取“key为null”的元素的值
  20. // HashMap将“key为null”的元素存储在table[0]位置,但不一定是该链表的第一个位置!
  21. private V getForNullKey() {
  22. for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  23. if (e.key == null)
  24. return e.value;
  25. }
  26. return null;
  27. }

首先,假设key为null,则直接从哈希表的第一个位置table[0]相应的链表上查找。记住。key为null的键值对永远都放在以table[0]为头结点的链表中,当然不一定是存放在头结点table[0]中。

假设key不为null,则先求的key的hash值,依据hash值找到在table中的索引,在该索引相应的单链表中查找是否有键值对的key与目标key相等,有就返回相应的value,没有则返回null。

put方法略微复杂些,代码例如以下:

[java] view
plain
copy

  1. // 将“key-value”加入到HashMap中
  2. public V put(K key, V value) {
  3. // 若“key为null”,则将该键值对加入到table[0]中。
  4. if (key == null)
  5. return putForNullKey(value);
  6. // 若“key不为null”,则计算该key的哈希值,然后将其加入到该哈希值相应的链表中。
  7. int hash = hash(key.hashCode());
  8. int i = indexFor(hash, table.length);
  9. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  10. Object k;
  11. // 若“该key”相应的键值对已经存在,则用新的value代替旧的value。

    然后退出!
  12. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  13. V oldValue = e.value;
  14. e.value = value;
  15. e.recordAccess(this);
  16. return oldValue;
  17. }
  18. }
  19. // 若“该key”相应的键值对不存在,则将“key-value”加入到table中
  20. modCount++;
  21. //将key-value加入到table[i]处
  22. addEntry(hash, key, value, i);
  23. return null;
  24. }

假设key为null,则将其加入到table[0]相应的链表中。putForNullKey的源代码例如以下:

[java] view
plain
copy

  1. // putForNullKey()的作用是将“key为null”键值对加入到table[0]位置
  2. private V putForNullKey(V value) {
  3. for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  4. if (e.key == null) {
  5. V oldValue = e.value;
  6. e.value = value;
  7. e.recordAccess(this);
  8. return oldValue;
  9. }
  10. }
  11. // 假设没有存在key为null的键值对。则直接题阿见到table[0]处!
  12. modCount++;
  13. addEntry(0, null, value, 0);
  14. return null;
  15. }

假设key不为null,则相同先求出key的hash值,依据hash值得出在table中的索引,而后遍历相应的单链表,假设单链表中存在与目标key相等的键值对,则将新的value覆盖旧的value,比将旧的value返回,假设找不到与目标key相等的键值对,或者该单链表为空,则将该键值对插入到改单链表的头结点位置(每次新插入的节点都是放在头结点的位置),该操作是有addEntry方法实现的,它的源代码例如以下:

[java] view
plain
copy

  1. // 新增Entry。将“key-value”插入指定位置。bucketIndex是位置索引。
  2. void addEntry(int hash, K key, V value, int bucketIndex) {
  3. // 保存“bucketIndex”位置的值到“e”中
  4. Entry<K,V> e = table[bucketIndex];
  5. // 设置“bucketIndex”位置的元素为“新Entry”。
  6. // 设置“e”为“新Entry的下一个节点”
  7. table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
  8. // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小
  9. if (size++ >= threshold)
  10. resize(2 * table.length);
  11. }

注意这里倒数第三行的构造方法,将key-value键值对赋给table[bucketIndex]。并将其next指向元素e,这便将key-value放到了头结点中。并将之前的头结点接在了它的后面。

该方法也说明,每次put键值对的时候,总是将新的该键值对放在table[bucketIndex]处(即头结点处)。

两外注意最后两行代码,每次增加键值对时,都要推断当前已用的槽的数目是否大于等于阀值(容量*载入因子),假设大于等于,则进行扩容。将容量扩为原来容量的2倍。

6、关于扩容。上面我们看到了扩容的方法,resize方法。它的源代码例如以下:

[java] view
plain
copy

  1. // 又一次调整HashMap的大小。newCapacity是调整后的单位
  2. void resize(int newCapacity) {
  3. Entry[] oldTable = table;
  4. int oldCapacity = oldTable.length;
  5. if (oldCapacity == MAXIMUM_CAPACITY) {
  6. threshold = Integer.MAX_VALUE;
  7. return;
  8. }
  9. // 新建一个HashMap,将“旧HashMap”的所有元素加入到“新HashMap”中,
  10. // 然后。将“新HashMap”赋值给“旧HashMap”。
  11. Entry[] newTable = new Entry[newCapacity];
  12. transfer(newTable);
  13. table = newTable;
  14. threshold = (int)(newCapacity * loadFactor);
  15. }

非常明显,是新建了一个HashMap的底层数组。而后调用transfer方法,将就HashMap的所有元素加入到新的HashMap中(要又一次计算元素在新的数组中的索引位置)。transfer方法的源代码例如以下:

[java] view
plain
copy

  1. // 将HashMap中的所有元素都加入到newTable中
  2. void transfer(Entry[] newTable) {
  3. Entry[] src = table;
  4. int newCapacity = newTable.length;
  5. for (int j = 0; j < src.length; j++) {
  6. Entry<K,V> e = src[j];
  7. if (e != null) {
  8. src[j] = null;
  9. do {
  10. Entry<K,V> next = e.next;
  11. int i = indexFor(e.hash, newCapacity);
  12. e.next = newTable[i];
  13. newTable[i] = e;
  14. e = next;
  15. } while (e != null);
  16. }
  17. }
  18. }

非常明显。扩容是一个相当耗时的操作。由于它须要又一次计算这些元素在新的数组中的位置并进行复制处理。因此。我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。

7、注意containsKey方法和containsValue方法。前者直接能够通过key的哈希值将搜索范围定位到指定索引相应的链表。而后者要对哈希数组的每一个链表进行搜索。

8、我们重点来分析下求hash值和索引值的方法,这两个方法便是HashMap设计的最为核心的部分。二者结合能保证哈希表中的元素尽可能均匀地散列。

计算哈希值的方法例如以下:

[java] view
plain
copy

  1. static int hash(int h) {
  2. h ^= (h >>> 20) ^ (h >>> 12);
  3. return h ^ (h >>> 7) ^ (h >>> 4);
  4. }

它仅仅是一个数学公式,JDK这样设计对hash值的计算。自然有它的优点,至于为什么这样设计,我们这里不去追究。仅仅要明确一点,用的位的操作使hash值的计算效率非常高。

由hash值找到相应索引的方法例如以下:

[java] view
plain
copy

  1. static int indexFor(int h, int length) {
  2. return h & (length-1);
  3. }

这个我们要重点说下,我们一般对哈希表的散列非常自然地会想到用hash值对length取模(即除法散列法),Hashtable中也是这样实现的,这样的方法基本能保证元素在哈希表中散列的比較均匀,但取模会用到除法运算,效率非常低,HashMap中则通过h&(length-1)的方法来取代取模,相同实现了均匀的散列,但效率要高非常多,这也是HashMap对Hashtable的一个改进。

接下来,我们分析下为什么哈希表的容量一定要是2的整数次幂。首先,length为2的整数次幂的话。h&(length-1)就相当于对length取模,这样便保证了散列的均匀。同一时候也提升了效率;其次,length为2的整数次幂的话,为偶数。这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便能够保证散列的均匀性,而假设length为奇数的话,非常明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0。即仅仅能为偶数,这样不论什么hash值都仅仅会被散列到数组的偶数下标位置上。这便浪费了近一半的空间。因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。

时间: 2024-10-17 07:29:44

HashMap源代码剖析的相关文章

【Java集合源代码剖析】HashMap源代码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/36034955 您好,我正在參加CSDN博文大赛,假设您喜欢我的文章.希望您能帮我投一票.谢谢! 投票地址:http://vote.blog.csdn.net/Article/Details? articleid=35568011 HashMap简单介绍 HashMap是基于哈希表实现的,每个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,相同会自己

HashMap源代码解析

HashMap原理剖析 之前有看过别人的HashMap源代码的分析,今天尝试自己来分析一波,纯属个人愚见.听一些老的程序员说过,当别人跟你说用某样技术到项目中去,而你按照别人的想法实现了的时候,你只能是一个码农,当你自己会想到用一样东西到你的实际开发中的时候,你是一个普通的程序员,当你不仅能想到用某样技术到项目中去,而且深深的熟悉这项技术的底层实现,那就是一个有内功的程序员了. HashMap,哈希表,基于哈希算法实现的一个java集合.起初使用这个哈希表的时候,感觉非常的不习惯,主要是容易和其

【Java集合源代码剖析】LinkedHashmap源代码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985 前言:有网友建议分析下LinkedHashMap的源代码.于是花了一晚上时间研究了下,分享出此文(这个系列的最后一篇博文了),希望大家相互学习.LinkedHashMap的源代码理解起来也不难(当然.要建立在对HashMap源代码有较好理解的基础上). LinkedHashMap简单介绍 LinkedHashMap是HashMap的子类,与HashMap有着相同的存储结构.

【Java集合源代码剖析】TreeMap源代码剖析

转载请注明出处:http://blog.csdn.net/ns_code/article/details/36421085 前言 本文不打算延续前几篇的风格(对全部的源代码加入凝视),由于要理解透TreeMap的全部源代码.对博主来说.确实须要耗费大量的时间和经历.眼下看来不大可能有这么多时间的投入.故这里意在通过于阅读源代码对TreeMap有个宏观上的把握,并就当中一些方法的实现做比較深入的分析. 红黑树简单介绍 TreeMap是基于红黑树实现的,这里仅仅对红黑树做个简单的介绍,红黑树是一种特

菜鸟nginx源代码剖析数据结构篇(八) 缓冲区链表ngx_chain_t

菜鸟nginx源代码剖析数据结构篇(八) 缓冲区链表 ngx_chain_t Author:Echo Chen(陈斌) Email:[email protected]mail.com Blog:Blog.csdn.net/chen19870707 Date:Nov 6th, 2014 1.缓冲区链表结构ngx_chain_t和ngx_buf_t nginx的缓冲区链表例如以下图所看到的.ngx_chain_t为链表.ngx_buf_t为缓冲区结点: 2.源码位置 头文件:http://trac.

Qt中事件分发源代码剖析(一共8个步骤,顺序非常清楚:全局的事件过滤器,再传递给目标对象的事件过滤器,最终传递给目标对象)

Qt中事件分发源代码剖析 Qt中事件传递顺序: 在一个应该程序中,会进入一个事件循环,接受系统产生的事件,并且进行分发,这些都是在exec中进行的.下面举例说明: 1)首先看看下面一段示例代码: [cpp] view plaincopy int main(int argc, char *argv[]) { QApplication a(argc, argv); MouseEvent w; w.show(); return a.exec(); } 2)a.exec进入事件循环,调用的是QAppli

Android多线程研究(1)——线程基础及源代码剖析

从今天起我们来看一下Android中的多线程的知识,Android入门easy,可是要完毕一个完好的产品却不easy,让我们从线程開始一步步深入Android内部. 一.线程基础回想 package com.maso.test; public class TraditionalThread { public static void main(String[] args) { /* * 线程的第一种创建方式 */ Thread thread1 = new Thread(){ @Override p

《STL源代码剖析》---stl_deque.h阅读笔记(2)

看完,<STL源代码剖析>---stl_deque.h阅读笔记(1)后.再看代码: G++ 2.91.57,cygnus\cygwin-b20\include\g++\stl_deque.h 完整列表 /* * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modify, distribute and sell this software * and its documentation f

菜鸟nginx源代码剖析 配置与部署篇(一) 手把手实现nginx &amp;quot;I love you&amp;quot;

菜鸟nginx源代码剖析 配置与部署篇(一) 手把手配置nginx "I love you" Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:Nov 7th, 2014 还记得在前几年的CSDN泄漏账号事件中.统计发现程序猿的账号中含有love的最多,这里我也俗套下.在这篇文章中将解说怎样 一步一步有用Nginx在一台机器上搭建一个最简单的显示"I love y