[Java集合] 彻底搞懂HashMap,HashTable,ConcurrentHashMap之关联

JasperReport制作行内容合并的表格 效果图: 实现思路一: 交叉表 另一个思路: 普通表格 缺点:无法实现内容 垂直居中显示 准备工作 一、数据准备 DROP TABLE IF EXISTS `address_item_sex_value`; CREATE TABLE `address_item_sex_value` ( `id` int(11) NOT NULL AUTO_INCREM... 阅读详情

注: 今天看到的一篇讲hashMap,hashTable,concurrentHashMap很透彻的一篇文章, 感谢原作者的分享. 
原文地址: 
http://blog.youkuaiyun.com/zhangerqing/article/details/8193118 

Java集合类是个非常重要的知识点,HashMap、HashTable、ConcurrentHashMap等算是集合类中的重点,可谓“重中之重”,首先来看个问题,如面试官问你:HashMap和HashTable有什么区别,一个比较简单的回答是:

1、HashMap是非线程安全的,HashTable是线程安全的。

2、HashMap的键和值都允许有null值存在,而HashTable则不行。

3、因为线程安全的问题,HashMap效率比HashTable的要高。

能答出上面的三点,简单的面试,算是过了,但是如果再问:Java中的另一个线程安全的与HashMap极其类似的类是什么?同样是线程安全,它与HashTable在线程同步上有什么不同?能把第二个问题完整的答出来,说明你的基础算是不错的了。带着这个问题,本章开始系Java之美[从菜鸟到高手演变]系列之深入解析HashMap和HashTable类应用而生!总想在文章的开头说点儿什么,但又无从说起。从最近的一些面试说起吧,感受就是:知识是永无止境的,永远不要觉得自己已经掌握了某些东西。如果对哪一块知识感兴趣,那么,请多多的花时间,哪怕最基础的东西也要理解它的原理,尽量往深了研究,在学习的同时,记得多与大家交流沟通,因为也许某些东西,从你自己的角度,是很难发现的,因为你并没有那么多的实验环境去发现他们。只有交流的多了,才能及时找出自己的不足,才能认识到:“哦,原来我还有这么多不知道的东西!”。

一、HashMap的内部存储结构 
Java中数据存储方式最底层的两种结构,一种是数组,另一种就是链表,数组的特点:连续空间,寻址迅速,但是在删除或者添加元素的时候需要有较大幅度的移动,所以查询速度快,增删较慢。而链表正好相反,由于空间不连续,寻址困难,增删元素只需修改指针,所以查询慢、增删快。有没有一种数据结构来综合一下数组和链表,以便发挥他们各自的优势?答案是肯定的!就是:哈希表。哈希表具有较快(常量级)的查询速度,及相对较快的增删速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”,我们可以理解为“链表的数组”,如下图:

从上图中,我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。它的内部其实是用一个Entity数组来实现的,属性有key、value、next。接下来我会从初始化阶段详细的讲解HashMap的内部结构。

1、初始化 
首先来看三个常量: 
static final int DEFAULT_INITIAL_CAPACITY = 16; 初始容量:16 
static final int MAXIMUM_CAPACITY = 1  
<< 30; 最大容量:2的30次方:1073741824 
static final float DEFAULT_LOAD_FACTOR = 0.75f;  
装载因子,后面再说它的作用 
来看个无参构造方法,也是我们最常用的:

[java]  view plain  copy
  1. public HashMap() {  
  2.         this.loadFactor = DEFAULT_LOAD_FACTOR;  
  3.         threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);  
  4.         table = new Entry[DEFAULT_INITIAL_CAPACITY];  
  5.         init();  
  6.     }  

loadFactor、threshold的值在此处没有起到作用,不过他们在后面的扩容方面会用到,此处只需理解table=new Entry[DEFAULT_INITIAL_CAPACITY].说明,默认就是开辟16个大小的空间。另外一个重要的构造方法:

[java]  view plain  copy
  1. public HashMap(int initialCapacity, float loadFactor) {  
  2.         if (initialCapacity < 0)  
  3.             throw new IllegalArgumentException("Illegal initial capacity: " +  
  4.                                                initialCapacity);  
  5.         if (initialCapacity > MAXIMUM_CAPACITY)  
  6.             initialCapacity = MAXIMUM_CAPACITY;  
  7.         if (loadFactor <= 0 || Float.isNaN(loadFactor))  
  8.             throw new IllegalArgumentException("Illegal load factor: " +  
  9.                                                loadFactor);  
  10.   
  11.         // Find a power of 2 >= initialCapacity  
  12.         int capacity = 1;  
  13.         while (capacity < initialCapacity)  
  14.             capacity <<= 1;  
  15.   
  16.         this.loadFactor = loadFactor;  
  17.         threshold = (int)(capacity * loadFactor);  
  18.         table = new Entry[capacity];  
  19.         init();  
  20.     }  


就是说传入参数的构造方法,我们把重点放在:

[java]  view plain  copy
  1. while (capacity < initialCapacity)  
  2.            capacity <<= 1;  


上面,该代码的意思是,实际的开辟的空间要大于传入的第一个参数的值。举个例子:
new HashMap(7,0.8),loadFactor为0.8,capacity为7,通过上述代码后,capacity的值为:8.(1 << 2的结果是4,2 << 2的结果为8<此处感谢网友wego1234的指正>)。所以,最终capacity的值为8,最后通过new Entry[capacity]来创建大小为capacity的数组,所以,这种方法最红取决于capacity的大小。
2、put(Object key,Object value)操作
 
当调用put操作时,首先判断key是否为null,如下代码1处:

[java]  view plain  copy
  1. <p>public V put(K key, V value) {  
  2.         if (key == null)  
  3.             return putForNullKey(value);  
  4.         int hash = hash(key.hashCode());  
  5.         int i = indexFor(hash, table.length);  
  6.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  7.             Object k;  
  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  9.                 V oldValue = e.value;  
  10.                 e.value = value;  
  11.                 e.recordAccess(this);  
  12.                 return oldValue;  
  13.             }  
  14.         }</p><p>        modCount++;  
  15.         addEntry(hash, key, value, i);  
  16.         return null;  
  17.     }</p>  


如果key是null,则调用如下代码:

[java]  view plain  copy
  1. private V putForNullKey(V value) {  
  2.         for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
  3.             if (e.key == null) {  
  4.                 V oldValue = e.value;  
  5.                 e.value = value;  
  6.                 e.recordAccess(this);  
  7.                 return oldValue;  
  8.             }  
  9.         }  
  10.         modCount++;  
  11.         addEntry(0, null, value, 0);  
  12.         return null;  
  13.     }  


就是说,获取Entry的第一个元素table[0],并基于第一个元素的next属性开始遍历,直到找到key为null的Entry,将其value设置为新的value值。
如果没有找到key为null的元素,则调用如上述代码的addEntry(0, null, value, 0);增加一个新的entry,代码如下:

[java]  view plain  copy
  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.     Entry<K,V> e = table[bucketIndex];  
  3.         table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
  4.         if (size++ >= threshold)  
  5.             resize(2 * table.length);  
  6.     }  


先获取第一个元素table[bucketIndex],传给e对象,新建一个entry,key为null,value为传入的value值,next为获取的e对象。如果容量大于threshold,容量扩大2倍。
如果key不为null,这也是大多数的情况,重新看一下源码:

[java]  view plain  copy
  1. public V put(K key, V value) {  
  2.         if (key == null)  
  3.             return putForNullKey(value);  
  4.         int hash = hash(key.hashCode());//---------------2---------------  
  5.         int i = indexFor(hash, table.length);  
  6.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {//--------------3-----------  
  7.             Object k;  
  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  9.                 V oldValue = e.value;  
  10.                 e.value = value;  
  11.                 e.recordAccess(this);  
  12.                 return oldValue;  
  13.             }  
  14.         }//-------------------4------------------  
  15.         modCount++;//----------------5----------  
  16.         addEntry(hash, key, value, i);-------------6-----------  
  17.         return null;  
  18.     }  


看源码中2处,首先会进行key.hashCode()操作,获取key的哈希值,hashCode()是Object类的一个方法,为本地方法,内部实现比较复杂,我们
会在后面作单独的关于Java中Native方法的分析中介绍。hash()的源码如下:

[java]  view plain  copy
  1. static int hash(int h) {  
  2.         // This function ensures that hashCodes that differ only by  
  3.         // constant multiples at each bit position have a bounded  
  4.         // number of collisions (approximately 8 at default load factor).  
  5.         h ^= (h >>> 20) ^ (h >>> 12);  
  6.         return h ^ (h >>> 7) ^ (h >>> 4);  
  7.     }  

