HashMap jdk1.7和1.8概述

大家好,我是烤鸭:
这是一篇关于HashMap的概述和底层原理的介绍。算是网上很多帖子的综合和我自己的一点想法。
HashMap在jdk1.8以前是数组+链表。

在jdk1.8以后是数组+链表+红黑树。一点点分析数据结构。


1. Map中的entry对象:

static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V 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;}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;}}

2.load-factor负载因子和capacity容量

简单说一下存储就是node中key计算的hash值决定存储在数组中的位置的bucket(桶)。

如果hash值一样,数组中该位置的bucket(桶)里就会变成链表。
在jdk1.8,链表的长度如果>8,就会变成红黑树。

与HashMap实例相关的参数常用的有两个,load-factor负载因子和capacity容量。
简单解释一下两个参数:
loadFactor 就是创建hashMap什么时间扩容。举个例子来说:

默认new一个HashMap的capacity:16,loadFactor:0.75。

/*** Constructs an empty <tt>HashMap</tt> with the default initial capacity* (16) and the default load factor (0.75).*/public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}/*** The load factor used when none specified in constructor.*/static final float DEFAULT_LOAD_FACTOR = 0.75f;
16*0.75 = 12;
    也就是当Map的大小达到12的时候,开始扩容。

    reSize方法:

/*** Initializes or doubles table size.  If null, allocates in* accord with initial capacity target held in field threshold.* Otherwise, because we are using power-of-two expansion, the* elements from each bin must either stay at same index, or move* with a power of two offset in the new table.** @return the table*/final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;int newCap, newThr = 0;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr;@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {oldTab[j] = null;if (e.next == null)newTab[e.hash & (newCap - 1)] = e;else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}

看这句:

else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
就知道,每次扩容都是之前的2倍。第一次的大小是16,扩容后就变成32。
这里注意一下1.7和1.8的变化;
先说一下1.7的源码:

转自:http://blog.csdn.net/yimi099/article/details/62043566

3. 1.7源码

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);//修改阈值  
} void transfer(Entry[] newTable) {  Entry[] src = table;                   //src引用了旧的Entry数组  int newCapacity = newTable.length;  for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组  Entry<K, V> e = src[j];             //取得旧Entry数组的每个元素  if (e != null) {  src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)  do {  Entry<K, V> next = e.next;  int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置  e.next = newTable[i]; //标记[1]  newTable[i] = e;      //将元素放在数组上  e = next;             //访问下一个Entry链上的元素  } while (e != null);  }  }  
}  

1.7里是每次扩容都去计算元素的hash值,从而改变该元素在数组中的位置。capacity变了,位置自然就要改变。

1.8做了优化:

do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}

解释一下什么意思。

转自:http://blog.csdn.net/brycegao321/article/details/52527236/

(e.hash & oldCap) == 0写的很赞!!! 它将原来的链表数据散列到2个下标位置,  概率是当前位置50%,高位位置50%。     你可能有点懵比, 下面举例说明。  上边图中第0个下标有496和896,  假设它俩的hashcode(int型,占4个字节)是

resize前:
496的hashcode: 00000000  00000000  00000000  00000000
896的hashcode: 01010000  01100000  10000000  00100000
oldCap是16:       00000000  00000000  00000000  00010000

496和896对应的e.hash & oldCap的值为0, 即下标都是第0个。

resize后:
496的hashcode: 00000000  00000000  00000000  00000000
896的hashcode: 01010000  01100000  10000000  00100000
oldCap是32:       00000000  00000000  00000000  00100000

496和896对应的e.hash & oldCap的值为0和1, 即下标都是第0个和第16个。

因为hashcode的第n位是0/1的概率相同, 理论上链表的数据会均匀分布到当前下标或高位数组对应下标。

再说一下其他参数:
bucket桶:
数组中每一个位置上都放有一个桶,每个桶里就是装一个链表,链表中可以有很多个元素(entry),这就是桶的意思。也就相当于把元素都放在桶中。
size:

HashMap的实例中实际存储的元素的个数。

4. threshold:

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

/*** The bin count threshold for using a tree rather than list for a* bin.  Bins are converted to trees when adding an element to a* bin with at least this many nodes. The value must be greater* than 2 and should be at least 8 to mesh with assumptions in* tree removal about conversion back to plain bins upon* shrinkage.*/
static final int TREEIFY_THRESHOLD = 8;
对于一个桶(容器)来说,桶的统计临界值比起list集合更用于树。
当向有多很节点的桶添加一个元素的时候,桶转换成树。这个很多节点就是8。
再说下扩容的过程:

是否扩容主要看:threshold这个参数,threshold = capacity*loadFactor,初始值是threshold = 16*0.75=12,第一次扩容capacity=capacity*2=32,threshold =threshold *2=24。

