JDK 1.5 HashMap 源代码读解

 HashMap
基于哈希表的 Map 接口的实现。
此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。
(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)
此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
(from JDK DOC)

 

缺省的 Map大小是 16. (这里要求必须是 2 的幂数,为什么一定是 2的幂数(16,32,64,128...下边有解释).
 static final int DEFAULT_INITIAL_CAPACITY = 16;

 

 缺省的比例因子是 0.75
 static final float DEFAULT_LOAD_FACTOR = 0.75f;


 长度必须总是 2 的 幂数。
 保存 K,V 对象的数组
 transient Entry[] table;

     /**
      * The next size value at which to resize (capacity * load factor).
      * @serial
      */
 重设 Entry[] table 的阀值( = capacity * load factor) 
 int threshold;


 当前比例因子
 final float loadFactor;


 记录修改次数  
 transient volatile int modCount;

 

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity The initial capacity.
     * @param  loadFactor      The load factor.
     * @throws IllegalArgumentException if the initial capacity is negative
            or the load factor is nonpositive.
     */
    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); loadFactor 默认是 0.75. 所以 threshold 默认是 12
        table = new Entry[capacity]; 按照给定的大小初化 Entry 数组。(默认情况下是  16 )
        init();
    }
 
  
 
    看了下边这几行代码就能看出来 HashMap是怎么支持 null 做为 Key了。
    static final Object NULL_KEY = new Object();
    static <T> T maskNull(T key) {
        return key == null ? (T)NULL_KEY : key;
    }
    static <T> T unmaskNull(T key) {
        return (key == NULL_KEY ? null : key);
    }

   
    HashMap 对key 值的  hash 算法。
    Object 类使用的 对象的 内部地址 提供 hashCode.
    String 使用一个算法提供hashCode。(算法:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1])
    当然这样一种算法是不能完全保证 得到的hashCode是唯一的。
   
    String s1 = "结算";
    String s2 = "罚款";
    两个对象的hashcode是相同的(1038116);
    经过hash之后是-423929781.
   
    这个函数的算法我就看不大懂了。还有高人指教了。
 
    static int hash(Object x) {
        int h = x.hashCode();
        h += ~(h << 9);
        h ^=  (h >>> 14);
        h +=  (h << 4);
        h ^=  (h >>> 10);
        return h;
    }
   
    根据hashCode值 值和 当前 Entry数组的长度计算 该数据应该在数组中的索引值
    因为这里的算法 所以 capacity 大小必须为 2 的幂数。
    举例,默认值是 16 , 其减1是 15, 二进制是 00001111 .
    h 是一个特殊的 hash值。 与  0x0F进行 & 操作当然结果应该在 0x0F以内了。
    但这里必须保证 length -1 全为 1. 否则话某些位可能永远是 0,那么Entry数组
    的一个范围可能永远也不会被用到。
   
    这里没使用取余计算应该出于效率考虑的吧。
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
   
   
    public V get(Object key) {
        Object k = maskNull(key);    //检查是否是null Key
        int hash = hash(k);          //计算hash
        int i = indexFor(hash, table.length); //计算索引
        Entry<K,V> e = table[i];              //取得Entry对象
        while (true) {                        //循环查找链上 对象
            if (e == null)
                return null;
            if (e.hash == hash && eq(k, e.key))
                return e.value;
            e = e.next;
        }
    }

    检查是否包含指定key的过程 与 取得这个key
    所对应 value的过程基本一致。
    看了这段代码之后发觉以前有一些不好的写代码习惯:
     if(!containsKey(key)){
       add();
     }
     v = get(key)
            -----------------------
            看来完全可以(这样的代码应该校率更高一些):
            v = get(key)
            if( null == v){
               add();
            }

    public boolean containsKey(Object key) {
        Object k = maskNull(key);
        int hash = hash(k);
        int i = indexFor(hash, table.length);
        Entry e = table[i];
        while (e != null) {
            if (e.hash == hash && eq(k, e.key))
                return true;
            e = e.next;
        }
        return false;
    }
   
   
   
   
    public V put(K key, V value) {
 K k = maskNull(key);                              //检查是否是null Key
        int hash = hash(k);                               //计算hash
        int i = indexFor(hash, table.length);             //计算索引

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {//已经存在key的情况下处理方式
            if (e.hash == hash && eq(k, e.key)) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);                     //一个空操作的方法,不用理他
                return oldValue;
            }
        }
 
        modCount++;                                       //记录修改次数
        addEntry(hash, k, value, i);                      //不存在对应key时添加 K-V
        return null;
    }
   
   
    //从下边代码可以看出,最新加入的 Entry 一定会在内部链表的最前边。
    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)        //如果当前的size 达到了 threshold(缺省参数12) 将会重设 整个 内部 table.length
            resize(2 * table.length);
    }

    Entry的构造方法:
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;
        key = k;
        hash = h;
    }
  
  
  
  
   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);  //计算新的阀值
    }
    
    将所有的 Entry 对象进行重新 hash计算/索引计算放置到 新的 table中。
    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);
            }
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值