HashMap:put与resize

简介

HashMap 一直是面试考察的重点,在JDK1.8中,HashMap的底层进行了优化,引入了红黑树的数据结构,并且对扩容机制进行了优化。本文将讲解 HashMap中的Node,resize(),以及put方法;

继承关系

HashMap继承自接口 java.util.Map,同样继承自该接口的还有Hashtable(遗留类,且线程安全)、LinkedHashMap(HashMap的子类,可以保存插入顺序)和TreeMap(可以根据存入的的键值进行排序)。

HashMap中的关键字段

    

     int threshold;              
     final float loadFactor;    
     int modCount;  
     int size;

threshold:键值对的极限容量

size:HashMap中现存键值对的数量

loadFactor:负载因子,默认值为0.75

modCount:记录当前集合被修改的次数。

HashMap的默认初始化长度 Node[ ] = 16;

这几个值涉及到HashMap的扩容机制,即 threshold = LoadFactor * Node[].length;

也就是说,Node[ ]的长度越大,LoadFactor越大,那么所能容纳的键值对就越多。

如果存储的键值对超出了这个数据,就会调用resize()方法进行扩容。

Node与put

Node的jdk1.8源码:

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;//  用来定位数组的位置
        final K key;
        V value;
        Node<K,V> next; // 链表结构,指向下一个node

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

Node[是HashMap的内部类,该内部类实现了Map.Entry接口。

运行put方法后系统执行的操作

map.put(“key”,“value”);

1.系统将调用”key“的hashcode方法,得到他的hashcode值(这也是每个类都必须定义hashcode方法的原因),然后通过Hash算法的高位运算与取模运算来得到该键值对存储的数组下标。

2.如果两个“key”定位到了相同的存储位置,此时就会判断二者的key值是否相同(equal()方法);

3.若相同,则覆盖”key“对应的value值。

若仍不相同,在jdk8中,就会判断这个map是否已经转化为红黑树结构,如果时,就直接插入数值

4.若还不是,就说明此时Node数组还是链表结构,那么就会遍历数组,判断链表的长度是否大于8,如果大于8,就将其转化为红黑树。小于8,则进行插入操作,如果在遍历过程中发现key已经存在,就直接将对应的value值覆盖。

5.插入成功后,还要判断map的size值是否超过了最大容量threshold,如果超过,就会调用resize()方法进行扩容,扩容的默认长度是初始值的两倍。

resize()扩容机制

当HashMap中的size超过threshold时,就会调用这个方法。

 1 void resize(int newCapacity) {  //新的容量
 2     Entry[] oldTable = table;    //存放之前的entry数组
 3     int oldCapacity = oldTable.length;         
 4     if (oldCapacity == MAXIMUM_CAPACITY) { 
 5         threshold = Integer.MAX_VALUE; 
 6         return;
 7     }
 8  
 9     Entry[] newTable = new Entry[newCapacity];  //初始化新的 Entry 数组
10     transfer(newTable);                         //将数据转移到新的Entry数组里
11     table = newTable;                           //HashMap的table属性引用新的Entry数组
12     threshold = (int)(newCapacity * loadFactor);//修改threshold的阈值
13 }

此处源码为JDK1.7的源码,因为JDK8引入了新的红黑树过于复杂。此处红黑树暂且不谈。

其中的transfer()方法如下:

void transfer(Entry[] newTable) {
 2     Entry[] src = table;                   
 3     int newCapacity = newTable.length;
 4     for (int j = 0; j < src.length; j++) { 
 5         Entry<K,V> e = src[j];             
 6         if (e != null) {
 7             src[j] = null;
 8             do {
 9                 Entry<K,V> next = e.next;
10                 int i = indexFor(e.hash, newCapacity); 
11                 e.next = newTable[i]; 
12                 newTable[i] = e;      
13                 e = next;           
14             } while (e != null);
15         }
16     }
17 }

具体过程为

1.先把旧的table数组传入src之中,再遍历src中的每一个值,将其取出。

2.如果src[j]的值不为空,就将src[j]的引用置为空,释放空间。

3.indexfor()方法,用于重新计算每个值在数组中的位置。

4.将旧链表的元素转移到新的链表之中,此处使用的是头插法。jdk1.8中则采用了尾插法。

JDK1.7 和 1.8的不同

1.底层,JDK1.7的底层采用了数组+链表的形式,JDK1.8则采用了数组+链表+红黑树的形式。

2.效率,通过底层的改进,在Hash分布均衡的情况下相差不大,但是在Hash分布不平衡的情况下,越不平衡,JDK1.8的红黑树所带来的效率提升就越明显。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值