集合

框架关系图

补充:HashTable父类是Dictionary,不是AbstractMap。

一:HashMap中的链循环:

一般来说HashMap中的链循环会发生在多线程操作时(虽然HashMap就不是线程安全的,一般多线程的时候也不会用它,但这种情况难免会发生)

以JDK1.7为例:

    void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);//扩容方法,参数为原来数组长度的2倍
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
} createEntry(hash, key, value, bucketIndex);
}
void resize(int newCapacity) {
Entry[] oldTable = table;//数组原来的数据
int oldCapacity = oldTable.length;//原来数组的长度
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
} Entry[] newTable = new Entry[newCapacity];//以新的初始容量创建新的数组
transfer(newTable, initHashSeedAsNeeded(newCapacity));//转换方法
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);//e是oldTable上的Entry对象,这里算出新数组的下标
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}

通过代码分析:当old Table中的数值都转移到newTable时,元素的顺序会进行颠倒。效果图如下:

在这种情况下,假设有两个线程同时进行扩容的操作且都进入了transfer方法时,但是因为某个情况让线程一先执行完,线程二才开始执行。

这个时候如果线程一执行完了,线程二开始的状态就是这样:

这个时候:线程二的e还是A对象,e.Next(A.Next)还是指向B,但是因为线程一的关系,实际上此时B.Next已经指向了A。于是就产生了循环:A.Next指向B,B.Next指向A。

二:hashtable

HashTable是Map接口下的一个子类,线程安全的,1.7和1.8差别不大,常和HashMap作比较,部分源码如下:

 //hashTable默认初始容量和加载因子
public Hashtable() {
this(11, 0.75f);
} //Put方法
public synchronized V put(K key, V value) {
// 不允许存null
if (value == null) {
throw new NullPointerException();
} // key相同时新值代替老值,并把老值返回出去
Entry tab[] = table;
int hash = hash(key);//哈希算法
int index = (hash & 0x7FFFFFFF) % tab.length;
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();//扩容 tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
} Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
//哈希算法
private int hash(Object k) {
return hashSeed ^ k.hashCode();
}
//扩容方法
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table; int newCapacity = (oldCapacity << 1) + 1;//扩容后容量为原来的2倍再加1
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
Entry<K,V>[] newMap = new Entry[newCapacity]; modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean rehash = initHashSeedAsNeeded(newCapacity); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next; if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
//remove方法
public synchronized V remove(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
//get方法
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
//keySet方法
public Set<K> keySet() {
if (keySet == null)
keySet = Collections.synchronizedSet(new KeySet(), this);
return keySet;
} private class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return getIterator(KEYS);
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return Hashtable.this.remove(o) != null;
}
public void clear() {
Hashtable.this.clear();
}
}
//entrySet方法
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
entrySet = Collections.synchronizedSet(new EntrySet(), this);
return entrySet;
} private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return getIterator(ENTRIES);
} public boolean add(Map.Entry<K,V> o) {
return super.add(o);
} public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry entry = (Map.Entry)o;
Object key = entry.getKey();
Entry[] tab = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index]; e != null; e = e.next)
if (e.hash==hash && e.equals(entry))
return true;
return false;
}
//clear方法
public synchronized void clear() {
Entry tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
//size方法和isEmpty方法
public synchronized int size() {
return count;
} public synchronized boolean isEmpty() {
return count == 0;
}

可以看出和HashMap比较,有以下特点:

1、初始容量为11。

2、扩容方法为:oldTable.Length*2+1。

3、不允许存null值。

4、计算hash,和获取数组下标不同:

HashTable:

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

HashMap:

int hash = hash(key);//
int i = indexFor(hash, table.length);

5、HashTable线程安全,主要的方法都用了synchronized,加锁。

6、HashMap和HashTable父类不同

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable

7、HashTable线程安全,但因为每个主要方法上都加上了锁,所以在执行效率上会慢很多。

三:其他线程安全Map

1、synchronizedMap

通过工具类Collections的方法,返回一个HashMap,例如:Map<String,String> map = Collections.synchronizedMap(new HashMap<String,String>());

     public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
} private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L; private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize SynchronizedMap(Map<K,V> m) {
if (m==null)
throw new NullPointerException();
this.m = m;
mutex = this;
} SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
} public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
} public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
public void putAll(Map<? extends K, ? extends V> map) {
synchronized (mutex) {m.putAll(map);}
}
public void clear() {
synchronized (mutex) {m.clear();}
} private transient Set<K> keySet = null;
private transient Set<Map.Entry<K,V>> entrySet = null;
private transient Collection<V> values = null; public Set<K> keySet() {
synchronized (mutex) {
if (keySet==null)
keySet = new SynchronizedSet<>(m.keySet(), mutex);
return keySet;
}
} public Set<Map.Entry<K,V>> entrySet() {
synchronized (mutex) {
if (entrySet==null)
entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
return entrySet;
}
} public Collection<V> values() {
synchronized (mutex) {
if (values==null)
values = new SynchronizedCollection<>(m.values(), mutex);
return values;
}
} public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return m.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return m.hashCode();}
}
public String toString() {
synchronized (mutex) {return m.toString();}
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
}