int i = indexFor(hash, table.length);的意思,相当于int i = hash % Entry[].length;得到i后,就是在Entry数组中的位置,(上述代码5和6处是如果Entry数组中不存在新要增加的元素,则执行5,6处的代码,如果存在,即Hash冲突,则执行 3-4处的代码,此处HashMap中采用链地址法解决Hash冲突。此处经网友bbycszh指正,发现上述陈述有些问题)。重新解释:其实不管Entry数组中i位置有无元素,都会去执行5-6处的代码,如果没有,则直接新增,如果有,则将新元素设置为Entry[0],其next指针指向原有对象,即原有对象为Entry[1]。具体方法可以解释为下面的这段文字:(3-4处的代码只是检查在索引为i的这条链上有没有key重复的,有则替换且返回原值,程序不再去执行5-6处的代码,无则无处理)

上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。如, 第一个键值对A进来,通过计算其key的hash得到的i=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其i也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,i也等于0,那么C.next = B,Entry[0] = C;这样我们发现i=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起,也就是说数组中存储的是最后插入的元素。

到这里为止,HashMap的大致实现,我们应该已经清楚了。当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个i的链就会很长,会不会影响性能?HashMap里面设置一个因素(也称为因子),随着map的size越来越大,Entry[]会以一定的规则加长长度。

2、get(Object key)操作
get(Object key)操作时根据键来获取值,如果了解了put操作,get操作容易理解,先来看看源码的实现:

[java]  view plain  copy
  1. public V get(Object key) {  
  2.         if (key == null)  
  3.             return getForNullKey();  
  4.         int hash = hash(key.hashCode());  
  5.         for (Entry<K,V> e = table[indexFor(hash, table.length)];  
  6.              e != null;  
  7.              e = e.next) {  
  8.             Object k;  
  9.             if (e.hash == hash && ((k = e.key) == key || key.equals(k)))//-------------------1----------------  
  10.                 return e.value;  
  11.         }  
  12.         return null;  
  13.     }  


意思就是:1、当key为null时,调用getForNullKey(),源码如下:

[java]  view plain  copy
  1. private V getForNullKey() {  
  2.         for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
  3.             if (e.key == null)  
  4.                 return e.value;  
  5.         }  
  6.         return null;  
  7.     }  

2、当key不为null时,先根据hash函数得到hash值,在更具indexFor()得到i的值,循环遍历链表,如果有:key值等于已存在的key值,则返回其value。如上述get()代码1处判断。

总结下HashMap新增put和获取get操作:

[java]  view plain  copy
  1. //存储时:  
  2. int hash = key.hashCode();  
  3. int i = hash % Entry[].length;  
  4. Entry[i] = value;  
  5.   
  6. //取值时:  
  7. int hash = key.hashCode();  
  8. int i = hash % Entry[].length;  
  9. return Entry[i];  

理解了就比较简单。

此处附一个简单的HashMap小算法应用:

[java]  view plain  copy
  1. package com.xtfggef.hashmap;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5. import java.util.Set;  
  6.   
  7. /** 
  8.  * 打印在数组中出现n/2以上的元素 
  9.  * 利用一个HashMap来存放数组元素及出现的次数 
  10.  * @author erqing 
  11.  * 
  12.  */  
  13. public class HashMapTest {  
  14.       
  15.     public static void main(String[] args) {  
  16.           
  17.         int [] a = {2,3,2,2,1,4,2,2,2,7,9,6,2,2,3,1,0};  
  18.           
  19.         Map<Integer, Integer> map = new HashMap<Integer,Integer>();  
  20.         for(int i=0; i<a.length; i++){  
  21.             if(map.containsKey(a[i])){  
  22.                 int tmp = map.get(a[i]);  
  23.                 tmp+=1;  
  24.                 map.put(a[i], tmp);  
  25.             }else{  
  26.                 map.put(a[i], 1);  
  27.             }  
  28.         }  
  29.         Set<Integer> set = map.keySet();//------------1------------  
  30.         for (Integer s : set) {  
  31.             if(map.get(s)>=a.length/2){  
  32.                 System.out.println(s);  
  33.             }  
  34.         }//--------------2---------------  
  35.     }  
  36. }  

此处注意两个地方,map.containsKey(),还有就是上述1-2处的代码。

理解了HashMap的上面的操作,其它的大多数方法都很容易理解了。搞清楚它的内部存储机制,一切OK!

二、HashTable的内部存储结构

HashTable和HashMap采用相同的存储机制,二者的实现基本一致,不同的是:

1、HashMap是非线程安全的,HashTable是线程安全的,内部的方法基本都是synchronized。

2、HashTable不允许有null值的存在。

在HashTable中调用put方法时,如果key为null,直接抛出NullPointerException。其它细微的差别还有,比如初始化Entry数组的大小等等,但基本思想和HashMap一样。

三、HashTable和ConcurrentHashMap的比较

如我开篇所说一样,ConcurrentHashMap是线程安全的HashMap的实现。同样是线程安全的类,它与HashTable在同步方面有什么不同呢?

之前我们说,synchronized关键字加锁的原理,其实是对对象加锁,不论你是在方法前加synchronized还是语句块前加,锁住的都是对象整体,但是ConcurrentHashMap的同步机制和这个不同,它不是加synchronized关键字,而是基于lock操作的,这样的目的是保证同步的时候,锁住的不是整个对象。事实上,ConcurrentHashMap可以满足concurrentLevel个线程并发无阻塞的操作集合对象。关于concurrentLevel稍后介绍。

1、构造方法

为了容易理解,我们先从构造函数说起。ConcurrentHashMap是基于一个叫Segment数组的,其实和Entry类似,如下:

[java]  view plain  copy
  1. public ConcurrentHashMap()  
  2.   {  
  3.     this(16, 0.75F, 16);  
  4.   }  


默认传入值16,调用下面的方法:

[java]  view plain  copy
  1. public ConcurrentHashMap(int paramInt1, float paramFloat, int paramInt2)  
  2.   {  
  3.     if ((paramFloat <= 0F) || (paramInt1 < 0) || (paramInt2 <= 0))  
  4.       throw new IllegalArgumentException();  
  5.   
  6.     if (paramInt2 > 65536) {  
  7.       paramInt2 = 65536;  
  8.     }  
  9.   
  10.     int i = 0;  
  11.     int j = 1;  
  12.     while (j < paramInt2) {  
  13.       ++i;  
  14.       j <<= 1;  
  15.     }  
  16.     this.segmentShift = (32 - i);  
  17.     this.segmentMask = (j - 1);  
  18.     this.segments = Segment.newArray(j);  
  19.   
  20.     if (paramInt1 > 1073741824)  
  21.       paramInt1 = 1073741824;  
  22.     int k = paramInt1 / j;  
  23.     if (k * j < paramInt1)  
  24.       ++k;  
  25.     int l = 1;  
  26.     while (l < k)  
  27.       l <<= 1;  
  28.   
  29.     for (int i1 = 0; i1 < this.segments.length; ++i1)  
  30.       this.segments[i1] = new Segment(l, paramFloat);  
  31.   }  


你会发现比HashMap的构造函数多一个参数,paramInt1就是我们之前谈过的initialCapacity,就是数组的初始化大小,paramfloat为loadFactor(装载因子),而paramInt2则是我们所要说的concurrentLevel,这三个值分别被初始化为16,0.75,16,经过:

[java]  view plain  copy
  1. while (j < paramInt2) {  
  2.       ++i;  
  3.       j <<= 1;  
  4.     }  


后,j就是我们最终要开辟的数组的size值,当paramInt1为16时,计算出来的size值就是16.通过:

this.segments = Segment.newArray(j)后,我们看出了,最终稿创建的Segment数组的大小为16.最终创建Segment对象时:

[java]  view plain  copy
  1. this.segments[i1] = new Segment(cap, paramFloat);  

需要cap值,而cap值来源于:

[java]  view plain  copy
  1. int k = paramInt1 / j;  
  2.   if (k * j < paramInt1)  
  3.     ++k;  
  4.   int cap = 1;  
  5.   while (cap < k)  
  6.     cap <<= 1;  

组后创建大小为cap的数组。最后根据数组的大小及paramFloat的值算出了threshold的值:

this.threshold = (int)(paramArrayOfHashEntry.length * this.loadFactor)。

2、put操作

[java]  view plain  copy
  1. public V put(K paramK, V paramV)  
  2.   {  
  3.     if (paramV == null)  
  4.       throw new NullPointerException();  
  5.     int i = hash(paramK.hashCode());  
  6.     return segmentFor(i).put(paramK, i, paramV, false);  
  7.   }  


与HashMap不同的是,如果key为null,直接抛出NullPointer异常,之后,同样先计算hashCode的值,再计算hash值,不过此处hash函数和HashMap中的不一样:

[java]  view plain  copy
  1. private static int hash(int paramInt)  
  2.   {  
  3.     paramInt += (paramInt << 15 ^ 0xFFFFCD7D);  
  4.     paramInt ^= paramInt >>> 10;  
  5.     paramInt += (paramInt << 3);  
  6.     paramInt ^= paramInt >>> 6;  
  7.     paramInt += (paramInt << 2) + (paramInt << 14);  
  8.     return (paramInt ^ paramInt >>> 16);  
  9.   }  


 

