标签:
HashMap.Entry数组,数组+拉链
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
}
DEFAULT_INITIAL_CAPACITY是16DEFAULT_LOAD_FACTOR是0.75initialCapacity大于MAXIMUM_CAPACITY,会重置为MAXIMUM_CAPACITYHashMap实际的容量大于等于指定的容量initialCapacity,因为hashmap的容量是2的幂,如果initialCapacity不是2的幂,实际容量将设置为比initialCapacity大的最小的2的幂值
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;如果使用另一个map进行初始化,负载因子会使用DEFAULT_LOAD_FACTOR,容量会在(int) (m.size() / DEFAULT_LOAD_FACTOR) + 1和DEFAULT_INITIAL_CAPACITY中取大者
init() 方法(空实现,LinkedHashMap会重写这个方法)recordAccess方法,凡是访问了HashMap中元素的方法,都会调用recordAccess方法,在HashMap中是空实现,在LinkedHashMap会重写这个方法addEntry方法,创建一个新的Entry对象,放到Entry数组对应的桶中,成为拉链的第一个元素size>=threshold,则进行2倍扩容(注意,如果容量已经等于最大容量MAXIMUM_CAPACITY,只把threshold改为Integer.MAX_VALUE,而不扩容)。创建新的Entry数组,并将当前数组中的元素迁移到新的数组中(重新计算每个元素在新数组中的索引)modCount++,size++,modCount是用来记录结构性修改次数的,凡是修改了底层结构的操作都会令modCount加1,modCount主要用于迭代器的快速失败,迭代器将根据modCount的值是否发生了变化来判断是否有其他进程对HashMap做了修改hash算法(h是传入的key的hashCode)
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);根据hash定位数组索引的算法
h & (length-1)判断key是否存在的方法
e.hash == hash && ((k = e.key) == key || key.equals(k))equals)modCount++,size--,调用recordRemoval方法,凡是删除了HashMap中元素的方法都会调用recordRemoval,在HashMap中是空实现,在LinkedHashMap会重写这个方法equals),返回元素的值keySet对象keySet是HashMap中的一个内部类,继承了AbstractSet,KeySet的remove和clear方法可以直接修改HashMap本身的内容。因为,KeySet只不过是HashMap的一个视图而已
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}keySet的iterator方法调用了外部类(HashMap)的newKeyIterator方法,newKeyIterator将返回一个KeyIterator对象,KeyIterator继承了HashIterator。LinkedHashMap重写了newKeyIterator方法。HashIterator中的域,expectedModCount初始化为modCount,index初始化为第一个不为空的桶的索引+1,next初始化为第一个不为空的桶中拉链的第一个元素。
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K,V> current; // current entry
}
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}next方法。迭代的方案是,按照索引从小到大(大于0,小于table.length)遍历每一个桶,如果桶不为空,则遍历这个桶中拉链的每个元素。每次被遍历到的元素将赋值给current,current对于下一次操作而言,保存了上一个返回的元素。
while (index < t.length && (next = t[index++]) == null); //风骚的代码remove方法。如果current==null,将抛出异常,这说明在调用remove方法之前必须先调用next,删除current.key对应的元素,最后expectedModCount = modCount,这是因为删除操作会使得modCount++,而这次的删除操作并不是并发操作引起的,因此重置expectedModCount,以免触发快速失败。
在调用next和remove时,有个快速失败(fast-fail)机制
if (modCount != expectedModCount)
throw new ConcurrentModificationException();Object数组
检查容量,同时modCount++,如果size+1 > elementData.length,则需要先进行扩容,默认1.5倍扩容,如果扩容后容量还是还是比size+1小,则新的容量等于size+1,扩容使用Arrays.copyOf静态方法
int newCapacity = (oldCapacity * 3)/2 + 1;
elementData = Arrays.copyOf(elementData, newCapacity);添加元素
elementData[size++] = e;index必须小于size返回元素,做了类型转换
return (E) elementData[index];index必须小于sizesize 的所有元素置为null(释放引用,让垃圾回收机制工作)size=0 index必须小于sizemodCount++计算需要移动的元素数量numMoved
int numMoved = size - index - 1;如果numMoved > 0,调用System.arraycopy将从index+1开始的numMoved个元素向前移动一位,System.arraycopy是本地方法
System.arraycopy(elementData, index+1, elementData, index, numMoved);
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);为了释放引用,让垃圾回收机制工作,将移动前最后一个元素的位置设置为null
equals 的元素,就删除这个元素,删除的方式见 remove(int index) index必须大于0,小于size检查容量,同时modCount++,如果size+1 > elementData.length,则需要先进行扩容,默认1.5倍扩容,如果扩容后容量还是还是比size+1小,则新的容量等于size+1,扩容使用Arrays.copyOf静态方法
int newCapacity = (oldCapacity * 3)/2 + 1;
elementData = Arrays.copyOf(elementData, newCapacity);index 开始的元素向后移动一位,将新元素设置到 index 的位置size++equals 的元素,则返回对应的索引,否则,返回-1HashMap
HashSet内部封装了HashMap,其功能都是通过HashMap实现的,HashSet中的元素存储在HashMap的key中,Value使用一个静态哑变量 PRESENT 占位
private static final Object PRESENT = new Object();HashSet实现举例
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}LinkedHashMap.Entry数组:数组+拉链+双向循环链表
LinkedHashMap的内部类Entry继承了HashMap.Entry,多了before和after两个域,用于实现双向循环链表,而原有的next域是用于桶中拉链的单向链表的,不要搞混了。
private static class Entry<K,V> extends HashMap.Entry<K,V> {
// These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after;
}accessOrder 域,表示排序的方式,如果 accessOrder 是true,按照访问顺序排序,否则,按照插入顺序排序accessOrder 默认为 falseaccessOrder 参数调用 init() 方法,LinkedHashMap重载了HashMap的 init() 方法,用于初始化双向循环链表头节点 header
void init() {
header = new Entry<K,V>(-1, null, null, null);
header.before = header.after = header;
}如果键已经存在,会调用recordAccess方法,凡是访问了HashMap中元素的方法,都会调用recordAccess方法,在HashMap中是空实现,LinkedHashMap重写了这个方法,如果accessOrder等于true,modCount++,从双向循环链表中删除当前节点,并把当前节点插入到header之前
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}如果键不存在,会调用addEntry方法,LinkedHashMap重写了HashMap的addEntry方法,创建一个新的Entry对象,放到Entry数组对应的桶中,成为拉链的第一个元素,并在双向循环链表中把新节点插入到header之前
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}如果键不存在,添加了新元素之后,会判断是否需要扩容。如果size>=threshold,则进行2倍扩容(注意,如果容量已经等于最大容量MAXIMUM_CAPACITY,只把threshold改为Integer.MAX_VALUE,而不扩容)。创建新的Entry数组,并将当前数组中的元素迁移到新的数组中(重新计算每个元素在新数组中的索引)
modCount++,size++,modCount是用来记录结构性修改次数的,凡是修改了底层结构的操作都会令modCount加1,modCount主要用于迭代器的快速失败,迭代器将根据modCount的值是否发生了变化来判断是否有其他进程对HashMap做了修改equals),返回元素的值null ,则返回 null 否则在返回值之前调用 recordAccess 方法,LinkedHashMap重写了这个方法,如果accessOrder等于true,modCount++,从双向循环链表中删除当前节点,并把当前节点插入到header之前
void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
if (lm.accessOrder) {
lm.modCount++;
remove();
addBefore(lm.header);
}
}equals)如果找到了,modCount++,size--,调用recordRemoval方法,凡是删除了HashMap中元素的方法都会调用recordRemoval,在HashMap中是空实现,在LinkedHashMap会重写这个方法,将元素从双向循环链表中移除
void recordRemoval(HashMap<K,V> m) {
remove();
}最终将返回节点(Entry对象)的value值
keySet对象keySet是HashMap中的一个内部类,继承了AbstractSet,KeySet的remove和clear方法可以直接修改HashMap本身的内容。因为,KeySet只不过是HashMap的一个视图而已
private final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}keySet的iterator方法调用了外部类(HashMap)的newKeyIterator方法,LinkedHashMap重写了newKeyIterator方法,newKeyIterator将返回一个KeyIterator对象,KeyIterator继承了LinkedHashIterator。LinkedHashIterator中的域,expectedModCount初始化为modCount,lastReturned保存最近一次迭代返回的元素,初始化为null,nextEntry保存下一次迭代返回的元素,初始化为header后的第一个元素,即第一个插入或者访问的元素。
private abstract class LinkedHashIterator<T> implements Iterator<T> {
Entry<K,V> nextEntry = header.after;
Entry<K,V> lastReturned = null;
int expectedModCount = modCount;next方法。迭代的方案是,从header的下一个元素开始,按照双向循环链表的正向遍历。将本次返回的元素赋值给lastReturned,对于下一次操作而言,lastReturned保存了最近一次迭代返回的元素
remove方法。如果lastReturned==null,将抛出异常,这说明在调用remove方法之前必须先调用next,删除lastReturned.key对应的元素,最后expectedModCount = modCount,这是因为删除操作会使得modCount++,而这次的删除操作并不是并发操作引起的,因此重置expectedModCount,以免触发快速失败。
在调用next和remove时,有个快速失败(fast-fail)机制
if (modCount != expectedModCount)
throw new ConcurrentModificationException();ConcurrentHashMap.Segment数组 + ConcurrentHashMap.HashEntry数组
hashCode定位所在的段,再根据hashCode定位所在的桶,最后再在拉链中定位元素(equals)。Segment,注意count、table是volatile的
transient volatile int count;
transient int modCount;
transient int threshold;
transient volatile HashEntry<K,V>[] table;
final float loadFactor;HashEntry,注意key、hash、next是final的,value是volatile的
static final class HashEntry<K,V> {
final K key;
final int hash;
volatile V value;
final HashEntry<K,V> next;
HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
this.key = key;
this.hash = hash;
this.next = next;
this.value = value;
}
}DEFAULT_INITIAL_CAPACITY是16DEFAULT_LOAD_FACTOR是0.75DEFAULT_CONCURRENCY_LEVEL是16初始化段数组(Segment<K,V>[] segments)。
this.segments = Segment.newArray(ssize);
计算段数组容量ssize。若指定的容量concurrencyLevel大于MAX_SEGMENTS,会重置为MAX_SEGMENTS,实际的容量大于等于concurrencyLevel,因为容量是2的幂,如果concurrencyLevel不是2的幂,实际容量将置为比concurrencyLevel大的最小的2的幂值
int ssize = 1;
while (ssize < concurrencyLevel) {
//...
ssize <<= 1;
}计算segmentMask
segmentShift为每个段初始化HashEntry数组
for (int i = 0; i < this.segments.length; ++i)
this.segments[i] = new Segment<K,V>(cap, loadFactor);
计算HashEntry数组的容量cap。若指定的容量initialCapacity大于MAXIMUM_CAPACITY,会重置为MAXIMUM_CAPACITY,根据initialCapacity / ssize计算每个段中的容量并向上取为2的幂
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = 1;
while (cap < c)
cap <<= 1;key.hashCode(),如果key为null的话会抛出空指针异常。HashMap允许key和value为null,这里不一样。根据hash值计算在段数组中的索引
final Segment<K,V> segmentFor(int hash) {
return segments[(hash >>> segmentShift) & segmentMask];
}调用对应段对象的put方法
count+1是否大于threshold,如果大于,进行2倍扩容(如果容量已经大于等于最大容量MAXIMUM_CAPACITY,直接返回,不扩容)。创建新的Entry数组,并将当前数组中的元素迁移到新的数组中(重新计算每个元素在新数组中的索引,具体算法太风骚,没有看懂,稍后再看)modCount++,size++,modCount是用来记录结构性修改次数的,凡是修改了底层结构的操作都会令modCount加1,modCount主要用于迭代器的快速失败,迭代器将根据modCount的值是否发生了变化来判断是否有其他进程对HashMap做了修改hash算法(h是传入的key的hashCode)
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);根据hash定位数组索引的算法
int index = hash & (tab.length - 1);put,remove等写方法会对当前的段加锁,这是保障并发访问的关键所在,get方法不会加锁,除非需要value等于null的特殊情况(HashEntry中只有value不是final的,因此有可能被重排序到构造函数之外进行初始化)
lock();
try {
//...
} finally {
unlock();
} key.hashCode(),如果key为null的话会抛出空指针异常。HashMap允许key为null,这里不一样。count==0,直接返回nullequals),将返回对应的value。HashEntry的value是volatile的key.hashCode(),如果key为null的话会抛出空指针异常。HashMap允许key为null,这里不一样。equals)如果找到了,链表中该节点之后的节点不用动,该节点之前的每个元素拷贝一份。为什么不直接将节点从链表中摘除呢?这是因为HashEntry中的key,next,hash都是final的,一经初始化,不能修改,因此需要把节点之前的元素全部拷贝一份新的。(那么问题来了,为什么设计成final的呢?参见JMM-final)
HashEntry<K,V> newFirst = e.next;
for (HashEntry<K,V> p = first; p != e; p = p.next)
newFirst = new HashEntry<K,V>(p.key, p.hash,
newFirst, p.value);
tab[index] = newFirst;
1 -> 2 -> 3 -> 4 -> 5 - > 6
移除4后将变为
3 -> 2 -> 1 -> 5 -> 6如果找到了,modCount++,count--
count求和sum,并把每个段的modCount保存下来。第二次循环,再次对每个段的count求和check,并检查这次的modCount是否和上次的一样。如果每个段的modCount都没有变,且check=sum,则返回sum,否则,重试。(最多进行RETRIES_BEFORE_LOCK次)对所有段加锁,循环,对每个段的count求和sum,对所有段解锁,返回sum
sum = 0;
for (int i = 0; i < segments.length; ++i)
segments[i].lock();
for (int i = 0; i < segments.length; ++i)
sum += segments[i].count;
for (int i = 0; i < segments.length; ++i)
segments[i].unlock();keySet对象keySet是ConcurrentHashMap中的一个内部类,继承了AbstractSet,KeySet的remove和clear方法可以直接修改HashMap本身的内容。因为,KeySet只不过是HashMap的一个视图而已
final class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return new KeyIterator();
}
public int size() {
return ConcurrentHashMap.this.size();
}
public boolean contains(Object o) {
return ConcurrentHashMap.this.containsKey(o);
}
public boolean remove(Object o) {
return ConcurrentHashMap.this.remove(o) != null;
}
public void clear() {
ConcurrentHashMap.this.clear();
}
}keySet的iterator方法返回一个KeyIterator对象,KeyIterator继承了HashIterator。继承不同
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements MapHashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
Hashtable中,key和value都不允许出现null值。在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,既可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。
两个遍历方式的内部实现上不同。Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
标签:
原文地址:http://blog.csdn.net/wchgogo/article/details/51706576