java源码剖析之HashMap

【Java集合源码剖析】HashMap源码剖析 HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。 HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。 HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。 阅读详情

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

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

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

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

能答出上面的三点,简单的面试,算是过了,但是如果再问:Java中的另一个线程安全的与HashMap极其类似的类是什么?同样是线程安全,它与HashTable在线程同步上有什么不同?能把第二个问题完整的答出来,说明你的基础算是不错的了。带着这个问题,本章开始java源码之HashMap。

总想在文章的开头说点儿什么,但又无从说起。从最近的一些面试说起吧,感受就是:知识是永无止境的,永远不要觉得自己已经掌握了某些东西。如果对哪一块知识感兴趣,那么,请多多的花时间,哪怕最基础的东西也要理解它的原理,尽量往深了研究,在学习的同时,记得多与大家交流沟通,因为也许某些东西,从你自己的角度,是很难发现的,因为你并没有那么多的实验环境去发现他们。只有交流的多了,才能及时找出自己的不足,才能认识到:“哦,原来我还有这么多不知道的东西!”。



一、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;  //一直判断指定容量与开辟容量的大小,当指定容量小时,则开辟容量大小变为原来的2倍


上面,该代码的意思是,实际的开辟的空间要大于传入的第一个参数的值。举个例子:
new HashMap(7,0.8),loadFactor为0.8,capacity为7,通过上述代码后,capacity的值为:8.。所以,最终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);  //当key为null时,调用的方法
  4.         int hash = hash(key.hashCode());  //先算key的hashCode值,然后计算hash值,赋予hash
  5.         int i = indexFor(hash, table.length);  //根据hash和开辟的数组大小计算出数据在数组中存放的位置。
  6. //当前hash值位置有数据时,把旧数据赋予e临时存放起来
  7.         for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  8.             Object k;  
  9. //当新数据的hash值与旧数据的hash值相等,且新数据的key与旧数据的key逻辑上相等时,则用新数据替换旧数据,
  10. 返回旧数据的value
  11.             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  12.                 V oldValue = e.value;  
  13.                 e.value = value;  
  14.                 e.recordAccess(this);  
  15.                 return oldValue;  
  16.             }  
  17.         }
  18.         modCount++;  
  19. //把新数据的hash值,key,value,存储位置传入addEntry方法,添加新元素
  20.         addEntry(hash, key, value, i);  
  21.         return null;  
  22.     }</p>  


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

[java]  view plain copy
  1. private V putForNullKey(V value) {  
  2. //如果key为null所在位置有元素,则判断旧数据的key是否是null,为null则用新数据替换旧数据
  3. ,否则调用addEntry方法
  4.         for (Entry<K,V> e = table[0]; e != null; e = e.next) {  
  5.             if (e.key == null) {  
  6.                 V oldValue = e.value;  
  7.                 e.value = value;  
  8.                 e.recordAccess(this);  
  9.                 return oldValue;  
  10.             }  
  11.         }  
  12.         modCount++;  
  13. //调用addEntry方法,此处0为hash值,key为null的hash值计算后是0,null为key,插入数组0号位置
  14.         addEntry(0, null, value, 0);  
  15.         return null;  
  16.     }  


就是说,获取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类的一个方法,为本地方法,内部实现比较复杂。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数组中的位置,其实不管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!


此处附一个测试程序:

public class Chen {//测试作为map的key的对象,重写hashCode与equals方法,让所有chen对象的hashCode值都
相等,但是equals都返回false;,为了在map开辟的数组中构造链表结构,当hashcode一样,equals返回false的情况下,所有chen对象都存放在同一个数组位置,通过链表的形式存放不同的chen 对象。
	private int id;
	private String name;

	@Override
	public int hashCode() {
		return 32;
	}

	@Override
	public boolean equals(Object obj) {
		return false;

}

}

Map<Chen, String> map = new HashMap<Chen, String>();

map.put(null, "232"); map.put(null, "456");