[java]  view plain  copy
  1. final Segment<K, V> segmentFor(int paramInt)  
  2.   {  
  3.     return this.segments[(paramInt >>> this.segmentShift & this.segmentMask)];  
  4.   }  


根据上述代码找到Segment对象后,调用put来操作:

[java]  view plain  copy
  1. V put(K paramK, int paramInt, V paramV, boolean paramBoolean)  
  2. {  
  3.   lock();  
  4.   try {  
  5.     Object localObject1;  
  6.     Object localObject2;  
  7.     int i = this.count;  
  8.     if (i++ > this.threshold)  
  9.       rehash();  
  10.     ConcurrentHashMap.HashEntry[] arrayOfHashEntry = this.table;  
  11.     int j = paramInt & arrayOfHashEntry.length - 1;  
  12.     ConcurrentHashMap.HashEntry localHashEntry1 = arrayOfHashEntry[j];  
  13.     ConcurrentHashMap.HashEntry localHashEntry2 = localHashEntry1;  
  14.     while ((localHashEntry2 != null) && (((localHashEntry2.hash != paramInt) || (!(paramK.equals(localHashEntry2.key)))))) {  
  15.       localHashEntry2 = localHashEntry2.next;  
  16.     }  
  17.   
  18.     if (localHashEntry2 != null) {  
  19.       localObject1 = localHashEntry2.value;  
  20.       if (!(paramBoolean))  
  21.         localHashEntry2.value = paramV;  
  22.     }  
  23.     else {  
  24.       localObject1 = null;  
  25.       this.modCount += 1;  
  26.       arrayOfHashEntry[j] = new ConcurrentHashMap.HashEntry(paramK, paramInt, localHashEntry1, paramV);  
  27.       this.count = i;  
  28.     }  
  29.     return localObject1;  
  30.   } finally {  
  31.     unlock();  
  32.   }  
  33. }  


先调用lock(),lock是ReentrantLock类的一个方法,用当前存储的个数+1来和threshold比较,如果大于threshold,则进行rehash,将当前的容量扩大2倍,重新进行hash。之后对hash的值和数组大小-1进行按位于操作后,得到当前的key需要放入的位置,从这儿开始,和HashMap一样。

从上述的分析看出,ConcurrentHashMap基于concurrentLevel划分出了多个Segment来对key-value进行存储,从而避免每次锁定整个数组,在默认的情况下,允许16个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。

在多线程的环境中,相对于HashTable,ConcurrentHashMap会带来很大的性能提升!

欢迎读者批评指正,有任何建议请联系:

EGG:xtfggef@gmail.com      http://weibo.com/xtfggef

四、HashMap常见问题分析

1、此处我觉得网友huxb23@126的一篇文章说的很好,分析多线程并发写HashMap线程被hang住的原因 ,因为是优秀的资源,此处我整理下搬到这儿。

以下内容转自博文:http://blog.163.com/huxb23@126/blog/static/625898182011211318854/ 

先看原问题代码:

[java]  view plain  copy
  1. import java.util.HashMap;  
  2.   
  3. public class TestLock {  
  4.   
  5.     private HashMap map = new HashMap();  
  6.   
  7.     public TestLock() {  
  8.         Thread t1 = new Thread() {  
  9.             public void run() {  
  10.                 for (int i = 0; i < 50000; i++) {  
  11.                     map.put(new Integer(i), i);  
  12.                 }  
  13.                 System.out.println("t1 over");  
  14.             }  
  15.         };  
  16.   
  17.         Thread t2 = new Thread() {  
  18.             public void run() {  
  19.                 for (int i = 0; i < 50000; i++) {  
  20.                     map.put(new Integer(i), i);  
  21.                 }  
  22.   
  23.                 System.out.println("t2 over");  
  24.             }  
  25.         };  
  26.   
  27.         t1.start();  
  28.         t2.start();  
  29.   
  30.     }  
  31.   
  32.     public static void main(String[] args) {  
  33.         new TestLock();  
  34.     }  
  35. }  


就是启了两个线程,不断的往一个非线程安全的HashMap中put内容,put的内容很简单,key和value都是从0自增的整数(这个put的内容做的并不好,以致于后来干扰了我分析问题的思路)。对HashMap做并发写操作,我原以为只不过会产生脏数据的情况,但反复运行这个程序,会出现线程t1、t2被hang住的情况,多数情况下是一个线程被hang住另一个成功结束,偶尔会两个线程都被hang住。说到这里,你如果觉得不好好学习ConcurrentHashMap而在这瞎折腾就手下留情跳过吧。
好吧,分析下HashMap的put函数源码看看问题出在哪,这里就罗列出相关代码(jdk1.6):

[java]  view plain  copy
  1. public V put(K paramK, V paramV)  
  2. {  
  3.   if (paramK == null)  
  4.     return putForNullKey(paramV);  
  5.   int i = hash(paramK.hashCode());  
  6.   int j = indexFor(i, this.table.length);  
  7.   for (Entry localEntry = this.table[j]; localEntry != null; localEntry = localEntry.next)  
  8.   {  
  9.     if (localEntry.hash == i) { java.lang.Object localObject1;  
  10.       if (((localObject1 = localEntry.key) == paramK) || (paramK.equals(localObject1))) {  
  11.         java.lang.Object localObject2 = localEntry.value;  
  12.         localEntry.value = paramV;  
  13.         localEntry.recordAccess(this);  
  14.         return localObject2;  
  15.       }  
  16.     }  
  17.   }  
  18.   this.modCount += 1;  
  19.   addEntry(i, paramK, paramV, j);  
  20.   return null;  
  21. }  
  22.   
  23. private V putForNullKey(V paramV)  
  24. {  
  25.   for (Entry localEntry = this.table[0]; localEntry != null; localEntry = localEntry.next)  
  26.     if (localEntry.key == null) {  
  27.       java.lang.Object localObject = localEntry.value;  
  28.       localEntry.value = paramV;  
  29.       localEntry.recordAccess(this);  
  30.       return localObject;  
  31.     }  
  32.   
  33.   this.modCount += 1;  
  34.   addEntry(0, null, paramV, 0);  
  35.   return null;  
  36. }  

 

通过jconsole(或者thread dump),可以看到线程停在了transfer方法的while循环处。这个transfer方法的作用是,当Map中元素数超过阈值需要resize时,它负责把原Map中的元素映射到新Map中。我修改了HashMap,加上了@标记2和@标记3的代码片断,以打印出死循环时的状态,结果死循环线程总是出现类似这样的输出:“Thread-1,e==next:false,e==next.next:true,e:108928=108928,next:108928=108928,eq:true”。
这个输出表明:
1)这个Entry链中的两个Entry之间的关系是:e=e.next.next,造成死循环。
2)e.equals(e.next),但e!=e.next。因为测试例子中两个线程put的内容一样,并发时可能同一个key被保存了多个value,这种错误是在addEntry函数产生的,但这和线程死循环没有关系。

接下来就分析transfer中那个while循环了。先所说这个循环正常的功能:src[j]保存的是映射成同一个hash值的多个Entry的链表,这个src[j]可能为null,可能只有一个Entry,也可能由多个Entry链接起来。假设是多个Entry,原来的链是(src[j]=a)->b(也就是src[j]=a,a.next=b,b.next=null),经过while处理后得到了(newTable[i]=b)->a。也就是说,把链表的next关系反向了。

再看看这个while中可能在多线程情况下引起问题的语句。针对两个线程t1和t2,这里它们可能的产生问题的执行序列做些个人分析:

1)假设同一个Entry列表[e->f->...],t1先到,t2后到并都走到while中。t1执行“e.next = newTable[i];newTable[i] = e;”这使得e.next=null(初始的newTable[i]为null),newTable[i]指向了e。这时t2执行了“e.next = newTable[i];newTable[i] = e;”,这使得e.next=e,e死循环了。因为循环开始处的“final Entry next = e.next;”,尽管e自己死循环了,在最后的“e = next;”后,两个线程都会跳过e继续执行下去。

2)在while中逐个遍历Entry链表中的Entry而把next关系反向时,newTable[i]成为了被交换的引用,可疑的语句在于“e.next = newTable[i];”。假设链表e->f->g被t1处理成e<-f<-g,newTable[i]指向了g,这时t2进来了,它一执行“e.next = newTable[i];”就使得e->g,造成了死循环。所以,理论上来说,死循环的Entry个数可能很多。尽管产生了死循环,但是t1执行到了死循环的右边,所以是会继续执行下去的,而t2如果执行“final Entry next = e.next;”的next为null,则也会继续执行下去,否则就进入了死循环。

