ArrayMap的源码分析

ArrayMap详解
ArrayMap是一种优化内存使用的数据结构,适用于小规模数据集,利用两个数组分别存储key的hash值和value,提供高效的增删查改操作。本文深入解析ArrayMap的工作原理,包括其构造、扩容、查找和删除机制。

简介

ArrayMap是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值;在数据量不大的情况下,用来代替HashMap;

int[] mHashes;是key的hash值的数组;
Object[] mArray;是key,value数组,这两个值的下标和mHashes的下标存在index<<1,(index<<1)+1的关系;
mSize为数组mHashes的插入元素个数,和mHashes的length不同;mSize<=mHashes.length;

构造函数

public ArrayMap() {
    super();
}

public SimpleArrayMap() {
    mHashes = ContainerHelpers.EMPTY_INTS;
    mArray = ContainerHelpers.EMPTY_OBJECTS;
    mSize = 0;
}
添加操作

put():插入一个元素;
put()方法主要做了几件事:1)如果mHashes数组中存在当前key的hash值,则返回对应的index,否则返回负数(负数的绝对值,就是hash在mHashes数组中的下标;并且在二分查找时,已经确定位置,已经排好序了);2)如果mHashes数组中存在当前key的hash值,则返回mArray中对应的value值;如果不存在继续往下执行;3),如果已经达到最大容量,扩容,并且copy内容;4)插入元素,并且移动原来位置;

 @Override
public V put(K key, V value) {
    final int osize = mSize;
    final int hash;
    int index;
	//如果mHashes数组中存在当前key的hash值,则返回对应的index,否则返回负数;
    if (key == null) {
        hash = 0;
        index = indexOfNull();
    } else {
        hash = mIdentityHashCode ? System.identityHashCode(key) : key.hashCode();
        index = indexOf(key, hash);
    }
	//返回mArray中对应的value值
    if (index >= 0) {
        index = (index<<1) + 1;
        final V old = (V)mArray[index];
        mArray[index] = value;
        return old;
    }

    index = ~index;
	//如果已经插入元素的个数>=mHashes.length;确定扩容规则,扩容,并且copy内容;
    if (osize >= mHashes.length) {
		//确定新的容量大小n
        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;
		//扩容
        allocArrays(n);

        if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
            throw new ConcurrentModificationException();
        }
		//copy原来的内容
        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);
        }
		//释放
        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);
        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;
}

indexOf()方法:如果mHashes数组中存在当前key的hash值,并且key相等,则返回对应的index,否则返回负数(负数的绝对值,就是hash在mHashes数组中的下标;并且在二分查找时,已经确定位置,已经排好序了);indexOfNull()和indexOf()方法,逻辑一致;

int indexOf(Object key, int hash) {
    final int N = mSize;

	////如果mHashes数组没有插入元素,直接返回-1;
    // Important fast case: if nothing is in here, nothing to look for.
    if (N == 0) {
        return ~0;
    }
	//二分查找当前hash在数组mHashes中的index,如果不存在,则返回负数;
    int index = binarySearchHashes(mHashes, N, hash);
	//如果不存在,直接返回负数;
    // If the hash code wasn't found, then we have no entry for this key.
    if (index < 0) {
        return index;
    }
	//判断index按照一定规则得到newIndex在mArray中的key,判断是否相等,因为key的hash值相同的相同的情况下,equal可能不相等;
    // If the key at the returned index matches, that's what we want.
    if (key.equals(mArray[index<<1])) {
        return index;
    }
	//搜索hash值相等,并且key也相等;如果存在返回对应index,不存在返回负数;分段搜素,提高效率;
    // Search for a matching key after the index.
    int end;
    for (end = index + 1; end < N && mHashes[end] == hash; end++) {
        if (key.equals(mArray[end << 1])) return end;
    }

    // Search for a matching key before the index.
    for (int i = index - 1; i >= 0 && mHashes[i] == hash; i--) {
        if (key.equals(mArray[i << 1])) return i;
    }
	//key的hash在mHashes中存在,但是key在mArray数组中不存在,返回负数;
    // 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;
}

putAll():基本和单个元素插入一致;