//此处456代替了232,存放在key为null的数组为0的位置 Chen chen = new Chen(); chen.setId(1); chen.setName("chen"); map.put(chen, "gang"); Chen chen2 = new Chen(); chen2.setId(1); chen2.setName("chen2"); map.put(chen2, "gang2"); Chen chen3 = new Chen(); chen3.setId(1); chen3.setName("chen3"); map.put(chen3, "gang3"); //此处3个chen对象,都存放在hash值为34,数组下标为2的位置,3个对象间通过链表的方式连接在一起,头结点存放chen3,chen3的next指针指向chen2,chen2的next指针指向chen, System.out.println(map.get(chen)); System.out.println(map.get(null));


二、HashTable的内部存储结构

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

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

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

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

分析Hashtable类的put方法:

public synchronized V put(K key, V value) {//////方法是线程安全的,使用synchronized关键字修饰 // Make sure the value is not null if (value == null) {/////////////////////如果value是null,则直接报空指针 throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry tab[] = table;

/////计算hash值的方法与HashMap不同,Hashtable是直接调用object类的hashCode方法,HashMap是调用完object类的hashCode方////法后,在调用自己的hash方法,代码如下:

////static int hash(int h) { //// h ^= (h >>> 20) ^ (h >>> 12); /// return h ^ (h >>> 7) ^ (h >>> 4); /// }//这样做比只调用hashCode方法先进,优化。他使hash值更加分布均匀。 int hash = key.hashCode();/////////////////////如果key是null,则直接报空指针 int index = (hash & 0x7FFFFFFF) % tab.length;/////////计算存放在数组中的位置,注意,此处与HashMap

///的计算方法有点不同,HashMap的计算方法是 i = h & (length-1); for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { V old = e.value; e.value = value; return old; } } modCount++; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. Entry<K,V> e = tab[index]; tab[index] = new Entry<K,V>(hash, key, value, e); count++; return null; }


三、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


C++哈希表的原理和使用 目录1、概念2、散列表的构造方法2.1、直接定址法 1、概念 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,每个关键字key对应一个存储位置f(key),查找时,根据这个对应的关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。采用散列技术将记录存储在一块存储空间中,这块连续空间称为散列表或哈希表(Hash-Table)。 2、散列表的构造方法 2.1、直接定址法 直接定址法使用下 阅读详情

相关推荐

Java集合底层源码剖析-HashMap

全面详细介绍Java中HashMap的原理,包括源码层面的剖析

program哲学 687

JAVA中Map的使用:

JAVA中Map的使用: 定义: 1.key为整型,value为整形: Map<Integer, Integer> hashTable=new HashMap<Intger,Intger>(); 2.key为整型,value为字符: Map<Integer, Character>hashTable = new HashMap<Integer,Character>(); 进行存储时:hashTable.put(key,value);,例如以 2.key为整型

zml的博客 936

别再为格式发愁:safetensors转GGUF的三种高效方法及Ollama集成对比

本文详细介绍了将safetensors格式转换为GGUF格式的三种高效方法,包括llama.cpp原生转换、GGUF官方工具链和Transformers中转方案,并对比了它们在Ollama集成中的表现。文章还提供了实际项目中的踩坑经验和性能调优建议,帮助开发者快速掌握模型转换技术。

weixin_42651281的博客 641

两数之和-力扣第1题完整解析(Java)

Java实现:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

weixin_48240630的博客 849

DEV-C++调试设置

       昨天用C/C++去考了CSP,结果用DEV-C++调试总是闪退,按照我的记忆打开了调试参数,迷迷糊糊搞了一通,还是没办法调试,难受了几个小时。所以今天痛下决心,一定要写下DEV-C++设置调试的过程,警示自己! 解决步骤 1.打开DevC++找到上方工具栏 2.点击选择编译选项,再按下图所示设置即可

懒羊羊的快乐小屋 1029

【TM1640】STM32 TM1640芯片驱动程序

以下为.h文件: #ifndef __TM1640_H__ #define __TM1640_H__ #include "stm32f10x.h" #define ADDR_START1640 0XC0 #define DSP1640_DIS 0 #define DSP1640_ENB 1 /* 控制显示 ...

cxw312864660的博客 1万+

java之哈希表的应用详情

定义 HashMap<Integer, String> Sites = new HashMap<Integer, String>(); Java HashMap 方法 hashmap Java HashMap 常用方法列表如下: 方法 描述 clear() 删除 hashMap 中的所有键/值对 clone() 复制一份 hashMap isEmpty() 判断 hashMap 是否为空 size() 计算 hashMap 中键/值对的数量 put() 将键/值对添加到 .

Mr. Ding的小博客 686

《UDS协议从入门到精通》系列——到底什么是DTC?

到底什么是DTC?看这里,深度图解DTC格式,详述DTC状态位、快照信息和扩展信息!!

车载系统攻城狮 1万+

深度揭秘 Java HashMap:从源码剖析其使用原理

HashMap是 Java 集合框架中的一员,它实现了Map接口,用于存储键值对。HashMap允许使用null作为键和值,并且不保证元素的顺序。它基于哈希表(Hash Table)实现,通过哈希函数将键映射到数组的特定位置,从而实现快速的数据存储和查找。// HashMap 类继承自 AbstractMap 类,并实现了 Map、Cloneable 和 Serializable 接口// 序列号,用于序列化和反序列化// 默认的初始容量,必须是 2 的幂次方// 16。

Android开发领域创作者 1135

问答式AI vs 智能体(Agent):它们有什么不同?

你肯定用过 ChatGPT 或文心一言——问它一个问题,它给你一个答案。AI Agent(AI智能体)。很多人搞不清楚:它们不都是"AI"吗?有什么区别?这篇文章用一个最简单的类比,彻底讲清楚这两者的本质差异。框架特点适合人群AutoGPT完全自主执行任务,链式思考开发者MetaGPT模拟团队协作,多角色分工开发者LangChain最流行的Agent开发框架开发者Dify可视化无代码搭建Agent非技术用户Coze(扣子)字节跳动出品,国内最易用普通用户WorkBuddy。

Johnny2004的专栏 449

Python爬虫实战:基于Playwright与异步技术的专利数据抓取系统

专利数据是企业技术创新、市场竞争分析和行业趋势预测的关键情报来源。然而,专利数据通常分散在各个国家的专利局网站,数据结构复杂,反爬机制严格,使得高效抓取成为技术挑战。本文将详细介绍如何构建一个基于最新Python技术的专利数据抓取系统,涵盖Selenium的现代化替代方案Playwright、异步编程、智能代理轮换等高级技术。

2201_76125261的博客 1563

Java集合---HashMap源码剖析

http://www.cnblogs.com/ITtangtang/p/3948406.html

u013291972的博客 230

游戏大厂状态同步与帧同步技术全解析——让虚拟世界万物协同的“时间齿轮”

本文深入解析了大厂游戏开发中的两种核心同步技术:状态同步和帧同步。状态同步适合大型MMO和FPS游戏,由服务端计算并广播世界状态;帧同步则通过统一操作输入实现MOBA和RTS游戏的绝对公平。文章详细比较了两者的差异、适用场景和底层实现原理,并揭秘了《王者荣耀》《PUBG》等热门游戏的同步机制。同时探讨了本地预测、延迟补偿、防作弊等关键技术,以及未来AI和云游戏带来的同步新挑战,为开发者提供了全面的技术参考和实践指南。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 923

Java HashMap源码剖析

字面上看,HashMap由Hash和Map两个单词组成,Map表示映射关系,是一个接口,实现Map接口有多种方式,HashMap实现的方式利用了Hash。本文先分析Map接口,接着分析HashMap实现原理,最后总结分析HashMap的特点。

cangshanjiang的博客 1623

UNet 改进(27):结合SCSE注意力机制的UNet网络实现

本文介绍了一种结合SCSE注意力机制的改进UNet网络架构。该网络在保持UNet经典编码器-解码器结构基础上,通过在每层添加SCSEBlock模块,融合了通道注意力和空间注意力机制。通道注意力通过全局池化和1×1卷积生成通道权重,空间注意力则产生空间权重图,二者相加增强关键特征响应。该设计具有以下优势:1)保持原始UNet的多尺度特征提取能力;2)通过注意力机制提升对重要区域的关注度;3)计算效率高,仅增加少量参数。测试显示网络能保持输入输出分辨率不变,适用于医学图像分割等像素级预测任务。完整代码实现了5层