5. 1.8源码的put方法

再说一下put方法:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;   //初始化桶,默认16个元素if ((p = tab[i = (n - 1) & hash]) == null)   //如果第i个桶为空,创建Node实例tab[i] = newNode(hash, key, value, null);else { //哈希碰撞的情况, 即(n-1)&hash相等Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;   //key相同,后面会覆盖valueelse if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  //红黑树添加当前nodeelse {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);  //链表添加当前元素if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);  //当链表个数大于等于7时,将链表改造为红黑树break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;            //覆盖key相同的value并return, 即不会执行++size}}++modCount;if (++size > threshold)    //key不相同时,每次插入一条数据自增1. 当size大于threshold时resizeresize();afterNodeInsertion(evict);return null;
}
提一个新的名词,哈希碰撞。
如果hashMap的key和key的hashCode找到数组中同一个位置,就是哈希碰撞。

哈希碰撞是产生链表的原因。

最后!!!!

1,HashMap的初始容量是16个, 而且容量只能是2的幂。  每次扩容时都是变成原来的2倍。
2,默认的负载因子是0.75f,threshold:16*0.75=12。即默认的HashMap实例在插入第13个数据时,会扩容为32。
3,JDK1.8对HashMap的优化, 哈希碰撞后的链表上达到8个节点时要将链表重构为红黑树,  查询的时间复杂度变为O(logN)。
4,通常hashMap查询的时间复杂度是O(N),1.8以后红黑树的查询的时间复杂度是O(logN)。极少数情况不会出现哈希碰撞,那是数组,查询的时间复杂度是O(1)。
5,初始化数组或者扩容为2倍,初值为空时,则根据初始容量开辟空间来创建数组。否则, 因为我们使用2的幂定义数组大小,数据要么待在原来的下标, 或者移动到新数组的高位下标。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/413006.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot整合redis修改分区

转载的地址&#xff1a;https://blog.csdn.net/m0_37659871/article/details/81024068#commentBox springboot整合redis修改分区 问题由来 最近使用springboot整合redis&#xff0c;一个系统动态数据源连接不同数据库&#xff0c;缓存使用的redis&#xff0c;那么就需要将不同…

[css] 你是怎么设计css sprites(精灵图)的?有哪些技巧?

[css] 你是怎么设计css sprites&#xff08;精灵图&#xff09;的&#xff1f;有哪些技巧&#xff1f; 首先肯定不会去用PS量&#xff0c;那太费时间了~ 没有webpack以前&#xff0c;用Gulp的gulp.spritesmith插件&#xff0c;这里附上配置源码/* gulpfile.js */ const gulp …

iOS沙盒文件夹及获取路劲方法

iPhone沙盒中有四个文件夹&#xff0c;分别是&#xff1a;documents、tmp、app、library. 1、Documents &#xff1a;用户生成的文档或数据&#xff0c;或者应用不能重新新创建的数据&#xff0c;存储在/Documents目录下&#xff0c;并且会被自动备份到iCloud&#xff1b; 2、A…

springboot多环境加载yml和logback配置

大家好&#xff0c;我是烤鸭&#xff1a;这是一篇关于springboot多环境加载yml和logback配置文件。环境&#xff1a;开发工具 idea(推荐)/eclipse(对yml支持不好)jdk 1.8springboot 1.5.6.RELEASE 1. yml和logback文件1.1 结构,如图所示&#xff1a;1.2 application.yml (默…

[css] 请描述下你对translate()方法的理解

[css] 请描述下你对translate()方法的理解 Single length/percentage value一个长度值或百分比表示X轴和Y轴使用一样的值进行二维上的平移。等同于translate() &#xff08;2D 平移&#xff09;函数指定单个值。Two length/percentage values两个长度值或百分比表示在二维上分…

laydate闪退

1,解决方案一&#xff1a;加 trigger: ‘click’ laydate.render({ elem: this ,format:‘yyyy-MM-dd HH:mm:ss’ ,type:‘datetime’ ,trigger: click’ }); 解决方案二&#xff1a; $("#"dateControlId).removeAttr(“lay-key”);

汇编实验二

》实验结论 1.使用Debug将下面的程序写入内存&#xff0c;逐条执行&#xff08;见1-1&#xff09;&#xff0c;根据指令执行后的实际情况填空&#xff08;见1-2&#xff09; p.s. 已经按实验要求将使用 e 命令将内存单元 0021:0 ~0021:7 连续 8 个字节数据修改为 30H, 31H, 32H…

springboot中的拦截器interceptor和过滤器filter,多次获取request参数

大家好&#xff0c;我是烤鸭&#xff1a; 这是一篇关于springboot的拦截器(interceptor)和过滤器(Filter)。 先说一下过滤器和拦截器。区别&#xff1a;1. servlet请求&#xff0c;顺序&#xff1a;Filter ——> interceptor。2. Filter的作用是对所有进行过滤&#xff…

