欢迎访问昆山宝鼎软件有限公司网站! 设为首页 | 网站地图 | XML | RSS订阅 | 宝鼎邮箱 | 后台管理


新闻资讯

MENU

软件开发知识
原文出处: Snailclimb

本文从 Hash 要领开始,通过阐明源码,深入先容了 JDK 差异版本中 HashMap 的实现。

HashMap 简介

HashMap 主要用来存放键值对,它基于哈希表的Map接话柄现,是常用的Java荟萃之一。

JDK1.8 之前 HashMap 由 数组+链表 构成的,数组是 HashMap 的主体,链表则是主要为了办理哈希斗嘴而存在的(“拉链法”办理斗嘴).JDK1.8 今后在办理哈希斗嘴时有了较大的变革,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以淘汰搜索时间。

底层数据布局阐明

JDK1.8之前

JDK1.8 之前 HashMap 底层是 数组和链表 团结在一起利用也就是 链表散列。HashMap 通过 key 的 hashCode 颠末扰动函数处理惩罚事后获得 hash 值,然后通过 (n - 1) & hash 判定当前元素存放的位置(这里的 n 指的是数组的长度),假如当前位置存在元素的话,就判定该元素与要存入的元素的 hash 值以及 key 是否沟通,假如沟通的话,直接包围,不沟通就通过拉链法办理斗嘴。

所谓扰动函数指的就是 HashMap 的 hash 要领。利用 hash 要领也就是扰动函数是为了防备一些实现较量差的 hashCode() 要领 换句话说利用扰动函数之后可以淘汰碰撞。

JDK 1.8 HashMap 的 hash 要领源码:

JDK 1.8 的 hash要领 对比于 JDK 1.7 hash 要领越发简化,可是道理稳定。

static final int hash(Object key) {
  int h;
  // key.hashCode():返回散列值也就是hashcode
  // ^ :按位异或
  // >>>:无标记右移,忽略标记位,空位都以0补齐
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

比拟一下 JDK1.7的 HashMap 的 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);
}

对比于 JDK1.8 的 hash 要领 ,JDK 1.7 的 hash 要领的机能会稍差一点点,昆山软件开发,因为究竟扰动了 4 次。

所谓 “拉链法” 就是:将链表和数组相团结。也就是说建设一个链表数组,数组中每一格就是一个链表。若碰着哈希斗嘴,则将斗嘴的值加到链表中即可。

假如不是就遍  <a href=苏州软件公司 历链表插入" class="aligncenter size-full wp-image-31097" title="68747470733a2f2f757365722d676f6c642d63646e2e786974752e696f2f323031382f332f32302f313632343064626363333033643837323f773d33343826683d34323726663d706e6726733d3130393931" src="/uploads/allimg/c190103/1546459320B0-1cN.png" />

JDK1.8之后

对比于之前的版本,jdk1.8在办理哈希斗嘴时有了较大的变革,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,昆山软件开发,以淘汰搜索时间。

假如不是就遍  <a href=苏州软件公司 历链表插入" class="aligncenter size-full wp-image-31098" title="687474703a2f2f6d792d626c6f672d746f2d7573652e6f73732d636e2d6265696a696e672e616c6979756e63732e636f6d2f31382d382d32322f36373233333736342e6a7067" src="/uploads/allimg/c190103/1546459320M010-24141.jpg" />

类的属性:

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
    // 序列号
    private static final long serialVersionUID = 362498820763181265L;    
    // 默认的初始容量是16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   
    // 最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30; 
    // 默认的填充因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 当桶(bucket)上的结点数大于这个值时会转成红黑树
    static final int TREEIFY_THRESHOLD = 8; 
    // 当桶(bucket)上的结点数小于这个值时树转链表
    static final int UNTREEIFY_THRESHOLD = 6;
    // 桶中布局转化为红黑树对应的table的最小巨细
    static final int MIN_TREEIFY_CAPACITY = 64;
    // 存储元素的数组,老是2的幂次倍
    transient Node<k,v>[] table; 
    // 存放详细元素的集
    transient Set<map.entry<k,v>> entrySet;
    // 存放元素的个数,留意这个不便是数组的长度。
    transient int size;
    // 每次扩容和变动map布局的计数器
    transient int modCount;   
    // 临界值 当实际巨细(容量*填充因子)高出临界值时,会举办扩容
    int threshold;
    // 填充因子
    final float loadFactor;
}
  • loadFactor加载因子
  • loadFactor加载因子是节制数组存放数据的疏密水平,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,load Factor越小,也就是趋近于0,

    loadFactor太大导致查找元素效率低,太小导致数组的操作率低,存放的数据会很分手。loadFactor的默认值为0.75f是官方给出的一个较量好的临界值。

  • threshold
  • threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要思量对数组的扩增了,也就是说,这个的意思就是 权衡数组是否需要扩增的一个尺度。