Java容器解析系列(11) HashMap 详解

时间:2022-09-16 23:55:51

本篇我们来介绍一个最常用的Map结构——HashMap

关于HashMap,关于其基本原理,网上对其进行讲解的博客非常多,且很多都写的比较好,所以....

这里直接贴上地址:

关于hash算法:

Hash算法

Hash时取模一定要模质数吗?

关于HashMap:

深入Java集合学习系列:HashMap的实现原理

漫画:什么是HashMap?

JDK 源码中 HashMap 的 hash 方法原理是什么?

What is the use of Holder class in HashMap?(HashMap.Holder)

JDK7与JDK8中HashMap的实现(jdk8对HashMap的红黑树改进)

读完上面的内容,应该对HashMap有了很深的理解,这里补充1点助于深入的理解:

高并发情况下,HashMap为什么会出现死循环

漫画:高并发下的HashMap

疫苗:JAVA HASHMAP的死循环

上述博客均对多线程情况下的HashMap出现死循环问题的原理进行了解释,但是没有提供demo进行参考,我在网上也试图找相应的demo,但是没有能够100%复现的;这里我们直接参考疫苗:JAVA HASHMAP的死循环中的数据和过程,修改HashMap源码,提供循环demo,并阐述死锁发生的原因,及如何解决死循环:

首先,修改HashMap源码如下:

import java.util.Map;

public class HashMap<K,V>{