3)似乎情况会更复杂,因为即便线程跳出了死循环,它下一次做resize进入transfer时,有可能因为之前的死循环Entry链表而被hang住(似乎是一定会被hang住)。也有可能,在put检查Entry链表时(@标记1),因为Entry链表的死循环而被hang住。也似乎有可能,活着的线程和死循环的线程同时执行在while里后,两个线程都能活着出去。所以,可能两个线程平安退出,可能一个线程hang在transfer中,可能两个线程都被hang住而又不一定在一个地方。

4)我反复的测试,出现一个线程被hang住的情况最多,都是e=e.next.next造成的,这主要就是例子put两份增量数据造成的。我如果去掉@标记3的输出,有时也能复现两个线程都被hang住的情况,但加上后就很难复现出来。我又把put的数据改了下,比如让两个线程put范围不同的数据,就能复现出e=e.next,两个线程都被hang住的情况。

上面罗哩罗嗦了很多,一开始我简单的分析后觉得似乎明白了怎么回事,可现在仔细琢磨后似乎又不明白了许多。有一个细节是,每次死循环的key的大小也是有据可循的,我就不打哈了。感觉,如果样本多些,可能出现问题的原因点会很多,也会更复杂,我姑且不再蛋疼下去。至于有人提到ConcurrentHashMap也有这个问题,我觉得不大可能,因为它的put操作是加锁的,如果有这个问题就不叫线程安全的Map了。

2、HashMap中Value可以相同,但是键不可以相同

当插入HashMap的key相同时,会覆盖原有的Value,且返回原Value值,看下面的程序:

[java]  view plain  copy
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.           
  5.         HashMap<String,Integer> map = new HashMap<String,Integer>();  
  6.   
  7.         //出入两个Value相同的值,没有问题  
  8.         map.put("egg", 1);  
  9.         map.put("niu", 1);  
  10.           
  11.         //插入key相同的值,看返回结果  
  12.         int egg = (Integer) map.put("egg", 3);  
  13.           
  14.         System.out.println(egg);   //输出1  
  15.         System.out.println(map.get("egg"));   //输出3,将原值1覆盖  
  16.         System.out.println(map.get("niu"));   //输出1  
  17.     }  
  18. }  

相同的键会被覆盖,且返回原值。

3、HashMap按值排序

给定一个数组,求出每个数据出现的次数并按照次数的由大到小排列出来。我们选用HashMap来做,key存储数组元素,值存储出现的次数,最后用Collections的sort方法对HashMap的值进行排序。代码如下:

[java]  view plain  copy
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.         int data[] = { 2, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2,  
  6.                 7, 8, 8, 7, 8, 7, 9, 0 };  
  7.         Map<Integer, Integer> map = new HashMap<Integer, Integer>();  
  8.         for (int i : data) {  
  9.             if (map.containsKey(i)) {//判断HashMap里是否存在  
  10.                 map.put(i, map.get(i) + 1);//已存在,值+1  
  11.             } else {  
  12.                 map.put(i, 1);//不存在,新增  
  13.             }  
  14.         }  
  15.         //map按值排序  
  16.         List<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer, Integer>>(  
  17.                 map.entrySet());  
  18.         Collections.sort(list, new Comparator<Map.Entry<Integer, Integer>>() {  
  19.             public int compare(Map.Entry<Integer, Integer> o1,  
  20.                     Map.Entry<Integer, Integer> o2) {  
  21.                 return (o2.getValue() - o1.getValue());  
  22.             }  
  23.         });  
  24.         for (Map.Entry<Integer, Integer> m : list) {  
  25.             System.out.println(m.getKey() + "-" + m.getValue());  
  26.         }  
  27.     }  
  28.   
  29. }  

输出:

2-6
5-5
3-4
8-3
7-3
9-1
0-1

  1. public HashMap() { 
  2.         this.loadFactor = DEFAULT_LOAD_FACTOR; 
  3.         threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); 
  4.         table = new Entry[DEFAULT_INITIAL_CAPACITY]; 
  5.         init(); 
  6.     } 

loadFactor、threshold的值在此处没有起到作用,不过他们在后面的扩容方面会用到,此处只需理解table=new Entry[DEFAULT_INITIAL_CAPACITY].说明,默认就是开辟16个大小的空间。另外一个重要的构造方法:

[java]  view plain  copy
  1. public HashMap(int initialCapacity, float loadFactor) { 
  2.         if (initialCapacity < 0) 
  3.             throw new IllegalArgumentException("Illegal initial capacity: " + 
  4.                                                initialCapacity); 
  5.         if (initialCapacity > MAXIMUM_CAPACITY) 
  6.             initialCapacity = MAXIMUM_CAPACITY; 
  7.         if (loadFactor <= 0 || Float.isNaN(loadFactor)) 
  8.             throw new IllegalArgumentException("Illegal load factor: " + 
  9.                                                loadFactor); 
  10.  
  11.         // Find a power of 2 >= initialCapacity 
  12.         int capacity = 1; 
  13.         while (capacity < initialCapacity) 
  14.             capacity <<= 1; 
  15.  
  16.         this.loadFactor = loadFactor; 
  17.         threshold = (int)(capacity * loadFactor); 
  18.         table = new Entry[capacity]; 
  19.         init(); 
  20.     } 


就是说传入参数的构造方法,我们把重点放在:

[java]  view plain  copy
  1. while (capacity < initialCapacity) 
  2.            capacity <<= 1; 


上面,该代码的意思是,实际的开辟的空间要大于传入的第一个参数的值。举个例子: 
new HashMap(7,0.8),loadFactor为0.8,capacity为7,通过上述代码后,capacity的值为:8.(1 << 2的结果是4,2 << 2的结果为8<此处感谢网友wego1234的指正>)。所以,最终capacity的值为8,最后通过new Entry[capacity]来创建大小为capacity的数组,所以,这种方法最红取决于capacity的大小。 
2、put(Object key,Object value)操作 
  
当调用put操作时,首先判断key是否为null,如下代码1处:

[java]  view plain  copy
  1. <p>public V put(K key, V value) { 
  2.         if (key == null) 
  3.             return putForNullKey(value); 
  4.         int hash = hash(key.hashCode()); 
  5.         int i = indexFor(hash, table.length); 
  6.         for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
  7.             Object k; 
  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
  9.                 V oldValue = e.value; 
  10.                 e.value = value; 
  11.                 e.recordAccess(this); 
  12.                 return oldValue; 
  13.             } 
  14.         }</p><p>        modCount++; 
  15.         addEntry(hash, key, value, i); 
  16.         return null; 
  17.     }</p> 


如果key是null,则调用如下代码:

[java]  view plain  copy
  1. private V putForNullKey(V value) { 
  2.         for (Entry<K,V> e = table[0]; e != null; e = e.next) { 
  3.             if (e.key == null) { 
  4.                 V oldValue = e.value; 
  5.                 e.value = value; 
  6.                 e.recordAccess(this); 
  7.                 return oldValue; 
  8.             } 
  9.         } 
  10.         modCount++; 
  11.         addEntry(0, null, value, 0); 
  12.         return null; 
  13.     } 


就是说,获取Entry的第一个元素table[0],并基于第一个元素的next属性开始遍历,直到找到key为null的Entry,将其value设置为新的value值。 
如果没有找到key为null的元素,则调用如上述代码的addEntry(0, null, value, 0);增加一个新的entry,代码如下:

[java]  view plain  copy
  1. void addEntry(int hash, K key, V value, int bucketIndex) { 
  2.     Entry<K,V> e = table[bucketIndex]; 
  3.         table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 
  4.         if (size++ >= threshold) 
  5.             resize(2 * table.length); 
  6.     } 


先获取第一个元素table[bucketIndex],传给e对象,新建一个entry,key为null,value为传入的value值,next为获取的e对象。如果容量大于threshold,容量扩大2倍。 
如果key不为null,这也是大多数的情况,重新看一下源码:

[java]  view plain  copy
  1. public V put(K key, V value) { 
  2.         if (key == null) 
  3.             return putForNullKey(value); 
  4.         int hash = hash(key.hashCode());//---------------2--------------- 
  5.         int i = indexFor(hash, table.length); 
  6.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {//--------------3----------- 
  7.             Object k; 
  8.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
  9.                 V oldValue = e.value; 
  10.                 e.value = value; 
  11.                 e.recordAccess(this); 
  12.                 return oldValue; 
  13.             } 
  14.         }//-------------------4------------------ 
  15.         modCount++;//----------------5---------- 
  16.         addEntry(hash, key, value, i);-------------6----------- 
  17.         return null; 
  18.     } 


看源码中2处,首先会进行key.hashCode()操作,获取key的哈希值,hashCode()是Object类的一个方法,为本地方法,内部实现比较复杂,我们 
会在后面作单独的关于Java中Native方法的分析中介绍。hash()的源码如下:

