HashMap源码解析

HashMap根据key的hashcode值存储数据,大多数情况下可以直接定位到值,因而具有很快的访问速度,但遍历顺讯确实不确定的。HashMap最多允许一条记录的key为null,允许多条记录的value为null。同时HashMap是非线程安全的,即任一时刻可以有多个线程同时写HashMap,可能会导致数据不一致。




从结构上讲,HashMap是数组+链表的结合体,每一个键值对都被包装为Entry类。当执行put(key,value)的方法时,通过计算当前key的哈希值来决定该键值对在table数组中的下标i。如果table[ i ]为空,则直接插入;如果不为空,循环遍历table[ i ]的链表,如果有键值对的key与当前key相同,直接覆盖该键值对;如果没有相等的键值对,将当前键值对添加至链表尾部。

1、put实现
public V put(K key, V value) {
    /*  如果key值为null,调用putForNullKey,不同版本该方法的实现也不同,
        1.该方法将从头遍历table数组,将key为null的键值对放在数组中第一个为空的地方。
        2.用中间变量标记null 键值对。*/
    if (key == null)
        return putForNullKey(value);
    //key不为null时,计算当前key的hash值。
    int hash = hash(key.hashCode());
    //计算该hash值对应的数组下标i
    int i = indexFor(hash, table.length);
    /*  遍历table[i]中的Entry链表,如果不存在hash值相同的键值对,直接插入数组,
        如果存在hash值相同的键值对,比较两个键值对的key,如果key相同,用当前键值对覆盖此键值对,
        如果key不同,直接插入。
     */
    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;
}





2、hash()、indexFor()实现——确定键值对在table中的索引
我们希望HashMap中的元素位置尽量分布均匀,尽量使得每个位置上的元素数量只有一个,那么当用hash算法求得这个位置时,马上就可以知道对应位置的元素就是目标元素,不用遍历链表,大大优化了查询效率。HashMap定位数组索引位置,直接决定了hash方法的离散性能。
 static final int hash(Object key) {   //jdk1.8 & jdk1.7
      int h;
      // h = key.hashCode() 为第一步 取hashCode值
      // h ^ (h >>> 16)  为第二步 高位参与运算 为了让高低位都充分参与运算
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }


    static int indexFor(int h, int length) {  //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的
        return h & (length - 1);  //第三步 取模运算 h按位与上length - 1等价于h对length取模,使得元素分布均匀  
    }






3、resize()实现——table的扩容机制
table的默认初始长度为16,当无法装在更多元素时,就需要扩大数组长度调用resize方法。
由于在进行扩容时,是用更大容量的数组代替之前的小容量数组,并且会对原来的键值对重新计算排序,特别耗性能,所以在创建HashMap的时候最好把大概容量写上,避免进行扩容操作。另外,之所以hashmap不支持多线程操作,是因为resize扩容是在把小容量table的数据转移至大容量数组时,会让数组错位造成调用transfer时造成死循环。

   void resize(int newCapacity) {   //传入新的容量
        Entry[] oldTable = table;    //引用扩容前的Entry数组
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {  //扩容前的数组大小如果已经达到最大(2^30)了
            threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
            return;
        }

        Entry[] newTable = new Entry[newCapacity];  //初始化一个新的Entry数组
        transfer(newTable);                         //!!将数据转移到新的Entry数组里
        table = newTable;                           //HashMap的table属性引用新的Entry数组
        threshold = (int) (newCapacity * loadFactor);//修改阈值
    }


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值