可以看出,原理就是在原来Map的基础上,加上锁synchronized (mutex),来让线程变得安全,但和HashTable一样,效率慢。

2、ConcurrentHashMap

JDK1.7中的ConcurrentHashMap:

ConcurrentHashMap是线程安全的Map,继承AbstractMap。是和HashTable把整合数组共享一把锁不同,ConcurrentHashMap采用分段的思想,把HashMap分为多个段进行加锁的操作,这样既保证了线程安全性,又不会使效率像HashTable那样低。

ConcurrentHashMap的特点是分段式加锁,并利用Unsafe对象和volatile关键字来实现线程安全,他比较明显的一个局限性是并发级别一旦指定就不再更改。

结构:

代码:

 //默认初始容量(2的幂),实际上是指的HashEntry的数量
static final int DEFAULT_INITIAL_CAPACITY = 16;
//默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认并发级别(实际上指的Segmengt对象的数量,初始化后不可更改)
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//最大容量小于等于2的30次幂
static final int MAXIMUM_CAPACITY = 1 << 30;
//最小SEGMENT容量(一个Segment里至少有两个HashEntry)
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
//最大SEGMENT容量容量
static final int MAX_SEGMENTS = 1 << 16;
//加锁之前的重试次数
static final int RETRIES_BEFORE_LOCK = 2;
//Segment数组
final Segment<K,V>[] segments;
//计算segment位置的掩码,用于计算Segment[]下标
final int segmentMask;
//用于算segment位置时,hash参与运算的位数
final int segmentShift;
//内部类HashEntry,相当于HashMap中Entry[]中的Entry对象
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next; HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
} final void setNext(HashEntry<K,V> n) {
UNSAFE.putOrderedObject(this, nextOffset, n);
} static final sun.misc.Unsafe UNSAFE;
static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class k = HashEntry.class;
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
//hash函数
private int hash(Object k) {
int h = hashSeed; if ((0 != h) && (k instanceof String)) {
return sun.misc.Hashing.stringHash32((String) k);
} h ^= k.hashCode(); h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
//ConcurrentHashMap的构造方法
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {//初始容量,加载因子,并发级别
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)//保证参数不能小于0
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)//保证并发级别不能大于最大Segment容量
concurrencyLevel = MAX_SEGMENTS; int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;//统计ssize左移的次数
ssize <<= 1;//初始是1,通过左移然后2、4、8、16...作用是找到大于等于并发级别的2的“sshift”次幂
}
this.segmentShift = 32 - sshift;//用于Put方法中,求Segment[]数组的下标时用
this.segmentMask = ssize - 1;//Segmengt[].length-1
if (initialCapacity > MAXIMUM_CAPACITY)//如果初始容量大于最大
initialCapacity = MAXIMUM_CAPACITY;//则选择最大容量作为容量
int c = initialCapacity / ssize;//HashEntry[].length/Segmengt[].length,用来标记HashEntry的数量
if (c * ssize < initialCapacity)//例如initialCapacity = 9,concurrencyLevel(ssize) = 8 ,则initialCapacity / ssize = 1,c * ssize < initialCapacity
++c; // 然后让C = 2, 此时 c * ssize >= initialCapacity ,这里的目的就是保证数据都被Segmengt存储。
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)//拿最小的容量和刚刚计算的 c 进行比较
cap <<= 1; // 保证最小容量是2的n次幂
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);//初始化Segment对象
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];//初始化Segment数组
UNSAFE.putOrderedObject(ss, SBASE, s0); //调用Unsafe中的方法,作用是把刚刚初始化的Segemengt对象s0,放到刚刚初始化Segment[]数组中的Segment[0],也就是第一位
this.segments = ss;
}
//Put方法
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();//不能传null,此处个人认为应该加个 key也不能为 null的判断
int hash = hash(key);//计算hash值
int j = (hash >>> segmentShift) & segmentMask; // 计算Segmengt[]下标,即数据存储的位置
if ((s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null) // 调用Unsafe的方法,作用是获取Segmengt[j]的对象,如果等于空
s = ensureSegment(j);//判断第j个位置是不是为空,保证线程安全性
return s.put(key, hash, value, false);//调用Segmengt对象的put方法
} //Segmengt的Put方法
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);//加锁,保证线程安全
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);//获取该位置的第一个元素,然后遍历链表
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
} @SuppressWarnings("unchecked")
private void rehash(HashEntry<K,V> node) {//扩容方法,只扩容HashEntry[],没扩容Segmengt[] HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
int newCapacity = oldCapacity << 1;
threshold = (int)(newCapacity * loadFactor);
HashEntry<K,V>[] newTable =
(HashEntry<K,V>[]) new HashEntry[newCapacity];
int sizeMask = newCapacity - 1;
for (int i = 0; i < oldCapacity ; i++) {
HashEntry<K,V> e = oldTable[i];
if (e != null) {
HashEntry<K,V> next = e.next;
int idx = e.hash & sizeMask;
if (next == null)
newTable[idx] = e;
else {
HashEntry<K,V> lastRun = e;
int lastIdx = idx;
for (HashEntry<K,V> last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun; for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
V v = p.value;
int h = p.hash;
int k = h & sizeMask;
HashEntry<K,V> n = newTable[k];
newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
}
}
}
}
int nodeIndex = node.hash & sizeMask;
node.setNext(newTable[nodeIndex]);
newTable[nodeIndex] = node;
table = newTable;
} private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
int retries = -1;
while (!tryLock()) {
HashEntry<K,V> f;
if (retries < 0) {
if (e == null) {
if (node == null)
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
else if (key.equals(e.key))
retries = 0;
else
e = e.next;
}
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f;
retries = -1;
}
}
return node;
} private void scanAndLock(Object key, int hash) { HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
int retries = -1;
while (!tryLock()) {
HashEntry<K,V> f;
if (retries < 0) {
if (e == null || key.equals(e.key))
retries = 0;
else
e = e.next;
}
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f;
retries = -1;
}
}
} final V remove(Object key, int hash, Object value) {
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> e = entryAt(tab, index);
HashEntry<K,V> pred = null;
while (e != null) {
K k;
HashEntry<K,V> next = e.next;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
V v = e.value;
if (value == null || value == v || value.equals(v)) {
if (pred == null)
setEntryAt(tab, index, next);
else
pred.setNext(next);
++modCount;
--count;
oldValue = v;
}
break;
}
pred = e;
e = next;
}
} finally {
unlock();
}
return oldValue;
} final boolean replace(K key, int hash, V oldValue, V newValue) {
if (!tryLock())
scanAndLock(key, hash);
boolean replaced = false;
try {
HashEntry<K,V> e;
for (e = entryForHash(this, hash); e != null; e = e.next) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
if (oldValue.equals(e.value)) {
e.value = newValue;
++modCount;
replaced = true;
}
break;
}
}
} finally {
unlock();
}
return replaced;
} final V replace(K key, int hash, V value) {
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V> e;
for (e = entryForHash(this, hash); e != null; e = e.next) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
e.value = value;
++modCount;
break;
}
}
} finally {
unlock();
}
return oldValue;
} final void clear() {
lock();
try {
HashEntry<K,V>[] tab = table;
for (int i = 0; i < tab.length ; i++)
setEntryAt(tab, i, null);
++modCount;
count = 0;
} finally {
unlock();
}
}
}
//Get方法
public V get(Object key) {
Segment<K,V> s;
HashEntry<K,V>[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;//先获取Segment[] 的下标
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null && (tab = s.table) != null) { //用Unsafe的方法获取到Segment对象
//用Unsafe的方法HashEntry对象,然后遍历链表
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}

JDK1.8:

JDK1.8时,ConcurrentHashMap又放弃了分段式加锁的思想,而且也不在用Segment[]数组存值,而是采用在HashMap1.8的基础上,采用Node[] + 链表 + 红黑树 + CAS+ Synchronized 的思想保证线程安全。而且在扩容的时候,不仅要满足链表结构大于8,还要满足数据容量大于64。

 //最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认容量
private static final int DEFAULT_CAPACITY = 16;
//最大数组长度
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//默认并发等级(1.7遗留,兼容以前版本)
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//加载因子(1.7遗留,兼容以前版本)
private static final float LOAD_FACTOR = 0.75f;
//转换为红黑树的阈值(道理和HashMap1.8中一样,这里指链表长度)
static final int TREEIFY_THRESHOLD = 8;
//红黑树反转成链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
//转换为红黑树的阈值(这里指数组长度)
static final int MIN_TREEIFY_CAPACITY = 64;
//扩容转移时的最小数组分组大小
private static final int MIN_TRANSFER_STRIDE = 16;
//本类中没提供修改的方法 用来根据n生成位置一个类似时间搓的功能
private static int RESIZE_STAMP_BITS = 16;
// 2^15-1,help resize的最大线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
// 32-16=16,sizeCtl中记录size大小的偏移量
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//表示正在转移
static final int MOVED = -1;
// 表示已转换为红黑树
static final int TREEBIN = -2;
// 保留
static final int RESERVED = -3;
// 用在计算hash时进行安位与计算消除负hash
static final int HASH_BITS = 0x7fffffff;
// 可用处理器数量
static final int NCPU = Runtime.getRuntime().availableProcessors();
//构造函数
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)//判断参数是否大于等于0
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel)
initialCapacity = concurrencyLevel; // 容量最小为16
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);//获取数组容量并保证不大于最大容量
this.sizeCtl = cap;
}
//Put方法
public V put(K key, V value) {
return putVal(key, value, false);
} final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();//不允许存null
int hash = spread(key.hashCode());//计算hash值
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();//懒加载,数组为空时初始化
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//类似Unsafe获取对象的方法获取对象
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))//CAS,如果获取位置为空,则将数据存入
break;
}
else if ((fh = f.hash) == MOVED)//首地址处不null 并且Node的hash是-1 表示是ForwardingNode节点正在rehash扩容
tab = helpTransfer(tab, f);//帮助扩容的方法
else {
V oldVal = null;
synchronized (f) {//加锁
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {//遍历链表,如果key重复,值替换,老值返回出去
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {//如果为红黑树的对象,调用红黑树的put方法
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
//如果链表数据超过指定阈值,转换红黑树,并且会再进行一次判断,看数组容量是否大于64,数组大于64后才会转换为红黑树
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
//Get方法,基本和1.8HashMap一样,获取数组坐标,然后获取Node对象,并且没有加锁。
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}

注意:分析ConcurrentHashMap源码前,需要先了解一下CAS、Unsafe、Synchronized 、Volatile相关知识。

数据结构(集合)学习之Map(二)的更多相关文章

  1. 数据结构(集合)学习之Map(一)

    集合 框架关系图: 补充:HashTable父类是Dictionary,不是AbstractMap. Map: Map(接口)和Collection都属于集合,但是Map不是Collection的子类 ...

  2. 数据结构(集合)学习之Set

    集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于Set<E>的简单学习总结. 补充:HashTable父类是Dictionary,不是 ...

  3. 数据结构(集合)学习之List

    集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于List<E>的简单学习总结. 补充:HashTable父类是Dictionary,不 ...

  4. 数据结构(集合)学习之Queue

    集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于Queue<E>的简单学习总结. 补充:HashTable父类是Dictionary, ...

  5. 数据结构(集合)学习之Collection和Iterator

    集合 1.集合与数组 数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用. 集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使 ...

  6. 2019/3/4 java集合学习(二)

    java集合学习(二) 在学完ArrayList 和 LinkedList之后,基本已经掌握了最基本的java常用数据结构,但是为了提高程序的效率,还有很多种特点各异的数据结构等着我们去运用,类如可以 ...

  7. 【转】Java学习---Java核心数据结构(List,Map,Set)使用技巧与优化

    [原文]https://www.toutiao.com/i6594587397101453827/ Java核心数据结构(List,Map,Set)使用技巧与优化 JDK提供了一组主要的数据结构实现, ...

  8. Java学习:集合双列Map

    数据结构 数据结构: 数据结构_栈:先进后出 入口和出口在同一侧 数据结构_队列:先进先出 入口和出口在集合的两侧 数据结构_数组: 查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过 ...

  9. java集合学习(2):Map和HashMap

    Map接口 java.util 中的集合类包含 Java 中某些最常用的类.最常用的集合类是 List 和 Map. Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含 ...

随机推荐

  1. HEXO常用命令总结

    博客搬家:hexo常用命令总结 常见命令 hexo new "postName" #新建文章 hexo new page "pageName" #新建页面(新建 ...

  2. 题解 CF1294F 【Three Paths on a Tree】

    \[ Preface \] 打比赛的时候先开了 F 题(雾 然后一眼看出 F 题结论,最后居然因为没有判重,交了三次才过. \[ Description \] 给出一棵无权树(可理解为边权为 \(1\ ...

  3. 深入JVM垃圾回收机制,值得你收藏

    JVM可以说是为了Java开发人员屏蔽了很多复杂性,让Java开发的变的更加简单,让开发人员更加关注业务而不必关心底层技术细节,这些复杂性包括内存管理,垃圾回收,跨平台等,今天我们主要看看JVM的垃圾 ...

  4. 初入机器学习,安装tensorflow包等问题总结

    学习python,机器学习(maching-lerning).深度学习(deep-learning)等概念也是耳熟能详.我最近从新手开始学习maching-learning知识,不过课程偏向基本的理论 ...

  5. SpingBoot错误信息处理及原理

    SpringBoot错误信息处理机制 在一个web项目中,总需要对一些错误进行界面或者json数据返回,已实现更好的用户体验,SpringBoot中提供了对于错误处理的自动配置 ErrorMvcAut ...

  6. 《Head first设计模式》学习笔记

    1. 单例模式 2. 工厂模式 3. 抽象工厂 4. 策略模式 5. 观察者模式 6. 装饰者模式 7. 命令模式 8. 适配器模式 9. 外观模式 10. 模版方法模式 11. 迭代器模式 设计模式 ...

  7. 10-SpringMVC04

    FreeMarker 1.入门案例 1. 导包:freemarker.jar 2. 需要创建模板文件的路径:src/main/resources/template 3. 创建一个模板对象:hello. ...

  8. ubuntu下怎么配置/查看串口-minicom工具

    一.安装minicom工具: 可直接使用命令sudo apt-get install minicom来完成安装 上面的截图因为检测到我已经安装过了. 二.通过minicom工具配置串口: 1.启动mi ...

  9. IntelliJ 更改项目使用的 JDK 版本

    在当前使用的 IntelliJ 中的 JDK 版本为 1.8,如何修改 IntelliJ 使用的 JDK 版本为 1.11 呢? 你可以在 IntelliJ 中进行修改. 选择 File 后,然后选择 ...

  10. package包

    为什么需要package? 为了解决类之间的重名问题.为了方便管理类,合适的类放在合适的包. 怎么用package? 通常是类的第一句非注释性语句. 包名,域名倒着写,加上模块名,并与内部管理类. 命 ...