[css] 怎样去除图片自带的边距?

[css] 怎样去除图片自带的边距&#xff1f; 空隙产生的原因&#xff0c;换行符&#xff0c;空格符&#xff0c;制表符等你空白符&#xff0c;字体不为0的情况下&#xff0c;都会产生一个字符的空隙&#xff0c;空格符好会占据一定宽度&#xff0c;使用inline-block会产生元素间…

Java删除list

方案1>&#xff1a;for循环删除&#xff1a;注意从大到小遍历&#xff0c;不是从小到大&#xff1b; /*** 删除选中项*/private void deleteCheckedItem() {// list&#xff1a;初始化所有的数据&#xff1b;count&#xff1a;最后角标int count list.size() - 1 ;//从大到…

Unable to locate the default servlet for serving static content. Please set the 'defaultServletName'

大家好&#xff0c;我是烤鸭。 今天分享一个莫名其妙的异常及解决方式。 环境&#xff1a; tomcat6 jdk 1.6 异常主体&#xff1a; java.lang.IllegalStateException: Unable to locate the default servlet for serving static content. Please set the defaultServletName p…

iptables原理及规则

iptables简介和原理 我们先来了解以下社么是防火墙 防火墙&#xff1a;隔离功能&#xff0c;工作在网络或主机边缘&#xff0c;对进出网络或主机的数据包基于一定的规则检查&#xff0c;并在匹配某规则时由规则定义的行为进行处理的一组功能的组件&#xff0c;基本上的实现都是…

[css] 让你手写一个reset的文件,你应该怎么写?要考虑哪些方面呢?

[css] 让你手写一个reset的文件&#xff0c;你应该怎么写&#xff1f;要考虑哪些方面呢&#xff1f; 肯定首先考虑的是浏览器本身的样式&#xff0c;还有浏览器兼容。margin&#xff0c;padding >0ul,ol list style:nonea,text-decoration:nonefont-size:100%上标&#xff…

利用cookies跳过登陆验证码

前言在爬取某些网页时&#xff0c;登陆界面时经常遇到的一个坎&#xff0c;而现在大多数的网站在登陆时都会要求用户填写验证码。当然&#xff0c;我们可以设计一套机器学习的算法去破解验证码&#xff0c;然而&#xff0c;验证码的形式多种多样&#xff0c;稍微变一下&#xf…

org.apache.ibatis.reflection.ReflectionException: Error instantiating class with invalid types

大家好&#xff0c;我是烤鸭&#xff0c;记录一个初级异常&#xff0c;百度搜索结果不多&#xff1a; Caused by: org.apache.ibatis.reflection.ReflectionException: Error instantiating class com.xxx.xxx with invalid types 。 java.lang.NoSuchMethodException: com.xx…

[css] 你知道css的预处理器和后处理器都有哪些吗?它们有什么区别呢?

[css] 你知道css的预处理器和后处理器都有哪些吗&#xff1f;它们有什么区别呢&#xff1f; 目前最主流的 CSS 预处理器&#xff1a;Sass、LESS、Stylus 。优缺点优点&#xff1a;语言级逻辑处理&#xff0c;动态特性&#xff0c;改善项目结构缺点&#xff1a;采用特殊语法&…

[Swift]八大排序算法(八):基数排序

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号&#xff1a;山青咏芝&#xff08;shanqingyongzhi&#xff09;➤博客园地址&#xff1a;山青咏芝&#xff08;https://www.cnblogs.com/strengthen/ &#xff09;➤GitHub地址&…

推一波JAVA学习公众号

大家好&#xff0c;我是烤鸭&#xff0c;今天不水了。分享一波java学习公众号。从基础到架构都有&#xff0c;另外说一句&#xff0c;注意身体吧。另外说一句&#xff0c;本文不定时更新。1. JAVA思维导图2. 程序员小灰可爱的小仓鼠3. 码农每日一题4. JAVA后端技…

springmvc限流解决方案

本文采用3中限流方案&#xff1a; 1&#xff0c;谷歌的guava框架 2&#xff0c;使用redis技术 3&#xff0c;使用lua redis 技术 限流方案类型 1&#xff0c;令牌桶限流&#xff08;guava&#xff09; 2&#xff0c;计数器限流&#xff08;redis&#xff09; 各位看官可根…

二叉树的三种遍历(递归与非递归) + 层次遍历

<转载于 >>> > 二叉树是一种非常重要的数据结构&#xff0c;很多其他数据机构都是基于二叉树的基础演变过来的。二叉树有前、中、后三种遍历方式&#xff0c;因为树的本身就是用递归定义的&#xff0c;因此采用递归的方法实现三种遍历&#xff0c;不仅代码简洁…