导读:经过前面的博客总结,可以知道的是,HashMap是有一个一维数组和一个链表组成,从而得知,在解决冲突问题时,hashmap选择的是链地址法。为什么HashMap会用一个数组这链表组成,当时给出的答案是从那几种解决冲突的算法中推论的,这里给出一个正面的理由:

1,为什么用了一维数组:数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难

2,为什么用了链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易

而HashMap是两者的结合,用一维数组存放散列地址,以便更快速的遍历;用链表存放地址值,以便更快的插入和删除!

一、环形链表的形成分析

那么,在HashMap中,到底是怎样形成环形链表的?这个问题,得从HashMap的resize扩容问题说起!

备注:本博客中所示源码,均为java 7版本

HashMap的扩容原理:

 /**
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 16; /**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30; /**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

再看源码中,关于扩容resize()的实现:

   /**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the map, but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant).
*/
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
} Entry[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
transfer(newTable, rehash);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

备注:请注意这句话: newCapacity the new capacity, MUST be a power of two; must be greater than current capacity unless current  capacity is MAXIMUM_CAPACITY (in which case value is irrelevant)

在这里面,又调用了一个函数transfer函数:

   /**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}

总得来说,就是拷贝旧的数据元素,从新新建一个更大容量的空间,然后进行数据复制!

那么关于环形链表的形成,则主要在这扩容的过程。当多个线程同时对这个HashMap进行put操作,而察觉到内存容量不够,需要进行扩容时,多个线程会同时执行resize操作,而这就出现问题了,问题的原因分析如下:

首先,在HashMap扩容时,会改变链表中的元素的顺序,将元素从链表头部插入。PS:说是为了避免尾部遍历,这一部分不是本博客的主要介绍内容,后面再说。

而环形链表就在这一时刻发生,以下模拟2个线程同时扩容。假设,当前hashmap的空间为2(临界值为1),hashcode分别为0和1,在散列地址0处有元素A和B,这时候要添加元素C,C经过hash运算,得到散列地址为1,这时候由于超过了临界值,空间不够,需要调用resize方法进行扩容,那么在多线程条件下,会出现条件竞争,模拟过程如下:

(额,我画图的功底一向不好,见谅见谅)

线程一:读取到当前的hashmap情况,在准备扩容时,线程二介入

线程二:读取hashmap,进行扩容

线程一:继续执行

这个过程为,先将A复制到新的hash表中,然后接着复制B到链头(A的前边:B.next=A),本来B.next=null,到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将B.next=A,所以,这里继续复制A,让A.next=B,由此,环形链表出现:B.next=A; A.next=B

二、总结

在这里,只总结一个事儿,额,算是摘抄总结吧,就是在源码注释中,发现扩容的时候,必须为2的指数,这是为什么呢?

请点击此链接:HashMap扩容机制、线程安全  或者,自行学习hashmap的扩容机制

本篇博客介绍环形链表的形成就先到这里,下一篇博客介绍怎么判断是否出现环形链表!

【java基础 12】HashMap中是如何形成环形链表的?的更多相关文章

  1. Java基础:HashMap中putAll方法的疑惑

    最近回顾了下HashMap的源码(JDK1.7),当读到putAll方法时,发现了之前写的TODO标记,当时由于时间匆忙没来得及深究,现在回顾到了就再仔细思考了下 @Override public v ...

  2. Java基础系列--HashMap(JDK1.8)

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/10022092.html Java基础系列-HashMap 1.8 概述 HashMap是 ...

  3. 【Java基础】HashMap原理详解

    哈希表(hash table) 也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,本文会对java集合框架中Has ...

  4. java基础之hashmap

    Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多,但到底是自己学习的总结,就发出来跟大家一起分享,一起讨论. 1.hashma ...

  5. Java基础:HashMap假死锁问题的测试、分析和总结

    前言 前两天在公司的内部博客看到一个同事分享的线上服务挂掉CPU100%的文章,让我联想到HashMap在不恰当使用情况下的死循环问题,这里做个整理和总结,也顺便复习下HashMap. 直接上测试代码 ...

  6. java基础之 hashmap

    Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多,但到底是自己学习的总结,就发出来跟大家一起分享,一起讨论. 1.hashma ...

  7. [改善Java代码]减少HashMap中元素的数量

    在系统开发中我们经常会使用HashMap作为数据集容器,或者是用缓冲池来处理,一般很稳定,但偶尔也会出现内存溢出的问题(OutOfMemory错误),而且这经常是与HashMap有关的.而且这经常是与 ...

  8. java 基础 --- java8 HashMap

    问题 : HashMap 容量大小 (capacity)为什么为 2n HashMap 是线程安全的吗,为什么 HashMap 既然有hash进行排位还需要equals()作用是什么   文章部分图片 ...

  9. Java基础之String中equals,声明方式,等大总结

    无论你是一个编程新手还是老手,提到String你肯定感觉特别熟悉,因为String类我们在学习java基础的时候就已经学过,但是String类型有我们想象的那么简单吗?其实不然,String类型的知识 ...

随机推荐

  1. 编写sql查询语句思路

    编写查询语句思路/* 1.首先确定最终输出结果的列,包括几个方面:A.首先这些列来自于一个 表.还是多个表,如果是多个表则可能用到多表查询的(等值连接.不等值 连接.外连接.自连接):B.这些列是直接 ...

  2. SQL SEVER数据库重建索引的方法

    一.查询思路 1.想要判断数据库查询缓慢的问题,可以使用如下语句,可以列出查询语句的平均时间,总时间,所用的CPU时间等信息 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...

  3. PSNR

    PSNR,峰值信噪比,通常用来评价一幅图像压缩后和原图像相比质量的好坏,当然,压缩后图像一定会比原图像质量差的,所以就用这样一个评价指标来规定标准了.PSNR越高,压缩后失真越小.这里主要定义了两个值 ...

  4. 《毛毛虫组》【Alpha】Scrum meeting 2

    第二天 日期:2019/6/15 1.1 今日完成任务情况以及遇到的问题. 今日完成任务情况: (1)对数据库表进行完善及测试: (2)定义Regex类的IsMatch()方法: (3)进行这一模块代 ...

  5. vs2015驱动开发中使用RtlStringCchPrintfW()报错

    法一: 在头顶添加一段代码 #pragam comment(lib,"xxxxxx.lib") 法二: 右击工程点属性,选择Linker下的Input,在依赖项后面写上$(DDK_ ...

  6. oracle系統表、數據字典介紹與日常問題診斷

    oracle系統表.數據字典介紹與日常問題診斷 數據字典是由唯讀的table和view組成的,產生於$oracle_home\rdbms\admin\catalog.sql.裡面儲存Oracle資料庫 ...

  7. 01_6_Struts_ActionWildcard_通配符配置

    01_6_Struts_ActionWildcard_通配符配置 1.Struts_ActionWildcard_通配符配置 1.1配置struts.xml文件 <package name=&q ...

  8. 重装vs2008遇到的问题

    由于前几天办公室电脑dhcp服务挂了,wifi网线都上不了网,很无奈只能重装了系统.于是VS2008也要重装,之前一直用的都是前一个同事留下来的软件,没自己装过,自己装的时候踩了坑,记录一下. 重装了 ...

  9. jq封装插件,简单dome

    (function($) { $.fn.extend({ bold: function() { this.css({ fontWeight: "bold", color: 'red ...

  10. 初学redis,redis基本数据类型

    String: 1. set key value 2. get key 3. del key 4. strlen key 5. getset key value 修改键值对   6. getrange ...