HashMap 源码笔记

1 hashMap其key空间的长度一定为2的N次方.

 

2加快Hash效率的另一个有效途径是编写良好的自定义对象的HashCode,String的实现采用了如下的计算方法

for (int i = 0; i < len; i++) {

h = 31*h + val[off++];

 

}

hash = h;

这种方法HashCode的计算方法可能最早出现在Brian W. Kernighan和Dennis M. Ritchie的《The C Programming Language》中,被认为是性价比最高的算法(又被称为times33算法,因为C中乘数常量为33,JAVA中改为31),实际上,包括List在内的大多数的对象都是用这种方法计算Hash值。

 

3为了将各元素的hashCode保存至长度为Length的key数组中,一般采用取模的方式,即index = hashCode % Length。不可避免的,存在多个不同对象的hashCode被安排在同一位置,这就是我们平时所谓的“冲突”。如果仅仅是考虑元素均匀化与冲突极小化,似乎应该将Length取为素数(尽管没有明显的理论来支持这一点,但数学家们通过大量的实践得出结论,对素数取模的产生结果的无关性要大于其它数字)。为此,Craig Larman and Rhett Guthrie《Java Performence》中对此也大加抨击。为了弄清楚这个问题,Bruce Eckel(Thinking in JAVA的作者)专程采访了java.util.hashMap的作者Joshua Bloch,并将他采用这种设计的原因放到了网上

(http://www.roseindia.net/javatutorials/javahashmap.shtml) 。

 

上述设计的原因在于,取模运算在包括Java在内的大多数语言中的效率都十分低下,而当除数为2的N次方时,取模运算将退化为最简单的位运算,其效率明显提升(按照Bruce Eckel给出的数据,大约可以提升58倍)

最主要的原因是:rehash时只需要移动50%的数据。

 

4 Fail-Fast机制:

 

   我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

 这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,HashMap内容的修改都将增加这个值(即使是从map中移除数据),那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。

 

在HashMap的API中指出:

 

   由所有HashMap类的“collection视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果

从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将

抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来

不确定的时间发生任意不确定行为的风险。

 

 

 

5 key 可以是null ,getForNullKey()

 

 

6 判断key是否相等

 

if(e.hash == hash && ((k=e.key) == key|| key.equals(k))

 

if(k1 ==k2 || (k1 !=null &&k1.equals(k2))

 

7 putForCreate()

 

8 HashIterator 的构造函数

 

//fail-fast策略的初始化

expectedModCount = modCount;

 

//next 指向下一个元素

while(index < t.length && (next= t[index++]) ==null)

;

 

 

 

 

只是一遍一遍地重复是不够的,必须挑战恰好超越你能限的事情,尝试并思考你的表现,并自我矫正。

 


Entry 条目

 在引入Entry之前的标准的Map的遍历方法如下:

Set keys = map.keySet( );

if(keys != null) {

    Iterator iterator = keys.iterator( );

    while(iterator.hasNext( )) {

        Object key = iterator.next( );

        Object value = map.get(key);

   }

 

}

 

每次都要根据key  到Map中找 对应的值,低效。

 

引入以后

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

for (Map.Entry<Integer, Integer> entry : map.entrySet()) {

System.out.println(“Key = ” + entry.getKey() + “, Value = ” + entry.getValue());

}

或者

Map<Integer, Integer> map = new HashMap<Integer, Integer>();

Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();

while (entries.hasNext()) {

Map.Entry<Integer, Integer> entry = entries.next();

System.out.println(“Key = ” + entry.getKey() + “, Value = ” + entry.getValue());

}

每次都是将key-value 一起取出来,效率明显提高了。

 

 

 

  /**

     * The default initial capacity- MUST be a power of two.

     */

staticfinalintDEFAULT_INITIAL_CAPACITY = 16;

 

 

/**

     * The load factor used when none specified in constructor.

     */

    staticfinalfloatDEFAULT_LOAD_FACTOR = 0.75f;

 

    /**

     * The table, resized as necessary. Length MUST Always be a power of two.

* table就是 hash函数拉链法中的数组。显然Entry就是它要装的元素(键值对)。

     */

transient Entry[]table;

 

 

    /**

     * The number of key-value mappings contained in this map.

    * 这个table中实际装了多少元素

     */

    transientintsize;

 

    /**

     * The next size value at which to resize (capacity * load factor).

     *

     */

    intthreshold;

 

 

  /**

     *

     *不等装满就扩充容量了,这样能使冲突尽量减少,基本能保证在O1)能查找到所需元

     */

 

        if (size++ >=threshold)

            resize(2 *table.length);

 

 

    void resize(intnewCapacity) {

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

    }

  

 

 

public Object put(Object key, Object value) {
  //我们的内部数组是一个 Entry 对象数组
  //Entry[] table;
 
  //获取哈希码,并映射到一个索引
  int hash = key.hashCode();
  int index = (hash & 0x7FFFFFFF) % table.length;
 
  //冲突处理,不同的键对象可能拥有相同的哈希,也可能是相同的键对象的修改
   for (Entry e = table[index] ; e != null ; e = e.next) {
    //必须检查键是否相等,原因是不同的键对象可能拥有相同的哈希
 
    if ((e.hash == hash) && e.key.equals(key)) {
      假如放入的key是已经存在的,就替换原来的值
      Object old = e.value;
      e.value = value;
      return old;
    }
  }
 
   
  //创建一个指向上一个列表开头的新 Entry
  //用头插法建立的, 即先插入的在上面
  Entry e = new Entry(hash, key, value, table[index]);
  table[index] = e;
 
  return null;
}

 还有一点要注意的是,HashMap对key进行hash时,不是取的key的key.hashCode()方法,而是对key的hashcode作一些运算得到最后的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);
    }

 

这是因为 在后面的table检索中,使用的函数为

staticintindexFor(inth,int length)

 {returnh & (length-1);}

 

如果直接使用 key.hashcode 其除去低length-1位后的部分不会对key在table中的位置产生任何影响,这样只要保持低length-1位不变,

不管高位如何都会冲突,所以就想办法使得高位对其结果也产生影响

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值