[java]  view plain  copy
  1. static int hash(int h) { 
  2.         // This function ensures that hashCodes that differ only by 
  3.         // constant multiples at each bit position have a bounded 
  4.         // number of collisions (approximately 8 at default load factor). 
  5.         h ^= (h >>> 20) ^ (h >>> 12); 
  6.         return h ^ (h >>> 7) ^ (h >>> 4); 
  7.     } 

int i = indexFor(hash, table.length);的意思,相当于int i = hash % Entry[].length;得到i后,就是在Entry数组中的位置,(上述代码5和6处是如果Entry数组中不存在新要增加的元素,则执行5,6处的代码,如果存在,即Hash冲突,则执行 3-4处的代码,此处HashMap中采用链地址法解决Hash冲突。此处经网友bbycszh指正,发现上述陈述有些问题)。重新解释:其实不管Entry数组中i位置有无元素,都会去执行5-6处的代码,如果没有,则直接新增,如果有,则将新元素设置为Entry[0],其next指针指向原有对象,即原有对象为Entry[1]。具体方法可以解释为下面的这段文字:(3-4处的代码只是检查在索引为i的这条链上有没有key重复的,有则替换且返回原值,程序不再去执行5-6处的代码,无则无处理)

上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。如, 第一个键值对A进来,通过计算其key的hash得到的i=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其i也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,i也等于0,那么C.next = B,Entry[0] = C;这样我们发现i=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起,也就是说数组中存储的是最后插入的元素。

到这里为止,HashMap的大致实现,我们应该已经清楚了。当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个i的链就会很长,会不会影响性能?HashMap里面设置一个因素(也称为因子),随着map的size越来越大,Entry[]会以一定的规则加长长度。 

2、get(Object key)操作 
get(Object key)操作时根据键来获取值,如果了解了put操作,get操作容易理解,先来看看源码的实现:

[java]  view plain  copy
  1. public V get(Object key) { 
  2.         if (key == null) 
  3.             return getForNullKey(); 
  4.         int hash = hash(key.hashCode()); 
  5.         for (Entry<K,V> e = table[indexFor(hash, table.length)]; 
  6.              e != null; 
  7.              e = e.next) { 
  8.             Object k; 
  9.             if (e.hash == hash && ((k = e.key) == key || key.equals(k)))//-------------------1---------------- 
  10.                 return e.value; 
  11.         } 
  12.         return null; 
  13.     } 


意思就是:1、当key为null时,调用getForNullKey(),源码如下:

[java]  view plain  copy
  1. private V getForNullKey() { 
  2.         for (Entry<K,V> e = table[0]; e != null; e = e.next) { 
  3.             if (e.key == null) 
  4.                 return e.value; 
  5.         } 
  6.         return null; 
  7.     } 

2、当key不为null时,先根据hash函数得到hash值,在更具indexFor()得到i的值,循环遍历链表,如果有:key值等于已存在的key值,则返回其value。如上述get()代码1处判断。

总结下HashMap新增put和获取get操作:

[java]  view plain  copy
  1. //存储时: 
  2. int hash = key.hashCode(); 
  3. int i = hash % Entry[].length; 
  4. Entry[i] = value; 
  5.  
  6. //取值时: 
  7. int hash = key.hashCode(); 
  8. int i = hash % Entry[].length; 
  9. return Entry[i]; 

理解了就比较简单。

此处附一个简单的HashMap小算法应用:

[java]  view plain  copy
  1. package com.xtfggef.hashmap; 
  2.  
  3. import java.util.HashMap; 
  4. import java.util.Map; 
  5. import java.util.Set; 
  6.  
  7. /** 
  8. * 打印在数组中出现n/2以上的元素 
  9. * 利用一个HashMap来存放数组元素及出现的次数 
  10. * @author erqing 
  11. * 
  12. */ 
  13. public class HashMapTest { 
  14.      
  15.     public static void main(String[] args) { 
  16.          
  17.         int [] a = {2,3,2,2,1,4,2,2,2,7,9,6,2,2,3,1,0}; 
  18.          
  19.         Map<Integer, Integer> map = new HashMap<Integer,Integer>(); 
  20.         for(int i=0; i<a.length; i++){ 
  21.             if(map.containsKey(a[i])){ 
  22.                 int tmp = map.get(a[i]); 
  23.                 tmp+=1; 
  24.                 map.put(a[i], tmp); 
  25.             }else{ 
  26.                 map.put(a[i], 1); 
  27.             } 
  28.         } 
  29.         Set<Integer> set = map.keySet();//------------1------------ 
  30.         for (Integer s : set) { 
  31.             if(map.get(s)>=a.length/2){ 
  32.                 System.out.println(s); 
  33.             } 
  34.         }//--------------2--------------- 
  35.     } 
  36. } 

此处注意两个地方,map.containsKey(),还有就是上述1-2处的代码。

理解了HashMap的上面的操作,其它的大多数方法都很容易理解了。搞清楚它的内部存储机制,一切OK! 

二、HashTable的内部存储结构

HashTable和HashMap采用相同的存储机制,二者的实现基本一致,不同的是:

1、HashMap是非线程安全的,HashTable是线程安全的,内部的方法基本都是synchronized。

2、HashTable不允许有null值的存在。

在HashTable中调用put方法时,如果key为null,直接抛出NullPointerException。其它细微的差别还有,比如初始化Entry数组的大小等等,但基本思想和HashMap一样。

三、HashTable和ConcurrentHashMap的比较

如我开篇所说一样,ConcurrentHashMap是线程安全的HashMap的实现。同样是线程安全的类,它与HashTable在同步方面有什么不同呢?

之前我们说,synchronized关键字加锁的原理,其实是对对象加锁,不论你是在方法前加synchronized还是语句块前加,锁住的都是对象整体,但是ConcurrentHashMap的同步机制和这个不同,它不是加synchronized关键字,而是基于lock操作的,这样的目的是保证同步的时候,锁住的不是整个对象。事实上,ConcurrentHashMap可以满足concurrentLevel个线程并发无阻塞的操作集合对象。关于concurrentLevel稍后介绍。

1、构造方法

为了容易理解,我们先从构造函数说起。ConcurrentHashMap是基于一个叫Segment数组的,其实和Entry类似,如下:

[java]  view plain  copy
  1. public ConcurrentHashMap() 
  2.   { 
  3.     this(16, 0.75F, 16); 
  4.   } 


默认传入值16,调用下面的方法:

[java]  view plain  copy
  1. public ConcurrentHashMap(int paramInt1, float paramFloat, int paramInt2) 
  2.   { 
  3.     if ((paramFloat <= 0F) || (paramInt1 < 0) || (paramInt2 <= 0)) 
  4.       throw new IllegalArgumentException(); 
  5.  
  6.     if (paramInt2 > 65536) { 
  7.       paramInt2 = 65536; 
  8.     } 
  9.  
  10.     int i = 0; 
  11.     int j = 1; 
  12.     while (j < paramInt2) { 
  13.       ++i; 
  14.       j <<= 1; 
  15.     } 
  16.     this.segmentShift = (32 - i); 
  17.     this.segmentMask = (j - 1); 
  18.     this.segments = Segment.newArray(j); 
  19.  
  20.     if (paramInt1 > 1073741824) 
  21.       paramInt1 = 1073741824; 
  22.     int k = paramInt1 / j; 
  23.     if (k * j < paramInt1) 
  24.       ++k; 
  25.     int l = 1; 
  26.     while (l < k) 
  27.       l <<= 1; 
  28.  
  29.     for (int i1 = 0; i1 < this.segments.length; ++i1) 
  30.       this.segments[i1] = new Segment(l, paramFloat); 
  31.   } 


你会发现比HashMap的构造函数多一个参数,paramInt1就是我们之前谈过的initialCapacity,就是数组的初始化大小,paramfloat为loadFactor(装载因子),而paramInt2则是我们所要说的concurrentLevel,这三个值分别被初始化为16,0.75,16,经过:

[java]  view plain  copy
  1. while (j < paramInt2) { 
  2.       ++i; 
  3.       j <<= 1; 
  4.     } 


后,j就是我们最终要开辟的数组的size值,当paramInt1为16时,计算出来的size值就是16.通过:

this.segments = Segment.newArray(j)后,我们看出了,最终稿创建的Segment数组的大小为16.最终创建Segment对象时:

[java]  view plain  copy
  1. this.segments[i1] = new Segment(cap, paramFloat); 

需要cap值,而cap值来源于:

[java]  view plain  copy
  1. int k = paramInt1 / j; 
  2.   if (k * j < paramInt1) 
  3.     ++k; 
  4.   int cap = 1; 
  5.   while (cap < k) 
  6.     cap <<= 1; 

组后创建大小为cap的数组。最后根据数组的大小及paramFloat的值算出了threshold的值:

this.threshold = (int)(paramArrayOfHashEntry.length * this.loadFactor)。

2、put操作