2401_82355416的博客 415

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

【java集合框架源码剖析系列】java源码剖析之HashMap

前言:之所以打算写java集合框架源码剖析系列博客是因为自己反思了一下阿里内推一面的失败(估计没过,因为写此博客已距阿里巴巴一面一个星期),当时面试完之后感觉自己回答的挺好的,而且据面试官最后说的这几天可能会和你联系来看当时以为自己一面应该是通过的,但是事与愿违,痛定思痛,仔细回顾了一下面试官问我的整个过程,感兴趣的可以参看我的博客:【阿里内推一面】记我人生的处女面。感觉自己回答的不是很好的地方就

(* ̄︶ ̄) 1万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

java HashMap源码剖析

HashMap 是 Java 集合框架中的一个重要类,它基于哈希表实现,提供了快速的插入、删除和查找操作。 以下是一些关键点: 序列化:HashMap 类实现了 Serializable 接口,这意味着它可以被序列化和反序列化。 初始容量和负载因子:HashMap 有一个初始容量(默认为 16)和一个负载因子(默认为 0.75),这些参数影响哈希表的扩展和收缩行为。 哈希函数:HashMap 使用一个哈希函数来计算键的哈希码,并将其映射到哈希表的索引上。 链表和红黑树:

qq_36070104的博客 744

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

