HashMap在JDK1.7和1.8主要区别

HashMap在JDK1.7和1.8主要区别

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

首先从声明上来看,HashMap继承自AbstractMap 实现了Map、Cloneable、Serializable接口,点开AbstractMap 源码,发现AbstractMap 也实现了Map接口,那么HashMap为什么继承了AbstractMap 又要实现Map?完全无法解释的通,其实这就是类库设计者的写法错误。可以参考:http://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete回答。

HashMap在不同的JDK版本中底层的数据结构也不同,1.7的是数组+链表的实现方式,而1.8变成了数组+链表+红黑树的数据结构(当链表的长度大于8,转为红黑树)。

1.JDK1.7

简单描述一下HashMap的存值过程:

首先HashMap是数组+链表的数据结构。

  1. 当向HashMap中插入键值对的时候,首先会计算出key的hash值,然后根据hash值插入到数组相应的数组下标处。
  2. 一个数组元素=一个键值对=一个链表的头节点(hash,key,value,next)next表示下一个节点对象。
  3. 当数组下标中有元素的时候,则需要将原元素移动到链表中,冲突hash值对应的键值对放入数组元素中。(这和jdk1.8不同)

在这里插入图片描述

2.JDK1.8

此版本下HashMap的数据结构改变成数组+链表+红黑树(当链表长度大于8时,链表会转换为红黑树实现)。

为什么要引入红黑树呢:当链表的长度太长时,会影响HashMap的查询效率。时间复杂度O(n)。此时利用红黑树快速增删改查的特点将时间复杂度降为O(logn)

在这里插入图片描述

2.1 存储流程

在这里插入图片描述

2.2 实际存储对象

HashMap中数组的元素以及链表节点都是Node类实现与1.7相比(Entry)只不过是换了名字

/** 
  * Node  = HashMap的内部类,实现了Map.Entry接口,本质是 = 一个映射(键值对)
  * 实现了getKey()、getValue()、equals(Object o)和hashCode()等方法
  **/  

  static class Node<K,V> implements Map.Entry<K,V> {

        final int hash; // 哈希值,HashMap根据该值确定记录的位置
        final K key; // key
        V value; // value
        Node<K,V> next;// 链表下一个节点

        // 构造方法
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

      /** 
        * equals()
        * 作用:判断2个Entry是否相等,必须key和value都相等,才返回true  
        */
        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;
        }
    }

2.3 相关参数

 /** 
   * 主要参数 同  JDK 1.7 
   * 即:容量、加载因子、扩容阈值(要求、范围均相同)
   */

  // 1. 容量(capacity): 必须是2的幂 & <最大容量(2的30次方)
  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认容量 = 16 = 1<<4 = 00001中的1向左移4位 = 10000 = 十进制的2^4=16
  static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量 =  2的30次方(若传入的容量过大,将被最大值替换)

  // 2. 加载因子(Load factor):HashMap在其容量自动增加前可达到多满的一种尺度 
  final float loadFactor; // 实际加载因子
  static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认加载因子 = 0.75

  // 3. 扩容阈值(threshold):当哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表(即扩充HashMap的容量) 
  // a. 扩容 = 对哈希表进行resize操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数
  // b. 扩容阈值 = 容量 x 加载因子
  int threshold;

  // 4. 其他
  transient Node<K,V>[] table;  // 存储数据的Node类型 数组,长度 = 2的幂;数组的每个元素 = 1个单链表
  transient int size;// HashMap的大小,即 HashMap中存储的键值对的数量
 

  /** 
   * 与红黑树相关的参数
   */
   // 1. 桶的树化阈值:即 链表转成红黑树的阈值,在存储数据时,当链表长度 > 该值时,则将链表转换成红黑树
   static final int TREEIFY_THRESHOLD = 8; 
   // 2. 桶的链表还原阈值:即 红黑树转为链表的阈值,当在扩容(resize())时(此时HashMap的数据存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内数量 < 6时,则将 红黑树转换成链表
   static final int UNTREEIFY_THRESHOLD = 6;
   // 3. 最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)
   // 否则,若桶内元素太多时,则直接扩容,而不是树形化
   // 为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
   static final int MIN_TREEIFY_CAPACITY = 64;
  

2.4 加载因子

在这里插入图片描述

3.总结

  1. 底层的数据结构不一样:1.7数据结构为数组+链表的实现方式;1.8数据结构为数组+链表+红黑树的实现方式。
  2. JDK1.8中resize()方法在表为空时,创建表;在表不为空时,扩容;而JDK1.7resize()方法只负责扩容,inflateTable()负责创建表。
  3. 1.7新增节点是采用头插法,而1.8是采用尾插法
  4. 在扩容的时候:1.7在插入数据之前扩容,而1.8插入数据成功之后扩容。

在这里插入图片描述
图片转载自:https://blog.csdn.net/carson_ho/article/details/79373134

关于HashMap的源码详细分析:https://blog.csdn.net/carson_ho/article/details/79373134

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章