[java]  view plain  copy
  1. public V put(K paramK, V paramV) 
  2.   { 
  3.     if (paramV == null) 
  4.       throw new NullPointerException(); 
  5.     int i = hash(paramK.hashCode()); 
  6.     return segmentFor(i).put(paramK, i, paramV, false); 
  7.   } 


与HashMap不同的是,如果key为null,直接抛出NullPointer异常,之后,同样先计算hashCode的值,再计算hash值,不过此处hash函数和HashMap中的不一样:

[java]  view plain  copy
  1. private static int hash(int paramInt) 
  2.   { 
  3.     paramInt += (paramInt << 15 ^ 0xFFFFCD7D); 
  4.     paramInt ^= paramInt >>> 10; 
  5.     paramInt += (paramInt << 3); 
  6.     paramInt ^= paramInt >>> 6; 
  7.     paramInt += (paramInt << 2) + (paramInt << 14); 
  8.     return (paramInt ^ paramInt >>> 16); 
  9.   } 


 

[java]  view plain  copy
  1. final Segment<K, V> segmentFor(int paramInt) 
  2.   { 
  3.     return this.segments[(paramInt >>> this.segmentShift & this.segmentMask)]; 
  4.   } 


根据上述代码找到Segment对象后,调用put来操作:

[java]  view plain  copy
  1. V put(K paramK, int paramInt, V paramV, boolean paramBoolean) 
  2. { 
  3.   lock(); 
  4.   try { 
  5.     Object localObject1; 
  6.     Object localObject2; 
  7.     int i = this.count; 
  8.     if (i++ > this.threshold) 
  9.       rehash(); 
  10.     ConcurrentHashMap.HashEntry[] arrayOfHashEntry = this.table; 
  11.     int j = paramInt & arrayOfHashEntry.length - 1; 
  12.     ConcurrentHashMap.HashEntry localHashEntry1 = arrayOfHashEntry[j]; 
  13.     ConcurrentHashMap.HashEntry localHashEntry2 = localHashEntry1; 
  14.     while ((localHashEntry2 != null) && (((localHashEntry2.hash != paramInt) || (!(paramK.equals(localHashEntry2.key)))))) { 
  15.       localHashEntry2 = localHashEntry2.next; 
  16.     } 
  17.  
  18.     if (localHashEntry2 != null) { 
  19.       localObject1 = localHashEntry2.value; 
  20.       if (!(paramBoolean)) 
  21.         localHashEntry2.value = paramV; 
  22.     } 
  23.     else { 
  24.       localObject1 = null; 
  25.       this.modCount += 1; 
  26.       arrayOfHashEntry[j] = new ConcurrentHashMap.HashEntry(paramK, paramInt, localHashEntry1, paramV); 
  27.       this.count = i; 
  28.     } 
  29.     return localObject1; 
  30.   } finally { 
  31.     unlock(); 
  32.   } 
  33. } 


先调用lock(),lock是ReentrantLock类的一个方法,用当前存储的个数+1来和threshold比较,如果大于threshold,则进行rehash,将当前的容量扩大2倍,重新进行hash。之后对hash的值和数组大小-1进行按位于操作后,得到当前的key需要放入的位置,从这儿开始,和HashMap一样。

从上述的分析看出,ConcurrentHashMap基于concurrentLevel划分出了多个Segment来对key-value进行存储,从而避免每次锁定整个数组,在默认的情况下,允许16个线程并发无阻塞的操作集合对象,尽可能地减少并发时的阻塞现象。

在多线程的环境中,相对于HashTable,ConcurrentHashMap会带来很大的性能提升!

四、HashMap常见问题分析

1、此处我觉得网友huxb23@126的一篇文章说的很好,分析多线程并发写HashMap线程被hang住的原因 ,因为是优秀的资源,此处我整理下搬到这儿。

以下内容转自博文:http://blog.163.com/huxb23@126/blog/static/625898182011211318854/ 

先看原问题代码:

[java]  view plain  copy
  1. import java.util.HashMap; 
  2.  
  3. public class TestLock { 
  4.  
  5.     private HashMap map = new HashMap(); 
  6.  
  7.     public TestLock() { 
  8.         Thread t1 = new Thread() { 
  9.             public void run() { 
  10.                 for (int i = 0; i < 50000; i++) { 
  11.                     map.put(new Integer(i), i); 
  12.                 } 
  13.                 System.out.println("t1 over"); 
  14.             } 
  15.         }; 
  16.  
  17.         Thread t2 = new Thread() { 
  18.             public void run() { 
  19.                 for (int i = 0; i < 50000; i++) { 
  20.                     map.put(new Integer(i), i); 
  21.                 } 
  22.  
  23.                 System.out.println("t2 over"); 
  24.             } 
  25.         }; 
  26.  
  27.         t1.start(); 
  28.         t2.start(); 
  29.  
  30.     } 
  31.  
  32.     public static void main(String[] args) { 
  33.         new TestLock(); 
  34.     } 
  35. } 


就是启了两个线程,不断的往一个非线程安全的HashMap中put内容,put的内容很简单,key和value都是从0自增的整数(这个put的内容做的并不好,以致于后来干扰了我分析问题的思路)。对HashMap做并发写操作,我原以为只不过会产生脏数据的情况,但反复运行这个程序,会出现线程t1、t2被hang住的情况,多数情况下是一个线程被hang住另一个成功结束,偶尔会两个线程都被hang住。说到这里,你如果觉得不好好学习ConcurrentHashMap而在这瞎折腾就手下留情跳过吧。 
好吧,分析下HashMap的put函数源码看看问题出在哪,这里就罗列出相关代码(jdk1.6):

[java]  view plain  copy
  1. public V put(K paramK, V paramV) 
  2. { 
  3.   if (paramK == null) 
  4.     return putForNullKey(paramV); 
  5.   int i = hash(paramK.hashCode()); 
  6.   int j = indexFor(i, this.table.length); 
  7.   for (Entry localEntry = this.table[j]; localEntry != null; localEntry = localEntry.next) 
  8.   { 
  9.     if (localEntry.hash == i) { java.lang.Object localObject1; 
  10.       if (((localObject1 = localEntry.key) == paramK) || (paramK.equals(localObject1))) { 
  11.         java.lang.Object localObject2 = localEntry.value; 
  12.         localEntry.value = paramV; 
  13.         localEntry.recordAccess(this); 
  14.         return localObject2; 
  15.       } 
  16.     } 
  17.   } 
  18.   this.modCount += 1; 
  19.   addEntry(i, paramK, paramV, j); 
  20.   return null; 
  21. } 
  22.  
  23. private V putForNullKey(V paramV) 
  24. { 
  25.   for (Entry localEntry = this.table[0]; localEntry != null; localEntry = localEntry.next) 
  26.     if (localEntry.key == null) { 
  27.       java.lang.Object localObject = localEntry.value; 
  28.       localEntry.value = paramV; 
  29.       localEntry.recordAccess(this); 
  30.       return localObject; 
  31.     } 
  32.  
  33.   this.modCount += 1; 
  34.   addEntry(0, null, paramV, 0); 
  35.   return null; 
  36. } 

 

通过jconsole(或者thread dump),可以看到线程停在了transfer方法的while循环处。这个transfer方法的作用是,当Map中元素数超过阈值需要resize时,它负责把原Map中的元素映射到新Map中。我修改了HashMap,加上了@标记2和@标记3的代码片断,以打印出死循环时的状态,结果死循环线程总是出现类似这样的输出:“Thread-1,e==next:false,e==next.next:true,e:108928=108928,next:108928=108928,eq:true”。 
这个输出表明: 
1)这个Entry链中的两个Entry之间的关系是:e=e.next.next,造成死循环。 
2)e.equals(e.next),但e!=e.next。因为测试例子中两个线程put的内容一样,并发时可能同一个key被保存了多个value,这种错误是在addEntry函数产生的,但这和线程死循环没有关系。

接下来就分析transfer中那个while循环了。先所说这个循环正常的功能:src[j]保存的是映射成同一个hash值的多个Entry的链表,这个src[j]可能为null,可能只有一个Entry,也可能由多个Entry链接起来。假设是多个Entry,原来的链是(src[j]=a)->b(也就是src[j]=a,a.next=b,b.next=null),经过while处理后得到了(newTable[i]=b)->a。也就是说,把链表的next关系反向了。

再看看这个while中可能在多线程情况下引起问题的语句。针对两个线程t1和t2,这里它们可能的产生问题的执行序列做些个人分析:

1)假设同一个Entry列表[e->f->...],t1先到,t2后到并都走到while中。t1执行“e.next = newTable[i];newTable[i] = e;”这使得e.next=null(初始的newTable[i]为null),newTable[i]指向了e。这时t2执行了“e.next = newTable[i];newTable[i] = e;”,这使得e.next=e,e死循环了。因为循环开始处的“final Entry next = e.next;”,尽管e自己死循环了,在最后的“e = next;”后,两个线程都会跳过e继续执行下去。

