If a thread-safe implementation is not needed, it is recommended to use {@link HashMap} in place of {@code Hashtable}. If a thread-safe highly-concurrent implementation is desired, then it is recommended to use {@link java.util.concurrent.ConcurrentHashMap} in place of {@code Hashtable}.
public V put(K key, V value) {
// 如果key是null,则将其转换成一个内部固定对象NULL_KEY
final Object k = maskNull(key);
retryAfterResize: for (;;) {
final Object[] tab = table;
final int len = tab.length;
// 这里将key映射到数组上的方式和HashMap一样:将key的hash值和数组长度进行取模运算来得出key在数组上的位置
// 这里有个细微差别就是计算出的值是偶数(0、2、4、...)正好对应数组的奇数位(1、3、5、...),
// 这样也保证了数组上的存储顺序为:key、value、key、value、...
int i = hash(k, len);
// 从i位置开始往后遍历数组,i = nextKeyIndex(i, len)相当于 i += 2
for (Object item; (item = tab[i]) != null; i = nextKeyIndex(i, len)) {
// 这里就是IdentityHashMap的主要特性了:只有当两个key1==key2才算是同一个key(即便equals方法相等也不算)
if (item == k) {
// 如果该位置和插入的key相等,那么将value存放在下一个位置(i+1)
@SuppressWarnings("unchecked")
V oldValue = (V) tab[i + 1];
tab[i + 1] = value;
return oldValue;
}
}
final int s = size + 1;
// Use optimized form of 3 * s.
// Next capacity is len, 2 * current capacity.
if (s + (s << 1) > len && resize(len))
continue retryAfterResize;
modCount++;
tab[i] = k;
tab[i + 1] = value;
size = s;
return null;
}
}
private static int nextKeyIndex(int i, int len) {
return (i + 2 < len ? i + 2 : 0);
}
/**
* Value representing null keys inside tables.
*/
static final Object NULL_KEY = new Object();
/**
* Use NULL_KEY for key if it is null.
*/
private static Object maskNull(Object key) {
return (key == null ? NULL_KEY : key);
}
private final ReferenceQueue queue = new ReferenceQueue<>();
public int size() {
if (size == 0)
return 0;
// 调用expungeStaleEntries方法,移除已经被回收的key-value
expungeStaleEntries();
return size;
}
public V get(Object key) {
...
Entry[] tab = getTable();
....
}
public V put(K key, V value) {
...
Entry[] tab = getTable();
...
}
private Entry[] getTable() {
expungeStaleEntries();
return table;
}
private void expungeStaleEntries() {
// 检查queue里面是否有元素,如果有则将其从链表中移除
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry e = (Entry) x;
int i = indexFor(e.hash, table.lengt
Entry prev = table[i];
Entry p = prev;
while (p != null) {
Entry next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
public final class ArrayMap implements Map {
// 基础容量大小
private static final int BASE_SIZE = 4;
// 基础容量数组的缓存
// 当需要扩容时,如果当前使用的是基础容量,则会将当前的mHashes和mArray进行缓存。
// 当缩小容量到基础容量(或其他ArrayMap对象需要基础容量数组)时,会将其从缓存中取出使用。
// 存储方式:mBaseCache有点像是一个链表,要缓存的mArray数组作为链表的节点,
// mArray[0]指向上一个节点,mArray[1]存储当前的mHashes数组
static Object[] mBaseCache;
// 基础容量数组的当前缓存数量
static int mBaseCacheSize;
// 两倍基础容量数组的缓存
// 原理同上
static Object[] mTwiceBaseCache;
// 两倍基础容量数组的当前缓存数量
static int mTwiceBaseCacheSize;
// 缓存的上限
private static final int CACHE_SIZE = 10;
// 用来存放key的hash值
int[] mHashes;
// 用来存放键值对
Object[] mArray;
// 键值对数量
int mSize;
}
来看看put操作:
public V put(K key, V value) {
final int osize = mSize;
final int hash;
int index;
// 计算key的hash值以及hash值在mHashes数组上的索引
if (key == null) {
hash = 0;
index = indexOfNull();
} else {
hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
index = indexOf(key, hash);
}
// 如果索引不小于0,表示这个key存在,这直接替换原来的值
if (index >= 0) {
// key的hash值在mHashes数组上的索引是index,
// 那么对应的key在mArray数组上的存储位置为index * 2,value在mArray数组上的存储位置为idnex * 2 + 1
// 计算value的存储位置(index<<1) + 1也就是index * 2 + 1
index = (index<<1) + 1;
// 用新的值替换旧的值,并返回旧的值
final V old = (V)mArray[index];
mArray[index] = value;
return old;
}
// indexOf方法在查找位置的时候如果没有找到key的hash值时会返回最后一次查找的位置,
// 目的是为了将新元素插入到这个位置。但是为了和是否有这个元素做区分,返回需要小于0,
// 所以在返回之前对结果值进行了取反,让其变成一个负数,
// 这里对该值再次取反,让其重新变成正数,会将新的键值对插入到该位置
// 这样也保证了如果有相同的hash值,则这些相同的hash会保存在一起(相邻)
index = ~index;
// 如果数组已满,则需要扩容
if (osize >= mHashes.length) {
// 如果osize大于两倍基础大小,则扩容为现在的1.5倍(osize >> 1等价于 osize / 2)
// 如果osize在基础大小的一倍到两倍之间,则扩容为基础大小的两倍
// 如果osize小于基础大小,则扩容为基础大小
final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
: (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
if (DEBUG) Log.d(TAG, "put: grow from " + mHashes.length + " to " + n);
// 保存当前的两个数组引用
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
// 新建n大小的数组并赋值给mHashes,新建n*2大小的数组并赋值给mArray
allocArrays(n);
// 检查是否有其他线程在操作该ArrayMap对象导致mSize发生了变化
if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
throw new ConcurrentModificationException();
}
// 如果扩容成功,则将旧数组中的元素复制到新数组中
if (mHashes.length > 0) {
if (DEBUG) Log.d(TAG, "put: copy 0-" + osize + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
}
// 释放两个旧的数组
// 如果还没有达到缓存的最大值且ohashes的长度为基础长度或基础长度的二倍时,则将这两个数组缓存起来
freeArrays(ohashes, oarray, osize);
}
// 如果插入的位置在数组中间,则将从插入位置开始的元素集体后移一位
if (index < osize) {
if (DEBUG) Log.d(TAG, "put: move " + index + "-" + (osize-index) + " to " + (index+1));
System.arraycopy(mHashes, index, mHashes, index + 1, osize - index);
// mArray数组需要后移两位,
// index << 1表示起始位置, (index + 1) << 1表示移动后的起始位置,等价于(index << 1) + 2
System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1);
}
// 检查是否有多线程操作
if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
if (osize != mSize || index >= mHashes.length) {
throw new ConcurrentModificationException();
}
}
// 将键值对插入到数组中
mHashes[index] = hash;
mArray[index<<1] = key;
mArray[(index<<1)+1] = value;
mSize++;
return null;
}
int indexOf(Object key, int hash) {
final int N = mSize;
// 如果当前还没有元素,则对0取反并返回,
// 注意位运算法则:~0的结果为-1,所以上面put方法中的if (index >= 0)判断是不成立的
// Important fast case: if nothing is in here, nothing to look for.
if (N == 0) {
return ~0;
}
// 对mHashes使用二分查找算法查找hash值的位置
// binarySearch返回的是最后查找的位置,如果没有找到,同样对返回值进行了取反操作
int index = binarySearchHashes(mHashes, N, hash);
// 如果不存在这个hash值,则直接返回负数的index
// If the hash code wasn't found, then we have no entry for this key.
if (index < 0) {
return index;
}
// 判断在mArray数组中该位置的key和当前要插入的key是否相等
// 如果是则返回该key的在mHashes数组中的index
// If the key at the returned index matches, that's what we want.
if (key.equals(mArray[index<<1])) {
return index;
}
// Search for a matching key after the index.
int end;
// 当有多个key的hash值相同时,这些相同的hash值会相邻存储
// 从当前位置开始往后在所有相邻的hash值范围内进行遍历,
// 当超出这个范围(mHashes[end] != hash时)则停止遍历
for (end = index + 1; end < N && mHashes[end] == hash; end++) {
// 如果找到相等的key时就停止遍历,并返回该位置
if (key.equals(mArray[end << 1])) return end;
}
// Search for a matching key before the index.
// 从当前位置开始往前在所有相邻的hash值范围内进行遍历
for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
// 如果找到相等的key时就停止遍历,并返回该位置
if (key.equals(mArray[i << 1])) return i;
}
// 如果都没找到key,此时end为最后一次遍历的位置,取反后返回
// Key not found -- return negative value indicating where a
// new entry for this key should go. We use the end of the
// hash chain to reduce the number of array entries that will
// need to be copied when inserting.
return ~end;
}
再来看看remove操作:
public V remove(Object key) {
final int index = indexOfKey(key);
// 如果存在该key,则移除对应的键值对,并返回value
if (index >= 0) {
return removeAt(index);
}
// 如果不存在该key,则返回null
return null;
}
public int indexOfKey(Object key) {
// 查找key的hash值在mHashes数组上的索引(indexOf方法前面已经讲过了)
return key == null ? indexOfNull()
: indexOf(key, mIdentityHashCode ? System.identityHashCode(key) : key.hashCode());
}
public V removeAt(int index) {
// 获取key在mHashes数组上的位置在mArray数组里对应的value值
final Object old = mArray[(index << 1) + 1];
// 当前键值对的数量
final int osize = mSize;
final int nsize;
if (osize <= 1) {
// 如果当前最多只有一对数据,则这对数据移除后,将mHashes和mArray设置为空
// Now empty.
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
freeArrays(mHashes, mArray, osize);
mHashes = EmptyArray.INT;
mArray = EmptyArray.OBJECT;
nsize = 0;
} else {
nsize = osize - 1;
// 如果当前数组容量大于两倍基础容量,且当前使用量不到总容量的1/3,则进行容量收缩操作,
// 以便释放暂时用不到的内存
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
// Shrunk enough to reduce size of arrays. We don't allow it to
// shrink smaller than (BASE_SIZE*2) to avoid flapping between
// that and BASE_SIZE.
// 如果当前使用量大于两倍基础容量,则将数组的容量缩小为当前使用量的1.5倍,
// 否则将数组容量缩小为两倍基础容量
final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to " + n);
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
// 重新申请两个较小的数组,复制给mHashes和mArray
allocArrays(n);
// 检查是否有多线程操作
if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
throw new ConcurrentModificationException();
}
// 如果要删除的元素在数组中间(不是第一个元素),则先将数组中index之前的元素复制到新数组中
if (index > 0) {
if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
System.arraycopy(ohashes, 0, mHashes, 0, index);
System.arraycopy(oarray, 0, mArray, 0, index << 1);
}
// 如果要删除的元素在数组中间(不是最后一个元素),则将idnex之后的元素复制到新数组中
if (index < nsize) {
if (DEBUG) Log.d(TAG, "remove: copy from " + (index+1) + "-" + nsize + " to " + index);
System.arraycopy(ohashes, index + 1, mHashes, index, nsize - index);
System.arraycopy(oarray, (index + 1) << 1, mArray, index << 1, (nsize - index) << 1);
}
} else {
// 如果不需要收缩容量,且删除的不是最后一个元素,则将index之后的元素集体前移一位
if (index < nsize) {
if (DEBUG) Log.d(TAG, "remove: move " + (index+1) + "-" + nsize + " to " + index);
System.arraycopy(mHashes, index + 1, mHashes, index, nsize - index);
System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1, (nsize - index) << 1);
}
// 将要删除的位置设置为null
mArray[nsize << 1] = null;
mArray[(nsize << 1) + 1] = null;
}
}
// 检查是否有多线程操作
if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
throw new ConcurrentModificationException();
}
// 重新设置尺寸并返回删除的值
mSize = nsize;
return (V)old;
}
最后再来看看释放数组和申请数组的操作:
// 释放数组
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
// 如果要释放的数组长度正好是两倍基础容量大小则进行缓存操作
if (hashes.length == (BASE_SIZE*2)) {
// 因为缓存在静态变量中的,所以这里需要加锁做线程同步
synchronized (ArrayMap.class) {
// 如果还没有达到最大的缓存容量则进行缓存,否则就放弃不缓存
if (mTwiceBaseCacheSize < CACHE_SIZE) {
// 缓存的时候将array作为一个节点,array[0]用来指向以前的缓存节点,array[1]用来存放hashes数组
array[0] = mTwiceBaseCache;
array[1] = hashes;
// 将array[2]开始到最后的位置里的元素清空
for (int i=(size<<1)-1; i>=2; i--) {
array[i] = null;
}
// 更新mTwiceBaseCache和mTwiceBaseCacheSize
mTwiceBaseCache = array;
mTwiceBaseCacheSize++;
if (DEBUG) Log.d(TAG, "Storing 2x cache " + array
+ " now have " + mTwiceBaseCacheSize + " entries");
}
}
}
// 对一倍基础容量的数组进行缓存,逻辑和上面的一样
else if (hashes.length == BASE_SIZE) {
synchronized (ArrayMap.class) {
if (mBaseCacheSize < CACHE_SIZE) {
array[0] = mBaseCache;
array[1] = hashes;
for (int i=(size<<1)-1; i>=2; i--) {
array[i] = null;
}
mBaseCache = array;
mBaseCacheSize++;
if (DEBUG) Log.d(TAG, "Storing 1x cache " + array
+ " now have " + mBaseCacheSize + " entries");
}
}
}
}
// 申请数组
private void allocArrays(final int size) {
if (mHashes == EMPTY_IMMUTABLE_INTS) {
throw new UnsupportedOperationException("ArrayMap is immutable");
}
// 如果申请的是两倍基础容量大小的数组,则检查是否有缓存数组可用
if (size == (BASE_SIZE*2)) {
// 因为缓存时静态变量,所以需要加锁进行线程同步
synchronized (ArrayMap.class) {
// 有缓存
if (mTwiceBaseCache != null) {
final Object[] array = mTwiceBaseCache;
mArray = array;
mTwiceBaseCache = (Object[])array[0];
mHashes = (int[])array[1];
array[0] = array[1] = null;
mTwiceBaseCacheSize--;
if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
+ " now have " + mTwiceBaseCacheSize + " entries");
return;
}
}
}
// 如果申请的是基础容量大小的数组,则检查是否有缓存数组可用
else if (size == BASE_SIZE) {
synchronized (ArrayMap.class) {
if (mBaseCache != null) {
final Object[] array = mBaseCache;
mArray = array;
mBaseCache = (Object[])array[0];
mHashes = (int[])array[1];
array[0] = array[1] = null;
mBaseCacheSize--;
if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
+ " now have " + mBaseCacheSize + " entries");
return;
}
}
}
mHashes = new int[size];
mArray = new Object[size<<1];
}
Note that this implementation is not intended to be appropriate for data structures that may contain large numbers of items. It is generally slower than a traditional HashMap, since lookups require a binary search and adds and removes require inserting and deleting entries in the array. For containers holding up to hundreds of items, the performance difference is not significant, less than 50%.
二、SparseArray
SparseArray:added in API level 1 SparseArrayCompat:android.support.v4
Note that this container keeps its mappings in an array data structure, using a binary search to find keys. The implementation is not intended to be appropriate for data structures that may contain large numbers of items. It is generally slower than a traditional HashMap, since lookups require a binary search and adds and removes require inserting and deleting entries in the array. For containers holding up to hundreds of items, the performance difference is not significant, less than 50%.
今天和同事争论一问题,关于静态变量与非静态变量的初始化顺序,谁先谁后,最终想整理出来!测试代码:
import java.util.Map;
public class T {
public static T t = new T();
private Map map = new HashMap();
public T(){
System.out.println(&quo
完整命令
CREATE DATABASE mynewdb USER SYS IDENTIFIED BY sys_password USER SYSTEM IDENTIFIED BY system_password LOGFILE GROUP 1 ('/u01/logs/my/redo01a.log','/u02/logs/m
多线程并发使用同一个channel
java.nio.BufferOverflowException: null
at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:183) ~[na:1.7.0_60-ea]
at java.nio.ByteBuffer.put(ByteBuffer.java:832) ~[na:1.7.0_60-ea]