@Override
public void putAll(Map<? extends K, ? extends V> map) {
	//确保容量
    ensureCapacity(mSize + map.size());
    for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
        put(entry.getKey(), entry.getValue());
    }
}

 public void ensureCapacity(int minimumCapacity) {
    final int osize = mSize;
    if (mHashes.length < minimumCapacity) {
        final int[] ohashes = mHashes;
        final Object[] oarray = mArray;
        allocArrays(minimumCapacity);
        if (mSize > 0) {
            System.arraycopy(ohashes, 0, mHashes, 0, osize);
            System.arraycopy(oarray, 0, mArray, 0, osize<<1);
        }
        freeArrays(ohashes, oarray, osize);
    }
    if (CONCURRENT_MODIFICATION_EXCEPTIONS && mSize != osize) {
        throw new ConcurrentModificationException();
    }
}

get()方法:先在mHashes数组找到在对应key的index,如果index>=0;再从mArray中找到对应的value;否则直接返回null;

 @Override
public V get(Object key) {
    final int index = indexOfKey(key);
    return index >= 0 ? (V)mArray[(index<<1)+1] : null;
}

remove():找到下标index,如果index>=0 删除对应的hash值,key,value;否则返回null;

 @Override
public V remove(Object key) {
	//根绝key的hashCode找到key在mHashes数组中的index;
    final int index = indexOfKey(key);
	//index>0 说明mHashes数组中存在对应的key的hashCode
    if (index >= 0) {
        return removeAt(index);
    }

    return null;
}

public V removeAt(int index) {
	//根绝index找到mArray中对应的值value
    final Object old = mArray[(index << 1) + 1];
    final int osize = mSize;
    final int nsize;
	//mHashes数组最多只有一个插入元素,直接将mHashes,mArray数组清空;
    if (osize <= 1) {
        // Now empty.
        if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
        freeArrays(mHashes, mArray, osize);
        mHashes = ContainerHelpers.EMPTY_INTS;
        mArray = ContainerHelpers.EMPTY_OBJECTS;
        nsize = 0;
	//mHashes数组的插入元素个数>1;
    } else {
		
        nsize = osize - 1;
        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.
			//确定数组mHashes新的容量n;
            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;
			//减容
            allocArrays(n);

            if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
                throw new ConcurrentModificationException();
            }

			//把原mHashes数组0-index的元素赋值到新的数组中,把原mArray数组0-2*index的元素赋值到新的数组中,从0坐标开始赋值;
            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);
            }
			//如果index在中间,不是最后一个;将剩下的元素复制到对应的新数组中;
            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+1的元素往前移;
            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);
            }
            mArray[nsize << 1] = null;
            mArray[(nsize << 1) + 1] = null;
        }
    }
    if (CONCURRENT_MODIFICATION_EXCEPTIONS && osize != mSize) {
        throw new ConcurrentModificationException();
    }
    mSize = nsize;
    return (V)old;
}

问题一:不同的key,相同hashCode ArrayMap是如何处理的呢?

相同的hash值,key不相同的情况下,mHashes数组会将该hash值也存入;在获取下标时,找到对应hash值的index后,会判断equals(),相等才会返回index;如果不相等,则根据index分别向前,向后分段搜索;查找相同hash值,并且equals()也相等的index;具体业务逻辑可详看indexOf()方法;

比较

SparseArray和ArrayMap具体实现类似,主要区别在于key必须是int类型;

SparseArray,ArrayMap是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值;

ArrayMap和SparseArray一样,也会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,然后通过index来进行添加、查找、删除等操作,所以,应用场景和SparseArray的一样,如果在数据量比较大的情况下,那么它的性能将退化至少50%。

满足下面两个条件我们可以使用ArrayMap,SparseArray代替HashMap:

  • 数据量不大,最好在千级以内
  • key必须为int类型,这中情况下的HashMap可以用SparseArray代替:

以上就是ArrayMap的主要内容;如有问题,请多指教,谢谢!

<think>我们即将分析ArrayMap的源码,重点在于其内部数据结构和工作原理。根据引用[2]和[3],ArrayMap使用两个数组来存储数据:一个存储键的哈希值,另一个交替存储键值对。我们将按照以下步骤进行: 1. 数据结构:介绍ArrayMap内部的两个数组结构。 2. 核心方法:包括put、get、remove等操作的源码分析。 3. 性能分析:与HashMap的对比,特别是在内存和效率上的优势与局限。 4. 适用场景:总结ArrayMap的最佳使用场景。 注意:由于用户要求使用中文,且所有数学表达式用$...$或$$...$$格式,但本问题不涉及复杂数学公式,因此主要关注代码和文字描述。引用标识需自然地添加在引用段落末尾。 下面开始回答: </think>### ArrayMap 源码分析(基于 Android 源码) ArrayMap 是 Android 优化的键值对数据结构,核心设计为**双数组存储**: 1. **`int[] mHashes`**:存储键的哈希值(升序排列) 2. **`Object[] mArray`**:交替存储键值对(偶数索引存键,奇数索引存值) --- #### 一、核心数据结构 存储原理示意图: ``` mHashes = [hash0, hash1, hash2] mArray = [key0, value0, key1, value1, key2, value2] ``` - **键值定位**:键 `key_i` 的位置满足 `mHashes[i] = key_i.hashCode()` - **值获取**:`value_i = mArray[2*i + 1]` --- #### 二、核心方法源码分析 ##### 1. 构造函数 ```java public ArrayMap(int capacity) { if (capacity > 0) { allocArrays(capacity); // 初始化双数组 } else { mHashes = EmptyArray.INT; mArray = EmptyArray.OBJECT; } } ``` ##### 2. `put()` 方法流程 ```java public V put(K key, V value) { final int hash = key.hashCode(); int index = binarySearch(mHashes, mSize, hash); // 二分查找哈希数组 if (index >= 0) { // 键已存在 index = (index << 1) + 1; // 计算值索引 final V old = (V) mArray[index]; mArray[index] = value; // 替换旧值 return old; } index = ~index; // 计算新元素插入位置 if (mSize >= mHashes.length) { growSize(); // 扩容(通常增长50%) } // 移动后续元素腾出位置 System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index); System.arraycopy(mArray, index << 1, mArray, (index + 1) << 1, (mSize - index) << 1); // 插入新键值对 mHashes[index] = hash; mArray[index << 1] = key; mArray[(index << 1) + 1] = value; mSize++; return null; } ``` **关键点**: - 哈希冲突处理:依赖 `key.equals()` 精确匹配(无链表/红黑树) - 扩容机制:调用 `growSize()` 创建新数组并拷贝数据[^3] ##### 3. `get()` 方法 ```java public V get(Object key) { final int hash = key.hashCode(); int index = binarySearch(mHashes, mSize, hash); // 二分查找 if (index < 0) return null; return (V) mArray[(index << 1) + 1]; // 返回对应值 } ``` **性能**:时间复杂度 $O(\log n)$(二分查找) ##### 4. `remove()` 方法 ```java public V remove(Object key) { final int hash = key.hashCode(); int index = binarySearch(mHashes, mSize, hash); if (index < 0) return null; return removeAt(index); // 实际移除操作 } public V removeAt(int index) { final V old = (V) mArray[(index << 1) + 1]; // 压缩数组:前移后续元素覆盖删除项 if (mSize > 1) { System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index - 1); System.arraycopy(mArray, (index + 1) << 1, mArray, index << 1, (mSize - index - 1) << 1); } mSize--; return old; } ``` **优化**:直接移动数组元素避免标记删除[^2] --- #### 三、性能特性对比 | **操作** | **ArrayMap** | **HashMap** | |----------|---------------------|---------------------| | 内存占用 | 低(无链表结构) | 高(桶+链表/红黑树)| | 插入效率 | $O(\log n)$ ~ $O(n)$ | $O(1)$ ~ $O(n)$ | | 查询效率 | $O(\log n)$ | $O(1)$ ~ $O(n)$ | | 删除效率 | $O(n)$ | $O(1)$ ~ $O(n)$ | **适用场景**: - 数据量小(建议 < 1000 项) - 内存敏感场景(如移动设备) - 键为自定义对象(需正确实现 `hashCode()` 和 `equals()`)[^1][^3] --- #### 四、设计优势与局限 **优势**: 1. **内存紧凑**:双数组结构减少对象头和指针开销 2. **缓存友好**:连续内存访问提高 CPU 缓存命中率 **局限**: 1. 大规模数据时操作性能下降(数组移动成本高) 2. 非线程安全(需外部同步) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值