HashMap源码解析

来源:互联网 发布:maya软件视频教程 编辑:程序博客网 时间:2024/05/22 10:39
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);//修改阈值    }


原创粉丝点击