	private transient Entry[] table;
private transient int size;
private int threshold;
private final float loadFactor;
transient int modCount; public HashMap(int initialCapacity,float loadFactor){
int capacity = 1;
while(capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
} // 所有的hash值都是1
final int hash(Object k){
return 1;
} // 让所有的值的索引都是1
static int indexFor(int h,int length){
return 1;
} public V get(Object key){
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
} final Entry<K,V> getEntry(Object key){
int hash = (key == null) ? 0 : hash(key);
for(Entry<K,V> e = table[indexFor(hash,table.length)];
e != null;
e = e.next){
System.out.println("e.getKey():" + e.getKey());
Object k;
if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
return e;
}
}
return null;
} public V put(K key,V value,boolean delay){
int hash = hash(key);
int i = indexFor(hash,table.length);
for(Entry<K,V> e = table[i];e != null;e = e.next){
Object k;
if(e.hash == hash && ((k = e.key) == key || key.equals(k))){
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
modCount++;
addEntry(hash,key,value,i,delay);
return null;
} void addEntry(int hash,K key,V value,int bucketIndex,boolean delay){
if((size >= threshold) && (null != table[bucketIndex])){
resize(2 * table.length,delay);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash,table.length);
}
createEntry(hash,key,value,bucketIndex);
} void createEntry(int hash,K key,V value,int bucketIndex){
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash,key,value,e);
size++;
} // 扩容,添加了delay参数,用于在测试过程中进行测试
void resize(int newCapacity,boolean delay){
Entry[] newTable = new Entry[newCapacity];
transfer(newTable,delay);
threshold = (int)(newCapacity * loadFactor);
} // 原有的HashMap的transfer函数,会导致死循环,且有对象被丢弃
// 添加延时选项,用于手动控制多个线程的相对运行过程
void transfer(Entry<K,V>[] newTable,boolean delay){
System.out.println("transfer in\t" + Thread.currentThread().toString());
int newCapacity = newTable.length;
for(Entry e : table){
while(null != e){
Entry<K,V> next = e.next; if(delay){
try{
Thread.sleep(20);
}catch(InterruptedException e1){
e1.printStackTrace();
}
}
int i = indexFor(e.hash,newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
System.out.println("transfer out\t" + Thread.currentThread().toString());
} // 解决死循环
// 方式: 扩容后的数组,在对象rehash的过程中,如果某个位置出现冲突,采用尾插法将Entry插入链表
// 原理: 出现死循环的原因:在rehash过程中,对原数组某个位置的链表,采用从头开始的遍历方式,在新数组中,如果出现冲突,采用头插法将Entry插入链表
// 注意: 这里虽然解决了死循环的问题,但是因为并发修改对象内容,导致遍历过程中某些对象被丢弃的问题还是存在,
// 所以还是老老实实地用ConcurrentHashMap或者Collections.synchronizedMap()吧
//void transfer(Entry<K,V>[] newTable,boolean delay){
// System.out.println("transfer in\t" + Thread.currentThread().toString());
// int newCapacity = newTable.length;
// for(Entry e : table){
// while(null != e){
// Entry<K,V> next = e.next;
// if(delay){
// try{
// Thread.sleep(20);
// }catch(InterruptedException e1){
// e1.printStackTrace();
// }
// }
// int i = indexFor(e.hash,newCapacity);
// Entry tmp = newTable[i];
// if(tmp == null){
// newTable[i] = e;
// newTable[i].next = null;
// }else{
// // 尾插法
// while(tmp.next != null){
// System.out.println(tmp.next.getKey());
// System.out.println("----------------------");
// tmp = tmp.next;
// }
// tmp.next = e;
// tmp.next = null;
// }
// e = next;
// }
// }
// System.out.println("transfer out\t" + Thread.currentThread().toString());
//} static class Entry<K,V> implements Map.Entry<K,V>{
final K key;
V value;
Entry<K,V> next;
int hash;
Entry(int h,K k,V v,Entry<K,V> n){
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey(){
return key;
}
public final V getValue(){
return value;
}
public final V setValue(V newValue){
V oldValue = value;
value = newValue;
return oldValue;
}
// hashCode永远为1
public final int hashCode(){
return 1;
}
public final String toString(){
return getKey() + "=" + getValue();
}
}
}
public class HashMapInfinitLoop{

	public static void main(String[] args) throws InterruptedException{
HashMap<Integer,Integer> map = new HashMap<>(4,0.8f);
map.put(5,55,false);
map.put(7,77,false);
map.put(3,33,false); new Thread("Thread1"){
public void run(){
map.put(17,77,true);
System.out.println("put 17 finished");
}
}.start(); new Thread("Thread2"){
public void run(){
map.put(23,33,false);
System.out.println("put 23 finished");
}
}.start(); Thread.sleep(2000);
// 此处get()死循环
System.out.println(map.get(5)); } }

运行上述demo,控制台输出如下:

transfer in	Thread[Thread1,5,main]
transfer in Thread[Thread2,5,main]
transfer out Thread[Thread2,5,main]
put 23 finished
transfer out Thread[Thread1,5,main]
put 17 finished
e.getKey():17
e.getKey():23
e.getKey():3
e.getKey():7
e.getKey():3
e.getKey():7
.......

注释掉原有的transfer(),使用解决死循环的transfer(),运行结果如下:

transfer in	Thread[Thread1,5,main]
transfer in Thread[Thread2,5,main]
transfer out Thread[Thread2,5,main]
put 23 finished
transfer out Thread[Thread1,5,main]
put 17 finished
e.getKey():17
e.getKey():23
e.getKey():3
null

发现死循环问题是没有了,但是还是存在数据被丢弃的情况.

so,it sucks

Java容器解析系列(11) HashMap 详解的更多相关文章

  1. Java容器解析系列&lpar;13&rpar; WeakHashMap详解

    关于WeakHashMap其实没有太多可说的,其与HashMap大致相同,区别就在于: 对每个key的引用方式为弱引用; 关于java4种引用方式,参考java Reference 网上很多说 弱引用 ...

  2. Java容器解析系列&lpar;9&rpar; PrioriyQueue详解

    PriorityQueue:优先级队列; 在介绍该类之前,我们需要先了解一种数据结构--堆,在有些书上也直接称之为优先队列: 堆(Heap)是是具有下列性质的完全二叉树:每个结点的值都 >= 其 ...

  3. Java容器解析系列&lpar;14&rpar; IdentityHashMap详解

    IdentityHashMap,使用什么的跟HashMap相同,主要不同点在于: 数据结构:使用一个数组table来存储 key:value,table[2k] 为key, table[2k + 1] ...

  4. Java容器解析系列&lpar;7&rpar; ArrayDeque 详解

    ArrayDeque,从名字上就可以看出来,其是通过数组实现的双端队列,我们先来看其源码: /** 有自动扩容机制; 不是线程安全的; 不允许添加null; 作为栈使用时比java.util.Stac ...

  5. Java容器解析系列&lpar;12&rpar; LinkedHashMap 详解

    LinkedHashMap继承自HashMap,除了提供HashMap的功能外,LinkedHashMap还是维护一个双向链表(实际为带头结点的双向循环链表),持有所有的键值对的引用: 这个双向链表定 ...

  6. Java容器解析系列&lpar;17&rpar; LruCache详解

    在之前讲LinkedHashMap的时候,我们说起可以用来实现LRU(least recent used)算法,接下来我看一下其中的一个具体实现-----android sdk 中的LruCache. ...

  7. Java容器解析系列&lpar;0&rpar; 开篇

    最近刚好学习完成数据结构与算法相关内容: Data-Structures-and-Algorithm-Analysis 想结合Java中的容器类加深一下理解,因为之前对Java的容器类理解不是很深刻, ...

  8. java基础解析系列&lpar;五&rpar;---HashMap并发下的问题以及HashTable和CurrentHashMap的区别

    java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...

  9. java基础解析系列&lpar;三&rpar;---HashMap

    java基础解析系列(三)---HashMap java基础解析系列 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

随机推荐

  1. Spring-程序中获取注册bean的方式

    获得spring里注册Bean的四种方法,特别是第三种方法,简单: 一:方法一(多在struts框架中)继承BaseDispatchAction  import com.mas.wawacommuni ...

  2. C&plus;&plus;中构造函数详解及显式调用构造函数

    C++构造函数详解及显式调用构造函数                                         c++类的构造函数详解                        一. 构造函 ...

  3. android147 360 程序锁

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...

  4. 从app里跳到appstore评论页面的实现

    // 如果要实现在应用里面跳到appstore的对应评论页面里面的话,只要将下面地址中App_ID替换成自己的id就可以了,其他的地方都不用管. // 如果要用Safari浏览器做实验的话可以将地址中 ...

  5. skiing&lpar;搜索&plus;记忆化搜索&rpar;

    skiing 时间限制:3000 ms  |  内存限制:65535 KB 难度:5   描述 Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当 ...

  6. Windbg调试关键区&lpar;CriticalSection&rpar;死锁

    一. 准备工作 这里一个有关键区锁死问题的程序,运行之后依次点击"CS锁死"按钮.右上角退出按钮,程序就会卡死.(图1) 对于眼下的这个问题,界面完全失去响应,这说明负责消息处理的 ...

  7. linux网路编程&colon;字节序&lpar;大端、小端、网络、主机&rpar;

    字节序:就是数据在内存中的存放顺序,也可称之为端模式. 大端模式和小端模式的定义 1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端. 2) Big-End ...

  8. vue-cli3&period;X 打包后上传服务器刷新报 404的问题

    vue-cli3.X 默认配置 比2.X体验好很多,比如路由 如图,本地正常,传到服务器,因为二级目录,刷新就404,或 502等,找不到文件 nginx解决: location /{ error_p ...

  9. 从零开始学HTTP (二) HTTP结构与基础

    HTTP结构与基础 这篇文章中,我们主要针对HTTP\1.1版本进行介绍 请求报文和响应报文 请求报文 请求报文由客户端发出,其格式为: 请求方法 请求URI 协议版本 可选的请求首部字段和内容实体, ...

  10. img标签浏览器缓存图片问题

    问题:项目中用的img标签及其src属性,但是发现浏览器会缓存图片,这样每次如果修改了图片,再重新打开预览发现图片还是原来的. 原因:因为src后面的请求路径不变,所以浏览器就认为是同一个图片,就不会 ...