2)在while中逐个遍历Entry链表中的Entry而把next关系反向时,newTable[i]成为了被交换的引用,可疑的语句在于“e.next = newTable[i];”。假设链表e->f->g被t1处理成e<-f<-g,newTable[i]指向了g,这时t2进来了,它一执行“e.next = newTable[i];”就使得e->g,造成了死循环。所以,理论上来说,死循环的Entry个数可能很多。尽管产生了死循环,但是t1执行到了死循环的右边,所以是会继续执行下去的,而t2如果执行“final Entry next = e.next;”的next为null,则也会继续执行下去,否则就进入了死循环。

3)似乎情况会更复杂,因为即便线程跳出了死循环,它下一次做resize进入transfer时,有可能因为之前的死循环Entry链表而被hang住(似乎是一定会被hang住)。也有可能,在put检查Entry链表时(@标记1),因为Entry链表的死循环而被hang住。也似乎有可能,活着的线程和死循环的线程同时执行在while里后,两个线程都能活着出去。所以,可能两个线程平安退出,可能一个线程hang在transfer中,可能两个线程都被hang住而又不一定在一个地方。

4)我反复的测试,出现一个线程被hang住的情况最多,都是e=e.next.next造成的,这主要就是例子put两份增量数据造成的。我如果去掉@标记3的输出,有时也能复现两个线程都被hang住的情况,但加上后就很难复现出来。我又把put的数据改了下,比如让两个线程put范围不同的数据,就能复现出e=e.next,两个线程都被hang住的情况。

上面罗哩罗嗦了很多,一开始我简单的分析后觉得似乎明白了怎么回事,可现在仔细琢磨后似乎又不明白了许多。有一个细节是,每次死循环的key的大小也是有据可循的,我就不打哈了。感觉,如果样本多些,可能出现问题的原因点会很多,也会更复杂,我姑且不再蛋疼下去。至于有人提到ConcurrentHashMap也有这个问题,我觉得不大可能,因为它的put操作是加锁的,如果有这个问题就不叫线程安全的Map了。

2、HashMap中Value可以相同,但是键不可以相同

当插入HashMap的key相同时,会覆盖原有的Value,且返回原Value值,看下面的程序:

[java]  view plain  copy
  1. public class Test { 
  2.  
  3.     public static void main(String[] args) { 
  4.          
  5.         HashMap<String,Integer> map = new HashMap<String,Integer>(); 
  6.  
  7.         //出入两个Value相同的值,没有问题 
  8.         map.put("egg", 1); 
  9.         map.put("niu", 1); 
  10.          
  11.         //插入key相同的值,看返回结果 
  12.         int egg = (Integer) map.put("egg", 3); 
  13.          
  14.         System.out.println(egg);   //输出1 
  15.         System.out.println(map.get("egg"));   //输出3,将原值1覆盖 
  16.         System.out.println(map.get("niu"));   //输出1 
  17.     } 
  18. } 

相同的键会被覆盖,且返回原值。

3、HashMap按值排序

给定一个数组,求出每个数据出现的次数并按照次数的由大到小排列出来。我们选用HashMap来做,key存储数组元素,值存储出现的次数,最后用Collections的sort方法对HashMap的值进行排序。代码如下:

[java]  view plain  copy
  1. public class Test { 
  2.  
  3.     public static void main(String[] args) { 
  4.  
  5.         int data[] = { 2, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2, 
  6.                 7, 8, 8, 7, 8, 7, 9, 0 }; 
  7.         Map<Integer, Integer> map = new HashMap<Integer, Integer>(); 
  8.         for (int i : data) { 
  9.             if (map.containsKey(i)) {//判断HashMap里是否存在 
  10.                 map.put(i, map.get(i) + 1);//已存在,值+1 
  11.             } else { 
  12.                 map.put(i, 1);//不存在,新增 
  13.             } 
  14.         } 
  15.         //map按值排序 
  16.         List<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer, Integer>>( 
  17.                 map.entrySet()); 
  18.         Collections.sort(list, new Comparator<Map.Entry<Integer, Integer>>() { 
  19.             public int compare(Map.Entry<Integer, Integer> o1, 
  20.                     Map.Entry<Integer, Integer> o2) { 
  21.                 return (o2.getValue() - o1.getValue()); 
  22.             } 
  23.         }); 
  24.         for (Map.Entry<Integer, Integer> m : list) { 
  25.             System.out.println(m.getKey() + "-" + m.getValue()); 
  26.         } 
  27.     } 
  28.  
  29. } 

输出:

2-6 
5-5 
3-4 
8-3 
7-3 
9-1 
0-1

【异常】Cursor编辑器 “Taking longer than expected...“ 报错全链路排查与解决方案 该提示并非传统的程序崩溃或语法报错,本质是Cursor编辑器的超时预警机制——当核心任务(云端AI推理请求、本地项目索引等)的执行耗时超出系统预设阈值时,触发的用户告知类提示,核心诱因是任务执行全链路出现阻塞或性能瓶颈。以下方案按排查优先级从高到低排序,覆盖从快速急救到根因根治的全流程,可按步骤逐步验证解决。针对大仓库、多依赖项目的索引超时问题,核心是通过规则过滤无效文件,从根源降低索引负载。 阅读详情

相关推荐

从原理到调试:手把手教你用Matlab解码互补格雷码与相移码(附数据与常见错误排查)

本文详细介绍了如何使用Matlab解码互补格雷码与相移码,涵盖从原理到调试的全过程。通过实际案例和代码示例,帮助读者掌握结构光三维重建中的相位解包裹技术,并提供了常见错误排查与优化建议,提升解码效率与准确性。

weixin_29090917的博客 219

ConcurrentHashMap、Hashtable、HashMap

一、区别总结 1️⃣HashMap 是非线程安全的,不适用于多线程资源共享场景,在单线程场景下性能最好。在多线程环境中,需要手动实现同步机制。HashMap 中,null 可以作为键,这样的键只有一个,但可以有一个或多个键所对应的值为 null。当 get() 返回 null 时,既可以表示 HashMap 中没有该 key,也可以表示该 key 所对应的 value 为 null。因此,在 HashMap 中不能用 get() 来判断是否存在某个 key,应该用 containsKey() 来判断。 2️

ChineseSoftware的博客 689

FreeSWITCH实战:5分钟搞定阿里云SDM(MRCP-SERVER)语音机器人搭建(含Docker部署技巧)

本文详细介绍了如何利用FreeSWITCH与阿里云SDM(MRCP-SERVER)快速搭建高可用语音机器人,包括Docker部署技巧、系统调优及高可用架构设计。通过实战案例和优化建议,帮助开发者5分钟内完成部署,提升语音交互系统的性能和稳定性。

stone的博客 504

HashMap,Hashtable,ConcurrentHashMap

Hashtable、ConcurrentHashMap和HashMap之间的一些区别。

weixin_56738054的博客 1535

泛微E9表单建模查询列表勾选后带入流程明细表中

注意:参照下图各项配置,否则会有问题。

泛微二次开发后端知识总结 5309

基于STM32的浴室环境智能控制系统设计

摘要:本文设计了一款基于STM32F103的智能卫浴系统,通过多传感器协同检测浴室环境参数。系统采用DS18B20检测环境与卫浴温度,DHT11监测湿度,MQ-135检测空气质量,光敏电阻测量光照强度,D203S人体热释电模块检测人员存在。当检测到异常参数时,系统自动启动相应调节功能:温度异常时控制加热/制冷继电器,湿度过高时启动通风除湿,空气质量差时进行换气,光照不足且有人时自动开灯。系统支持按键、蓝牙APP和语音三种控制方式,可手动设置参数阈值、切换防雾模式、控制音乐播放等功能。实验结果表明,该系统能有

3254968421的博客 483

HashTable HashMap ConcurrentHashMap 的介绍以及区别

本文主要是介绍HashMap HashTable 以及ConcurrentHashMap三者,以及这三者之间的区别

qq_54469537的博客 1819

作物生长模型Oryza V3实战2:下载、安装与使用

摘要:ORYZA 现在的应用范围已从单一水稻作物模拟扩展到多个水稻作物模拟。我在csdn站内也上传了“作物生长模型oryza v3大全(含所有参数模板和4个校准程序1个分析工具)”,可以说是全网最全的吧。网上搜索一般下载的v3只有一个a3主程序,不适合作全面与深入的研究,本版本从国外下载回来,一开始少了一个“SoilHydrau.exe”文件,说是有病毒被隔离了,后经历各种技术手段,并向国际水道研究所发送邮件,才得以补全所有的参数化及校准程序、分析工具,来之不易,有缘人得之。

502

HashMap、Hashtable、ConcurrentHashMap的区别

