源码阅读系列——基础篇(Android SparseArray 集合源码分析)

之前我们了解了很多JAVA的集合源码分析,我们继续开坑学习Android集合源码实现

1、构造函数

public class SparseArray<E> implements Cloneable {
    private int[] mKeys;  // 存放key的数组
    private Object[] mValues;  // 存放value的数组
    private int mSize;
    public SparseArray(int initialCapacity) {
        if (initialCapacity == 0) {
            mKeys = EmptyArray.INT;
            mValues = EmptyArray.OBJECT;
        } else {
            mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
            mKeys = new int[mValues.length];
        }
        mSize = 0;
    }
}

从这个成员变量,其实就能看出来SparseArray没有类似HashMap那种链表/树的结构,他存的就是value的object本身,key就是int本身,并且一个key对应一个value,key的数组mKeys和value的数组mValues长度理论上也是一致的。而且从命名也看出来,他没有转换hash值,所以也不会存在HashMap那种key碰撞的问题。

2、插入

public void put(int key, E value) {
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key); // 在当前已存入数据的key的数组mKeys中,按照二分查找,看这个key在mKeys中存不存在

        if (i >= 0) {
            mValues[i] = value;  // 如果存在这个key,覆盖这个key对应的value的值
        } else {
            // 如果没找到对应的key,返回的就是mKeys中最接近小于key的index(mKeys的索引),并取非。所以再取非一次,就得到原来的index了(也就是这里的i变量)
            i = ~i;

            // 如果返回的index,没有对应的value值,直接把这个key插在这个位置
            if (i < mSize && mValues[i] == DELETED) {
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }

            // 如果当前的mSize大于等于mKeys的长度 (代码一)
            if (mGarbage && mSize >= mKeys.length) {
                // 回收元素,实际是整理元素,我们后面会讲**加粗样式**
                gc();

                // 重新搜索一次
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }

            // 将value 和 key都插入到mKeys和mValue的数组中,这里涉及到一个扩容逻辑
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

这里有个疑问就是代码一为什么会存在mSize不等于mKeys的长度,mGarbage和gc有什么用,后面删除元素,我们再看
看一下扩容逻辑

public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
        assert currentSize <= array.length;

        // 没有超过数组的最大长度,则正常插入
        if (currentSize + 1 <= array.length) {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }
       
        // 超过数组的最大长度,growsize扩容
        boolean[] newArray = new boolean[growSize(currentSize)];
        // 存入元素
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }

简单看一下扩容的实现

public static int growSize(int currentSize) {
    return currentSize <= 4 ? 8 : currentSize * 2;
}

也就是说,如果大小<=4 就直接设置为8,否则翻倍扩容。

3、删除

public void delete(int key) {
   // 二分法查找对应的key
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

    if (i >= 0) {
        if (mValues[i] != DELETED) {
           // 找到之后,将对应的index的value设置为DELETED,并且致标志位mGarbage
            mValues[i] = DELETED;
            mGarbage = true;
        }
    }
}

可以看到删除的时候,并没有真的在mKeys中移除i,也没有做mValue中的移除操作,因为这样势必会对数组再次进行遍历的挪移操作,很显然效率很低,所以插入时就能利用DELETED的标志位进行替换新的key和value的值。但是也存在两个问题:
1、key的值会变得相对分散,二分查找的效率不高,比如我本来实际上就只有10个元素,由于delete元素过多,查找的数组长度会变大
2、由于delete的元素过多,数组膨胀本身会造成很多的脏数据,对数组本身利用率也不高。
所以在insert的时候,我们看到存在mSize >= mKeys.length。所以同时满足mGarbage = true和mSize >= mKeys.length,我们对数组进行一次整理

private void gc() {
        int n = mSize;
        int o = 0;
        int[] keys = mKeys;
        Object[] values = mValues;

        for (int i = 0; i < n; i++) {
            Object val = values[i];

            if (val != DELETED) {
                if (i != o) {
                    keys[o] = keys[i];
                    values[o] = val;
                    values[i] = null;
                }

                o++;
            }
        }

        mGarbage = false;
        mSize = o;
    }

这个过程很简单,也就是找到所有非DELETED的元素,并且依次加到数组中,这样数组就有离散的分布,变成了集中的排序到一起。mSize也就此时才能真正表示SparseArray中元素的真实个数。这也就是为什么我们获取size()的时候,调用方法这样实现:

public int size() {
    if (mGarbage) {
        gc();
    }

    return mSize;
}

在gc()操作的最后,也需要设置回标志位mGarbage为false,表示当前已经完成元素整理了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
加壳是指将一个已经编译好的可执行文件,通过加密、压缩等手段,使其变得难以被反汇编和逆向工程,从而提高软件的安全性。其中,UPX 是一种常用的加壳工具,可以将 Windows 和 Linux 下的可执行文件进行压缩和加密。 下面我们来分析一下 UPX 的加壳原理: 1. 文件压缩 UPX 首先对可执行文件进行压缩,可以使用多种压缩算法,包括 LZMA、LZ77 和 Huffman 等。这些算法可以将文件中的重复数据或者无效数据进行删除或者压缩,从而减小文件的体积。 2. 加密 UPX 还可以对压缩后的文件进行加密,可以使用多种加密算法,包括 Blowfish、AES 和 RC4 等。加密可以使得文件内容变得难以被窃取和分析,从而提高软件的安全性。 3. 重定位 由于压缩和加密可能会影响可执行文件的结构,因此 UPX 还需要对文件进行重定位,以确保可执行文件在运行时能够正确加载和执行。重定位是指将可执行文件中的函数地址和数据地址进行修改,使得它们能够正确地指向压缩和加密后的数据。 4. 解压 当可执行文件被运行时,UPX 会先解压它,然后将它加载到内存中,并将其重定位。解压是指将压缩和加密后的文件恢复成原来的可执行文件,以便能够正确地执行其中的函数和数据。 总的来说,UPX 的加壳原理就是通过文件压缩、加密和重定位等手段,使得可执行文件变得难以被分析和逆向工程。但是,需要注意的是,UPX 只是一种基本的加壳工具,对于一些高级的反调试和反反汇编技术,可能并不能有效地提高软件的安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值