数据结构(集合)学习之Map(二)
集合
框架关系图
补充: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(二)的更多相关文章
- 数据结构(集合)学习之Map(一)
集合 框架关系图: 补充:HashTable父类是Dictionary,不是AbstractMap. Map: Map(接口)和Collection都属于集合,但是Map不是Collection的子类 ...
- 数据结构(集合)学习之Set
集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于Set<E>的简单学习总结. 补充:HashTable父类是Dictionary,不是 ...
- 数据结构(集合)学习之List
集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于List<E>的简单学习总结. 补充:HashTable父类是Dictionary,不 ...
- 数据结构(集合)学习之Queue
集合 框架关系图: Collection接口下面有三个子接口:List.Set.Queue.此篇是关于Queue<E>的简单学习总结. 补充:HashTable父类是Dictionary, ...
- 数据结构(集合)学习之Collection和Iterator
集合 1.集合与数组 数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用. 集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使 ...
- 2019/3/4 java集合学习(二)
java集合学习(二) 在学完ArrayList 和 LinkedList之后,基本已经掌握了最基本的java常用数据结构,但是为了提高程序的效率,还有很多种特点各异的数据结构等着我们去运用,类如可以 ...
- 【转】Java学习---Java核心数据结构(List,Map,Set)使用技巧与优化
[原文]https://www.toutiao.com/i6594587397101453827/ Java核心数据结构(List,Map,Set)使用技巧与优化 JDK提供了一组主要的数据结构实现, ...
- Java学习:集合双列Map
数据结构 数据结构: 数据结构_栈:先进后出 入口和出口在同一侧 数据结构_队列:先进先出 入口和出口在集合的两侧 数据结构_数组: 查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过 ...
- java集合学习(2):Map和HashMap
Map接口 java.util 中的集合类包含 Java 中某些最常用的类.最常用的集合类是 List 和 Map. Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含 ...
随机推荐
- 删除我的电脑wps、百度网盘图标
删除我的电脑wps.百度网盘图标 删除下面子项 输入"计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Expl ...
- 05讲基础篇:某个应用的CPU使用率居然达到100%,我该怎么办
小结 CPU 使用率是最直观和最常用的系统性能指标,更是我们在排查性能问题时,通常会关注的第一个指标.所以我们更要熟悉它的含义,尤其要弄清楚用户(%user).Nice(%nice).系统(%syst ...
- js笔记(3)--js实现数组转置(两种方法)
js实现数组转置 第一种方法: <script> window.onload=function(){ var array1=[[11,22,33,333],[4 ...
- 设计模式——Adapter Pattern 适配器模式
我第一次接触设计模式,选取了四大类型里面的结构型,这类型的特点是关注类&对象之间的组合(使用继承),我从中选取适配器模式来具体学习. 一.适配器模式(Adapter Pattern)定义: 适 ...
- Python爬虫小结
有些数据是没有专门的数据集的,为了找到神经网络训练的数据,自然而然的想到了用爬虫的方法开始采集数据.一开始采用了网上的一个动态爬虫的代码,发现爬取的图片大多是重复的,有效图片很少. 动态爬虫: fro ...
- c++ 初始化列表和构造函数初始化区别
先上代码 #include <iostream> class MyContruct { public: MyContruct() { std::cout << "My ...
- Robot Framework自动化测试框架核心指南-如何使用Java编写自定义的RobotFramework Lib
如何使用Java编写自定义的RobotFramework Lib 本文包括2个章节 1. Robot Frdamwork中如何调用java Lib库 2.使用 java编写自定义的Lib 本文作者为: ...
- MySQL8.0 InnoDB并行执行
概述 MySQL经过多年的发展已然成为最流行的数据库,广泛用于互联网行业,并逐步向各个传统行业渗透.之所以流行,一方面是其优秀的高并发事务处理的能力,另一方面也得益于MySQL丰富的生态.MySQL在 ...
- CSS选择器世界
CSS选择器世界 CSS选择器的分类与优先级 css选择器分为四类:选择器.选择符(后代关系的空格.>.+.~.||).伪类.伪元素(::before.::after.::first-lette ...
- Django请求过程