`
cjsmq
  • 浏览: 14276 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

java Map HashMap

阅读更多
HashMap 是以key-value来存储的数据结构。
底层的实现是:entry类型的数组。将key-value封装成entry对象。对于这种数据结构我们也称之为 散列链表。

HashMap 定义源码如下:
public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{

    /**
     * The default initial capacity - MUST be a power of two.
     */
    //初始最大容量 必须是2的次幂
    static final int DEFAULT_INITIAL_CAPACITY = 16;

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     **/
    //负载因子 表示散表的装满程度。
    //如HashMap 的size>最大容量 * 0.75;表示HashMap实际容量已满,需要扩容
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
   //即是HashMap底层实现: 是以Entry 类型的数组;
    transient Entry[] table;

    /**
     * The number of key-value mappings contained in this identity hash map.
     */
    //存储的有效数据 key-value(会被封装成Entry对象) 个数
    transient int size;
  
    /**
     * The next size value at which to resize (capacity * load factor).
     * @serial
     */
    //该变量定义作为 该Map是否扩容的依据 ;即是实际容量。
     //threshold = 最大容量*负载因子;
     //如果size > threshold ;容器将会被扩容2倍,下面会讲到扩容。
    int threshold;
 


HashMap 的构造方法:
   
  //无参构造函数
   public HashMap() {
        //默认负载因子 0.75
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        //实际容量 16*0.75
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        //Entry类型的数组 
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
     }


再来看看 内部定义Entry对象:

    
static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;  //hashMap key
        V value;      //hashMap value
        final int hash;  //通过对 key.hashCode()的值进行hash运算得出的值
        Entry<K,V> next; //下一entry对象的引用。



先看下HashMap的数据结构图:



来看看HashMap put方法:


    
 public V put(K key, V value) {
        if (key == null)
             //对key==null时处理
	    return putForNullKey(value);
        // 对key的hashCode值进行hash运算
        int hash = hash(key.hashCode());
        // 得到的hash值与数组长度进行 & (位) 运算
         // 得到的i即是数组中对应的下标位置
        int i = indexFor(hash, table.length);
        //对该entry数组下标为i的位置上entry对象的链表,进行遍历。
         //如若存在该数据 则替换原先的value。
        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;
    }
    
    //key==null的处理,定义一个常量NULL_KEY的Object对象,对它进行hash运算。
    private V putForNullKey(V value) {
        int hash = hash(NULL_KEY.hashCode());
        int i = indexFor(hash, table.length);

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            if (e.key == NULL_KEY) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, (K) NULL_KEY, value, i);
        return null;
    }
    
    //hash运算方法
     static int hash(int h) {
       return useNewHash ? newHash(h) : oldHash(h);
    }

    //具体的hash算法
    private static int oldHash(int h) {
        h += ~(h << 9);
        h ^=  (h >>> 14);
        h +=  (h << 4);
        h ^=  (h >>> 10);
        return h;
    }
    
    //hash 与 数组长度 & (位)运算。得到数组对应下标的位置
    static int indexFor(int h, int length) {
        return h & (length-1);
    }

    void addEntry(int hash, K key, V value, int bucketIndex) {
        //提取原先数组下标下的entry对象
         //如果该数组下标下没有entry,则e==null
        Entry<K,V> e = table[bucketIndex];
        //更改数组下标引用至新的entry对象
         //并将新的entry对象的next指向数组下标原先的entry对象
         //如若该数组下标不存在entry,则next引用为空。
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        //鉴于查找效率的考虑,HashMap 的size > 实际容量时,则扩容。
        if (size++ >= threshold)
            //entry数组扩容2倍。
            resize(2 * table.length);
    }
    
    //entry数组扩容2倍
    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];
        //数组扩容后,所有entry元素需要重新计算数组下标位置。
         //生成新的散列链表
         transfer(newTable);
        table = newTable;
        //重新计算实际容量
        threshold = (int)(newCapacity * loadFactor);
    }
    
    //遍历hashMap下所有entry 通过 indexFor 方法重新计算数组位置。
    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);
            }
        }
    }



HashMap get方法:

  
   //get方法与put方法中处理是否存在该数据是类似的。
   public V get(Object key) {
	if (key == null)
             // key==null的处理
	    return getForNullKey();
        int hash = hash(key.hashCode());
        //hash 、indexFor方法确定数组位置上entry的链表,进行遍历。
        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;
    }
  • 大小: 62.3 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics