1 ArrayMap特点
1.1 扩容机制上
- HashMap当前map的容量乘以扩容因子小于等于当前数量的时候就会引发扩容机制。每次扩容都会变为当前元素的二倍。
- ArrayMap的扩容机制,扩容时机在已经容量已经满了时候引发扩容。每次扩容的大小为原理容量的1.5倍。
- ArrayMap还有缩容机制,当ArrayMap内的元素小于自己容量的三分之一的时候就会引发缩容机制,最后缩为当前元素数量的1.5倍。
- 扩容的时候缓存机制,缓存的时候只会出现在两种情况下,扩容到mBaseSize(8)或者缩容到(4)的时候。
1.2 处理hash冲突上
- hashmap会采用拉链法。
- ArrayMap如果出现了hash冲突就开放地址法。
1.3 ArrayMap的存储结构
一个数组存的是key的hash值,一个存在的key value。结构如下。
1.4 非线程安全
嗯,如标题所言是非线程安全的,多个线程共享ArrayMap的时候就有数据不一致的问题。
2 关于存/取/删元素
2.1 存
2.2.1 主线逻辑
朴素的讲,就是往ArrayMap中存键值对,如果要存的这个key在ArrayMap中已经存在了的就用新值替换掉,如果不存在key就连值带键都存在里面。 大体的过程是这样的:
- 计算key 的hashCode值。
- 使用二分查找在mHash[]中索引hashCode的位置,找到后对比是否跟key的值相等。这个地方注意,hash值相等不代表两个值相等 ,就跟我与你同为雄性,不代表我们的身份证号一样。
- 如果key找到已经在Array中存在,就把值替换掉,已知hash值的在mHashp[]数组中的位置,index*2就是键在mArray数组中的位置。index * 2+1就是value的位置。
- 如果不存在就把新的键值对存进入。
2.1.2 扩容
当ArrayMap的容量已满的时候就要使用扩容机制了,具体怎么扩呢源码中找答案。
if (osize >= mHashes.length) {
final int n = osize >= (BASE_SIZE*2) ? (osize+(osize>>1))
: (osize >= BASE_SIZE ? (BASE_SIZE*2) : BASE_SIZE);
final int[] ohashes = mHashes;
final Object[] oarray = mArray;
allocArrays(n);
...
一波三目运算道出真理:
- 当前容量大于等于8的时候就只扩充1.5倍
- 当前容量大于等于4小于8时,扩容到8.
- 小于4时扩容到4
2.2 取数据
没什么好说的 什么底层源码 什么设计模式 一梭子代码
@Override
public V get(Object key) {
final int index = indexOfKey(key);
return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}
2.3 删元素
2.3.1 删除
删除操作,本质就是对数组进行复制操作。
2.3.2 缩容机制
当ArrayMap中的元素数小于容量的1/3的时候启动缩容模式,缩容到当期数量(不是容量)的1.5倍,如果数量小于8直接扩容到8。
if (mHashes.length > (BASE_SIZE*2) && mSize < mHashes.length/3) {
final int n = osize > (BASE_SIZE*2) ? (osize + (osize>>1)) : (BASE_SIZE*2);
....
}
3、 关于缓存
static Object[] mBaseCache;//容量为4,ArrayMap缓存链表
static int mBaseCacheSize;//容量为4的缓存数量
static Object[] mTwiceBaseCache;//容量为8的Array缓存链表
static int mTwiceBaseCacheSize;//容量为8的缓存数量
- ArrayMap会对容量为4和8的两种ArrayMap进行缓存。
- 而且对每一种ArrayMap缓存的数量也是有限制。
3.1 回收
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
//当前hashs数据的长度为8
if (hashes.length == (BASE_SIZE*2)) {
...
} else if (hashes.length == BASE_SIZE) {
synchronized (ArrayMap.class) {
if (mBaseCacheSize < CACHE_SIZE) {
array[0] = mBaseCache;//把array的第一个元素指向当前的mBaseCache
array[1] = hashes;//把array的第二个元素指向hash数组
for (int i=(size<<1)-1; i>=2; i--) {//把第二个以后的元素都置空
array[i] = null;
}
mBaseCache = array;//mBaseCache执行array
mBaseCacheSize++;//数字加1
}
}
}
}
看注释,应该就明白了,如果还不明白看看下面的这张图。
关注下,freeArrays()方法中的synchronized关键字,不是说好的线程不安全吗,怎么还有出现synchronized呢
线程安全问题的本质是,可变数据在多条线程中共享。 mBaseCache和mTwiceBaseCache都是静态的,也就是说会存在他们多个ArrayMap对象访问他们的情况。反过来思考,一个ArrayMap对象,在单一线程中创建使用,扩容缩容的时候如果没有加同步块会出现线程安全问题。
3.2 重用
就缓存两种容量的map,容量为4和容量为8的
private void allocArrays(final int size) {
if (size == (BASE_SIZE*2)) {
//当要分配数组的容量为8时
...
} else if (size == BASE_SIZE) {
//当分配数组的容量为4时
synchronized (ArrayMap.class) {
if (mBaseCache != null) {
final Object[] array = mBaseCache;
mArray = array;//mArray指向缓存链表中第一个数组
mBaseCache = (Object[])array[0];//mBaseCache指向下一个数组
mHashes = (int[])array[1];//mHashes数组指向arry的第一个元素
array[0] = array[1] = null;//置空
mBaseCacheSize--;
return;
}
}
}
mHashes = new int[size];
mArray = new Object[size<<1];
}
4 适用条件
- google官方建议当数据量小于1000的时候,推荐使用ArrayMap,但于1000使用HashMap。ArrayMap查询效率为O(logN),删除和添加元素的时候都要移动数组效率较低 。HashMap的查询更改速度为O(1)。但是ArrayMap优势就是省内存!
- 除了ArrayMap之外还有SpareArray,原理和ArrayMap差不多,但他的key为int类型,避免了基本数据类型的拆箱装箱带来的效率问题。当确定value的值为int/long/boolean的时候就可以使用SparseIntArray/SparseLongArray/SparseBooleanArray。
附录
补充一个二分查找算法
public static int binarySearch(int[] array,int value){
if (array == null){
throw new IllegalArgumetsException("不能为空");
}
int lo = 0;
int li = array.length - 1;
while(lo<=li){
int index = (lo+li)/2;
int midValue = array[index];
if(value>midValue){
li= index+1;
}else if(value<midValue){
lo = index-1;
}else{
return index;
}
}
return ~lo;
}