/** * The default initial capacity - MUST be a power of two. */ // 初始化容量 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ // 最大容量 static final int MAXIMUM_CAPACITY = 1 << 30;
/** * The load factor used when none specified in constructor. */ // 负载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f;
/** * The bin count threshold for using a tree rather than list for a * bin. Bins are converted to trees when adding an element to a * bin with at least this many nodes. The value must be greater * than 2 and should be at least 8 to mesh with assumptions in * tree removal about conversion back to plain bins upon * shrinkage. */ // 链表转树的阈值 static final int TREEIFY_THRESHOLD = 8;
/** * The bin count threshold for untreeifying a (split) bin during a * resize operation. Should be less than TREEIFY_THRESHOLD, and at * most 6 to mesh with shrinkage detection under removal. */ // 树转链表的阈值 static final int UNTREEIFY_THRESHOLD = 6;
/** * The smallest table capacity for which bins may be treeified. * (Otherwise the table is resized if too many nodes in a bin.) * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts * between resizing and treeification thresholds. */ // 最小树化的容量大小 static final int MIN_TREEIFY_CAPACITY = 64;
/** * The table, initialized on first use, and resized as * necessary. When allocated, length is always a power of two. * (We also tolerate length zero in some operations to allow * bootstrapping mechanics that are currently not needed.) */ // 桶数组 transient Node<K,V>[] table;
/** * Holds cached entrySet(). Note that AbstractMap fields are used * for keySet() and values(). */ // 调用hashmap.entrySet()的结果缓存。 transient Set<Map.Entry<K,V>> entrySet;
/** * The number of key-value mappings contained in this map. */ // 存放数据的个数 transient int size;
/** * The number of times this HashMap has been structurally modified * Structural modifications are those that change the number of mappings in * the HashMap or otherwise modify its internal structure (e.g., * rehash). This field is used to make iterators on Collection-views of * the HashMap fail-fast. (See ConcurrentModificationException). */ // 修改总数 transient int modCount;
/** * The next size value at which to resize (capacity * load factor). * * @serial */ // (The javadoc description is true upon serialization. // Additionally, if the table array has not been allocated, this // field holds the initial array capacity, or zero signifying // DEFAULT_INITIAL_CAPACITY.) // 扩容的阈值 int threshold;
/** * The load factor for the hash table. * * @serial */ // 加载因子 final float loadFactor;
// fail-fast @Test public void failFast() throws Exception{ // 初始化map Map map = new HashMap(); for (int i = 0; i < 10; i++) { map.put(i,i); } // 创建一个线程来循环map的元素 Thread thread = new Thread(() -> { Set set = map.entrySet(); for (Object s : set) { System.out.println(s); TestUtil.sleep(100); } }); thread.start(); // 操作map map.put("100",'1'); thread.join(); }
运行结果:
1 2 3 4 5 6 7
0=0 Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445) at java.util.HashMap$EntryIterator.next(HashMap.java:1479) at java.util.HashMap$EntryIterator.next(HashMap.java:1477) at demo.HashMapDemo$1.run(HashMapDemo.java:326) at java.lang.Thread.run(Thread.java:748)
final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final void forEach(Consumer<? super Map.Entry<K,V>> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount;// 暂存mc = modCount for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e); } if (modCount != mc)// 校验 mc 和 modCount 如果不等则抛出异常。 throw new ConcurrentModificationException(); } } }
threshold和loadFactor的关系? 我们先看下resize()函数的部分源码:
1 2 3 4 5 6 7 8 9 10
{ // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr;
通过源码可以看出:threshold = cap * loadFactor,threshold最大为Integer.MAX_VALUE。 另外我们在创建HashMap的时候可以传入loadFactor来定制负载因子,负载因子可以为>0的任意数,通过loadFactor我们可以控制hashmap的扩容阈值,假如我们要时间换空间,那么loadFactor可以设置大些,如果空间换时间可以设置小点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
Map<Integer, String> map = new HashMap<>(10,3); // 存储结构,16个桶位可以容纳三十个元素 [0 ] link 0=0 16=4 64=8 144=12 256=16 400=20 576=24 784=28 [1 ] link 1=1 49=7 81=9 225=15 289=17 529=23 625=25 [2 ] link null [3 ] link null [4 ] link 4=2 36=6 100=10 196=14 324=18 484=22 676=26 [5 ] link null [6 ] link null [7 ] link null [8 ] link null [9 ] link 9=3 25=5 121=11 169=13 361=19 441=21 729=27 841=29 [10 ] link null [11 ] link null [12 ] link null [13 ] link null [14 ] link null [15 ] link null
// hashmap构造 public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; // 将容量大小暂存到threshold中,在resize的时候会用来做newCap来初始化容量。 this.threshold = tableSizeFor(initialCapacity); }
// 主角 static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
通过源码我们可以看到当我们构造一个指定容量大小的hashmap时候会调用tableSizeFor生成新的容量,然后赋值给threshold,通过初始化resize函数最终传递给newCap:newCap = oldThr,实现newTab初始化的的容量大小. 那么tableSizeFor函数具体在干嘛呢?这一堆>>>计算什么? 还记得常量容量的注释上有这么一句话:MUST be a power of two.而我们定义的容量怎么转化为a power of two就是tableSizeFor要做的事情。 我们使用自定义容量cap=14来举例整个计算过程,14的二进制表示为:00000000 00000000 00000000 00001110。
25:36:262|INFO |main|33|size:13 25:36:263|INFO |main|35|modCount:13 25:36:263|INFO |main|37|threshold:24 25:36:272|INFO |main|39|loadFactor:0.75 25:36:275|INFO |main|42|table:32 [0 ] link 0=0 64=8 [1 ] link 1=1 [2 ] link null [3 ] link null [4 ] link 4=2 36=6 100=10 [5 ] link null... [8 ] link null [9 ] link 9=3 [10 ] link null... [15 ] link null [16 ] link 16=4 144=12 [17 ] link 49=7 81=9 [18 ] link null... [25 ] link 25=5 121=11 [30 ] link null [31 ] link null
当容量小于64时,某个桶的长度大于8的时候无论size是否大于threshold都进行扩容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
29:34:140|INFO |main|34|size:12 29:34:141|INFO |main|36|modCount:12 29:34:142|INFO |main|38|threshold:24 29:34:145|INFO |main|40|loadFactor:0.75 29:34:146|INFO |main|43|table:32 [0 ] link 32=5 64=6 128=7 256=8 512=9 1024=10 2048=11 4096=13 [1 ] link null [2 ] link 2=1 [3 ] link null [4 ] link 4=2 [5 ] link null.. [7 ] link null [8 ] link 8=3 [9 ] link null... [15 ] link null [16 ] link 16=4 [17 ] link null... [31 ] link null
@Test public void concurrentOpretor() throws Exception{ while (true){ CountDownLatch countDownLatch = new CountDownLatch(2); HashMap map = new HashMap(0);// 容量设置为0,增加扩容次数 // 两个线程同时插入元素 Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { map.put(i*2, String.valueOf(i)); } countDownLatch.countDown(); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { map.put(i*3, String.valueOf(i)); } countDownLatch.countDown(); }); t1.start(); t2.start(); countDownLatch.await(); // 再次获取每个元素,空则抛出异常 for (int i = 0; i < 1000; i++) { Object o = map.get(i*2); if (o==null){ printMapStructure(map); log.error("key:{}",s(i*2)); throw new RuntimeException(); } } for (int i = 0; i < 1000; i++) { Object o = map.get(i*3); if (o==null){ printMapStructure(map); log.error("key:{}",s(i*3)); throw new RuntimeException(); } } TestUtil.sleep(300); } }
// 运行结果: 32:54:631|INFO |main|45|size:1582 32:54:631|INFO |main|47|modCount:1580 32:54:631|INFO |main|49|threshold:3072 32:54:634|INFO |main|51|loadFactor:0.75 32:54:635|INFO |main|54|table:4096 [0 ] link 0=0 [1 ] link null... [22 ] link 22=11 [23 ] link null [24 ] link 24=8 152=76 [25 ] link null [26 ] link 26=13 [27 ] link 27=9... [4094 ] link null [4095 ] link null 32:54:736|ERROR|main|354|index:152 32:54:736|DEBUG|main|33|take up time:1728 ms Disconnected from the target VM, address: '127.0.0.1:60922', transport: 'socket'
java.lang.RuntimeException at demo.HashMapDemo.concurrentOpretor(HashMapDemo.java:355)