HashMap、Hashtable、ConcurrentHashMap是日常开发中使用频率较高的数据结构,它们都是以key-value的形式来存储数据,且都实现了Map接口,日常开发中很多人对其三者之间的区别并没有十分清晰的概念。本文将剖析部分源码,以及从线程安全性、速度等方面来分析三者之间的区别。首先讲下三者的一些区别:1.HashMap与Hashtable基本上等价,区别在于Hashtable的大部分方法都是被synchronized修饰,并且键值都不能为null(HashMap则可以);...

qq_33479841的博客 6064

Android P车机添加屏幕多指全局自定义手势操作

参照原生状态栏全局下拉手势拦截,实现屏幕全局多指手势响应操作,并对手势进行转换为KeyEvent,以便项目自定义手势行为或应用拦截手势行为。

shusuanly的博客 1251

60、拓扑码:从理论到应用

本博客深入探讨了拓扑码的基本理论、构造方法及其在量子计算中的应用。重点介绍了颜色码的拓扑起源、编码量子比特的计算方法、弦算子的构造与性质、边界条件的影响、同调描述以及与统计模型相关的错误阈值分析。同时,还涵盖了拓扑码的历史发展、与其他拓扑码的关系、与凝聚态系统的联系,以及其在激发态和高维中的表现。博客展示了拓扑码在容错量子计算和未来量子信息领域的广阔前景。

o1p2q3r的博客 129

HashMap、HashTable、ConcurrentHashMap使用和原理分析(以及内存优化)

HashMap使用和原理分析(以及内存优化) HashMap:通过链地址法存放键值对的一种数据结构 链地址法:(开散列方法):设散列表地址空间的位置从0~m-1,则通过对所有的Key用散列函数计算出存放的位置,具有相同地址的关键码归于一个子集合(桶),采用链表的方式链接起来(链接头)。 如Hash函数为 Hash(x) = x%16 则 Hash(2) = 2; Hash(18)=2......

5ingwings(SiHao)的博客 9727

Claude Code 完整详解(Anthropic 官方 AI 编程 Agent)

它是运行在终端(Terminal)的 AI 编程代理,不是普通 IDE 插件;你用自然语言发指令,它自主规划步骤、读写项目文件、执行系统命令、排查 Bug、处理 Git 工作流,是能真正动手干活的 AI 程序员。真正的AI 开发代理人,不止写代码,还能执行开发全流程超长全局上下文,大型项目理解碾压传统补全工具终端原生 + IDE 双接入,开发流程无割裂代码质量、可读性、工程规范性普遍偏高权限安全机制完善,修改前预览、可随时中断回滚。

wange6906的博客 3155

[Java集合] 彻底搞懂HashMap,HashTable,ConcurrentHashMap之关联.

[Java集合] 彻底搞懂HashMap,HashTable,ConcurrentHashMap之关联. 注: 今天看到的一篇讲hashMap,hashTable,concurrentHashMap很透彻的一篇文章, 感谢原作者的分享.原文地址:http://blog.youkuaiyun.com/zhangerqing/article/details/8193118Jav...

weixin_39941921的博客 112

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

[Java集合] 彻底搞懂HashMap,HashTable,ConcurrentHashMap之关联(各大公司面试经常遇到)

注: 今天看到的一篇讲hashMap,hashTable,concurrentHashMap很透彻的一篇文章, 感谢原作者的分享.  原文地址: http://blog.youkuaiyun.com/zhangerqing/article/details/8193118  Java集合类是个非常重要的知识点,HashMap、HashTable、ConcurrentHashMap等算是集合类中的重点,可

六月的尾巴 909

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Java集合——HashMap、HashTable以及ConCurrentHashMap异同比较

HashMap和HashTable的区别一种比较简单的回答是: (1)HashMap是非线程安全的,HashTable是线程安全的。 (2)HashMap的键和值都允许有null值存在,而HashTable则不行。 (3)因为线程安全、哈希效率的问题,HashMap效率比HashTable的要高。 但是如果继续追问Java中的另一个线程安全的与HashMap功能极其类似的类是什么?同样是线程安全,它与HashTable在线程同步上有什么不同……

关于Android开发的一些技术点总结 ╮( ̄▽ ̄”)╭ 3万+

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

HashMap、Hashtable和ConcurrentHashMap的区别?

HashMap:非线程安全,适合单线程或自行处理同步的场景,允许null键和值。Hashtable:线程安全,但性能较差,已经不推荐使用,不允许null键和值。:线程安全,采用分段锁机制,适合高并发场景,不允许null键和值,性能优于Hashtable。是现代 Java 开发中并发场景下的最佳选择,它结合了高性能和线程安全的优点,在高并发环境下提供了优异的表现。

m0_73154147的博客 1869

Java中的Map集合:从HashMap到ConcurrentHashMap

在Java集合框架中,Map接口占据着重要的位置。Map用于存储键值对(key-value pairs),其中每个键都是唯一的。Map接口定义了一系列操作这些键值对的方法,如添加、删除、获取等。: 添加键值对: 根据键获取值: 移除键值对: 检查是否包含某个键: 检查是否包含某个值size(): 返回Map的大小isEmpty(): 检查Map是否为空Java提供了丰富的Map实现,每种实现都有其特定的用途和性能特征。在选择使用哪种Map实现时,需要考虑以下因素:是否需要线程安全。

2303_76892351的博客 1205

彻底搞懂HashMap,HashTable,ConcurrentHashMap之关联

Java集合类是个非常重要的知识点,HashMap、HashTable、ConcurrentHashMap等算是集合类中的重点,可谓“重中之重”,首先来看个问题,如面试官问你:HashMap和HashTable有什么区别,一个比较简单的回答是: 1、HashMap是非线程安全的,HashTable是线程安全的。 2、HashMap的键和值都允许有null值存在,而HashTable则不行。 3、因...

春来桃花开的博客 367

ConcurrentHashMap、HashMap和HashTable

ConcurrentHashMap、HashMap和HashTable的底层实现、区别以及扩容原理

qw2869534823的博客 1675

HashMap、HashTable、ConcurrentHashMap详解

HashMap 是 Java 集合框架中最常用的非线程安全哈希表,实现了Map接口,基于 “数组 + 链表 / 红黑树” 的混合结构(JDK 1.8 优化),核心目标是高效查询、插入、删除(理想时间复杂度 O (1))。// 核心继承关系HashTable 是 Java 早期提供的线程安全哈希表,实现了Map接口,底层结构与 JDK 1.7 的 HashMap 类似(数组 + 链表,无红黑树优化),但因性能低下,目前已被替代,仅在遗留系统中可能见到。// 核心继承关系。

qq_74156239的博客 791

HashTable、HashMap 和 ConcurrentHashMap 之间的区别

HashTable:线程安全,但性能较差,已逐渐被淘汰,建议仅在需要兼容旧代码时使用。HashMap:非线程安全,性能优异,适用于单线程环境。:线程安全,性能优越,适用于高并发场景。通过理解这三种集合的区别,我们可以根据实际需求选择合适的工具,从而提升程序的性能和可靠性。

m0_74750751的博客 1284

彻底解密 Java 集合框架:HashMap、Hashtable 与 ConcurrentHashMap 的生死较量与底层奥秘!

在 Java 集合框架的璀璨星空中,HashMap 和 ConcurrentHashMap 无疑是最亮眼的两颗星。它们都是键值对存储的利器,但一个适用于单线程高速场景,另一个则在并发洪流中屹立不倒。深入理解它们的底层实现、数据结构和扩容原理,不仅能让你在面试中脱颖而出,更能助你写出高效、健壮的 Java 代码。

yiridancan的博客 963

HashTable·HashMap·ConcurrentHashMap 之间的区别

HashMap是基于哈希表实现的,通过哈希函数将键映射到索引位置,实现快速查找。与HashTable不同,HashMap允许使用null作为键和值。HashMap不是线程安全的,因此在多线程环境下使用时需求注意数据一致性问题。1.2工作原理与实现HashMap使用哈希函数将键转换为索引,该函数需要满足确定性,高效性和散列性。采用链地址法处理哈希冲突,即多个键哈希到同一个索引时,它们会被链接到一个链表中。

2302_81921490的博客 891

Hashtable,HashMap,ConcurrentHashMap之间的区别

本文对比分析了Hashtable、HashMap和ConcurrentHashMap的核心特性。Hashtable采用全局锁保证线程安全但性能低;HashMap非线程安全但单线程性能优异;ConcurrentHashMap通过细粒度锁机制实现高并发性能。三者在空值支持、继承关系等方面也存在差异:Hashtable和ConcurrentHashMap不允许null键值,而HashMap允许;继承关系上分别源自Dictionary、AbstractMap等不同父类。使用场景建议:单线程用HashMap,高并发用

huohaiyu的博客 861
上一篇: Java中的多线程
下一篇: 深入学习 Java 序列化
hambition
博客等级 码龄19年 34粉丝 2原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值