深度学习工具包 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源码剖析

看了几篇发现这篇是最好的!

m0_45210394的博客 819

Java集合之HashMap源码剖析(jdk1.8)

Java集合之HashMap源码剖析(jdk1.8) 文章目录Java集合之HashMap源码剖析(jdk1.8)1、简介2、源码分析1、属性2、内部类3、构造方法4、核心方法1、put方法2、resize方法3、get方法4、remove方法5、replace方法6、其他方法3、总结 1、简介 HashMap是较为常用的一种集合,底层结构较为复杂。 由数组+链表+红黑树组成。 HashMap使用...

cgl_dong的博客 220

深入剖析Java HashMap核心原理:聚焦扩容机制与源码实现

HashMap作为Java集合框架中高频使用的数据结构,其高效的查找性能背后隐藏着精妙的设计。理解其底层实现原理,特别是‌扩容机制‌,对优化程序性能、避免踩坑具有重要意义。本文将以JDK1.8源码为基础,深入解析HashMap的核心机制。深入理解HashMap的扩容机制,能帮助我们更好地优化程序性能,避免生产环境中的潜在问题。在面试和实际开发中,这都是体现Java功底的重要知识点。建议结合源码实践调试,加深对数据迁移过程的理解。

weixin_42593797的博客 807

Java 集合面试题从数据结构到 HashMap 源码剖析详解及常见考点梳理

摘要 Java集合框架是开发中的核心知识,本文从基础概念到源码层面进行了系统解析。首先介绍了Collection和Map两大接口体系,对比了ArrayList、LinkedList、HashSet等实现类的特点。重点剖析了HashMap的底层数据结构演变:JDK1.7采用数组+链表结构,而JDK1.8优化为数组+链表+红黑树组合,有效提升了查询性能。详细解读了HashMap的关键源码实现,包括哈希计算、扩容机制等核心流程。最后通过订单管理系统的实际案例,展示了HashMap的高效应用场景。全文既有理论深度,

sss191s的博客 1021

Java集合源码剖析之HashMap

Java集合源码剖析之HashMap1 HashMap概括1.1 特性1.2 数据结构2 HashMap源码剖析2.1 继承关系2.2 元素节点2.3 重要属性2.4 构造方法2.5 核心方法2.5.1 添加元素2.5.2 查询元素2.5.3 修改元素2.5.4 删除元素2.5.5 其它方法 1 HashMap概括 1.1 特性 HashMap中存储的元素都是<key, value>键...

Java大数据联盟 300
上一篇: SpringSecurity入门
下一篇: SpringSide 3 中的安全框架
javaACMer
博客等级 码龄14年 15粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值