美文网首页
SparseArray源码

SparseArray源码

作者: 陆元伟 | 来源:发表于2020-05-30 17:59 被阅读0次

    首先查看put方法,它限定只能由int类型的作为key

     public void put(int key, E value) {
            //1 二分查找key对应的index,如果没有查到,返回应该对应位置的~位,
            int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
            //找到,直接更新值
            if (i >= 0) {
                mValues[i] = value;
            } else {
                
                i = ~i;
                //2 如果该位置已经有个,则直接赋值,
                if (i < mSize && mValues[i] == DELETED) {
                    mKeys[i] = key;
                    mValues[i] = value;
                    return;
                }
                //3如果做了删除操作
                if (mGarbage && mSize >= mKeys.length) {
                    //把删除的地方置空
                    gc();
                    //删除后数据长度有变化,要重新获取它的索引值
                    // Search again because indices may have changed.
                    i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
                }
                //在i的位置插入key
                mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
                //在i的位置插入value
                mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
                mSize++;
            }
        }
    

    注释1 为什么可以对key,二分查找? 理解binarySearch后的代码后才恍如大悟
    binarySearch方法,乍一看,以为只是个二分查找,但是看到它返回了位置的非~ 操作后,细细品代码,发现通过这个返回值在做一个非操作,就可以对数组排好序了。举个栗子,假设现在key数组里面是[1,2,4,6],要插入的key是3,则要插入索引值为2,则该方法返回了 ~2,
    如果找到该值,则返回对应的索引,如果没有找到,则返回应该要插入的索引值的~ 位,这样外面就可以根据返回值判断是否找到未找到,并且再通过一个非~操作,就可以拿到它应该要插入的位置,这样就把数据排好序了

    binarySearch方法如下,一遍没看明白,多看几遍,就能发现它的不一般

    static int binarySearch(int[] array, int size, int value) {
            int lo = 0;
            int hi = size - 1;
    
            while (lo <= hi) {
                final int mid = (lo + hi) >>> 1;
                final int midVal = array[mid];
    
                if (midVal < value) {
                    lo = mid + 1;
                } else if (midVal > value) {
                    hi = mid - 1;
                } else {
                    return mid;  // value found
                }
            }
            return ~lo;  // value not present
        }
    

    要明白注释2 就得看它的删除方法,它删除后,数组长度还是不变,只是删除的那个位置对象指向了一个DELETED对象。这样如果delete和put都是同一个key的话,只需要对那个索引值的位置赋值即可,不需要任何复制数据的操作。

     public void delete(int key) {
            //查找,和上面方法一样
            int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
            //找到位置
            if (i >= 0) {
                //不是直接置空,而是指向一个DELETED对象
                if (mValues[i] != DELETED) {
                    mValues[i] = DELETED;
                    mGarbage = true;
                }
            }
        }
    

    删除后它的长度都不变,那这样子获取它的长度岂不是有问题?继续看它获取size的方法,mGarbage变量是在删除时才会设置为true,这个时候我们就要看gc方法了

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

    在这个方法里面,可以看到它把数组里面的DELETE的位置的都去掉了。重新修正了mSize值。

    private void gc() {
            // Log.e("SparseArray", "gc start with " + mSize);
    
            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;
    
            // Log.e("SparseArray", "gc end with " + mSize);
        }
    

    在GrowingArrayUtils方法里面看到,他增长长度的方式,如果小于要么是8,或者当前长度的2倍

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

    get方法,通过二分查找方式找到对应的索引,如果没有找到,或者那个位置已经delete了,返回null

    public E get(int key) {
        return get(key, null);
    }
    
    public E get(int key, E valueIfKeyNotFound) {
            int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    
            if (i < 0 || mValues[i] == DELETED) {
                return valueIfKeyNotFound;
            } else {
                return (E) mValues[i];
            }
     }
    

    总结下:

    key(不是它的hashcode)保存在mKeys[]的下一个可用的位置上。所以不会再对key自动装箱了。
    value保存在mValues[]的下一个位置上,value还是要自动装箱的,如果它是基本类型。
    查找的时候:
    查找key还是用的二分法查找。也就是说它的时间复杂度还是O(logN)
    知道了key的index,也就可以用key的index来从mValues中检索出value。
    相较于HashMap,我们舍弃了Entry和Object类型的key,放弃了HashCode并依赖于二分法查找。在添加和删除操作的时候有更好的性能开销

    相关文章

      网友评论

          本文标题:SparseArray源码

          本文链接:https://www.haomeiwen.com/subject/oushzhtx.html