`

Android SparseArray源码阅读

 
阅读更多
/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;

import libcore.util.EmptyArray;

/**
 * SparseArrays map integers to Objects.  Unlike a normal array of Objects,
 * there can be gaps in the indices.  It is intended to be more memory efficient
 * than using a HashMap to map Integers to Objects, both because it avoids
 * auto-boxing keys and its data structure doesn't rely on an extra entry object
 * for each mapping.
 * 
 * SparseArray用于映射integers到object。但不像普通数组那样,sparseArray的元素间没有无用元素。
 * 在映射integers到object的过程中,SparseArray由于采用避免自动装箱的keys和它的数据结构不依赖额外
 * 的对象来存储映射关系的实现,因此它比hashMap的内存使用更高效一些。
 * 
 * 
 * <p>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%.</p>
 * 
 * 注意:SparseArray在查找keys的过程中采用了二分查找, 这种实现不适合数据量大的情况。由于查找时要用到二分查找,
 * 添加删除时涉及到数组其他元素的挪动,因此通常SparseArray会比hashMap慢。当处理上百的数据量,这种性能差异不是特别
 * 明显,性能差异不超过50%。
 * 
 *
 * <p>To help with performance, the container includes an optimization when removing
 * keys: instead of compacting its array immediately, it leaves the removed entry marked
 * as deleted.  The entry can then be re-used for the same key, or compacted later in
 * a single garbage collection step of all removed entries.  This garbage collection will
 * need to be performed at any time the array needs to be grown or the the map size or
 * entry values are retrieved.</p>
 * 
 * 为了优化性能,SparseArray针对remove case作了优化,remove时它不是立即挤压数组空间,而是标记为delete。
 * 这个被标记的元素要么被重复利用,要么在多次remove之后通过一次gc操作中被挤压出去。
 * gc需要在下列情况之前被执行:数组要扩容;get map size;get values;
 * 
 *
 * <p>It is possible to iterate over the items in this container using
 * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
 * <code>keyAt(int)</code> with ascending values of the index will return the
 * keys in ascending order, or the values corresponding to the keys in ascending
 * order in the case of <code>valueAt(int)</code>.</p>
 * 
 * 可以用keyAt valueAt实现遍历
 */
public class SparseArray<E> implements Cloneable {
    //巧妙的flag
    //当有元素被remove delete时,先暂时设置该对象为DELETED,并置mGarbage=true
    //用以提升性能,体现在哪里?问题1
    private static final Object DELETED = new Object();
    private boolean mGarbage = false;

    //存储的数据结构,两个数组加一个size
    //特殊在哪里?或者怎么用的呢?问题2
    private int[] mKeys;
    private Object[] mValues;
    private int mSize;

    /**
     * Creates a new SparseArray containing no mappings.
     */
    public SparseArray() {
        this(10);//默认容量是10个元素
    }

    /**
     * Creates a new SparseArray containing no mappings that will not
     * require any additional memory allocation to store the specified
     * number of mappings.  If you supply an initial capacity of 0, the
     * sparse array will be initialized with a light-weight representation
     * not requiring any additional array allocations.
     */
    public SparseArray(int initialCapacity) {
        if (initialCapacity == 0) {
            //看EmptyArray的实现便知,mKeys的初值等于new int[0], 其他同理
            mKeys = EmptyArray.INT;
            mValues = EmptyArray.OBJECT;
        } else {
            //newUnpaddedObjectArray有啥特性?问题3
            mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
            mKeys = new int[mValues.length];
        }
        mSize = 0;
    }

    @Override
    @SuppressWarnings("unchecked")
    public SparseArray<E> clone() {
        SparseArray<E> clone = null;
        try {
            //java深拷贝
            clone = (SparseArray<E>) super.clone();
            clone.mKeys = mKeys.clone();
            clone.mValues = mValues.clone();
        } catch (CloneNotSupportedException cnse) {
            /* ignore */
        }
        return clone;
    }

    /**
     * Gets the Object mapped from the specified key, or <code>null</code>
     * if no such mapping has been made.
     */
    public E get(int key) {
        return get(key, null);
    }

    /**
     * Gets the Object mapped from the specified key, or the specified Object
     * if no such mapping has been made.
     */
    @SuppressWarnings("unchecked")
    public E get(int key, E valueIfKeyNotFound) {
        //二分查找, 返回值含义是什么?问题4
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i < 0 || mValues[i] == DELETED) {
            // 没找到对应的key,或者找到了Key,但该元素被标记为delete
            // 则返回默认值
            return valueIfKeyNotFound;
        } else {
            // i>0 且该位置的元素未被标记为待删除,返回该值
            return (E) mValues[i];
        }
    }

    /**
     * Removes the mapping from the specified key, if there was any.
     */
    public void delete(int key) {
        //二分查找
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        //若找到了
        if (i >= 0) {
            //若之前未被标记delete
            if (mValues[i] != DELETED) {
                //标记为delete,garbage=true
                mValues[i] = DELETED;
                mGarbage = true;
            }
        }
    }

    /**
     * Alias for {@link #delete(int)}.
     */
    public void remove(int key) {
        delete(key);
    }

    /**
     * Removes the mapping at the specified index.
     */
    public void removeAt(int index) {
        //移除特定位置的元素,注意传入的是mValues的index不是Key
        if (mValues[index] != DELETED) {
            mValues[index] = DELETED;
            mGarbage = true;
        }
    }

    /**
     * Remove a range of mappings as a batch.
     *
     * @param index Index to begin at
     * @param size Number of mappings to remove
     */
    public void removeAtRange(int index, int size) {
        //确定结束位置
        final int end = Math.min(mSize, index + size);
        //从起点开始循环 remove
        for (int i = index; i < end; i++) {
            removeAt(i);
        }
    }

    private void gc() {
        // Log.e("SparseArray", "gc start with " + mSize);

        //'挤'空间了, 提供空间利用率
        int n = mSize;
        int o = 0;
        int[] keys = mKeys;
        Object[] values = mValues;

        //循环整个元素区间
        /*
         * 模拟一遍就明白了
         *i    = 0 1 2 3 4 5  6 
         *key  = 1 2 6 8 9 10 11
         *value= x y z D u D  w
         *value D代表delete的对象
         *
         *i=0时,val==x, val != DELETED,i==o, o++, o == 1
         *i=1时,val==y, val != DELETED,i==o, o++, o == 2
         *i=2时,val==z, val != DELETED,i==o, o++, o == 3
         *i=3时,val==D, val == DELETED,o == 3
         *i=4时,val==u, val != DELETED,i!=o, keys[3] = keys[4], values[3] = values[4], values[4] = null, o++, o == 4
         *   (i=4之后,key= 1 2 6 9 9 10 11, value= x y z u null D w)
         *i=5时,val==D, val == DELETED,o == 4
         *   (i=5之后,key= 1 2 6 9 9 10 11, value= x y z u null D w)较上一步没变化
         *i=6时,val==w, val != DELETED,i!=o, keys[4] = keys[6], values[4] = values[6], values[6] = null, o++, o == 5
         *   (i=6之后,key= 1 2 6 9 11 10 11, value= x y z u w D null)
         *循环结束   
         *mSize = 5
         */
        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);
    }

    /**
     * Adds a mapping from the specified key to the specified value,
     * replacing the previous mapping from the specified key if there
     * was one.
     */
    public void put(int key, E value) {
        //先二分查找,确定插入位置,保证了key数组的有序性
        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            //找到了,直接替换
            mValues[i] = value;
        } else {
            //一点小技巧,跟二分查找的返回值有关
            //没找到的情况下i的意义是 i = -insertPoint -1,比如i=-2,则insertPoint=1
            //而-2在内存中存的是补码,对他取反刚好得insertPoint,跟上面计算结果一样,但位操作更高效
            i = ~i;

            //若i在size范围内,且刚好对应位置标记为delete了,直接放入
            if (i < mSize && mValues[i] == DELETED) {
                mKeys[i] = key;
                mValues[i] = value;
                return;
            }

            //若前面if不成立,即i超出了size范围,或者对应的位置的元素是有效的
            if (mGarbage && mSize >= mKeys.length) {
                //压缩空间
                gc();

                //重新查找插入点
                // Search again because indices may have changed.
                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
            }

            //插入,若空间不够则会重新分配数组
            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
            mSize++;
        }
    }

    /**
     * Returns the number of key-value mappings that this SparseArray
     * currently stores.
     */
    public int size() {
        if (mGarbage) {
            gc();
        }

        return mSize;
    }

    /**
     * Given an index in the range <code>0...size()-1</code>, returns
     * the key from the <code>index</code>th key-value mapping that this
     * SparseArray stores.
     *
     * <p>The keys corresponding to indices in ascending order are guaranteed to
     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
     * smallest key and <code>keyAt(size()-1)</code> will return the largest
     * key.</p>
     */
    public int keyAt(int index) {
        if (mGarbage) {
            gc();
        }

        return mKeys[index];
    }

    /**
     * Given an index in the range <code>0...size()-1</code>, returns
     * the value from the <code>index</code>th key-value mapping that this
     * SparseArray stores.
     *
     * <p>The values corresponding to indices in ascending order are guaranteed
     * to be associated with keys in ascending order, e.g.,
     * <code>valueAt(0)</code> will return the value associated with the
     * smallest key and <code>valueAt(size()-1)</code> will return the value
     * associated with the largest key.</p>
     */
    @SuppressWarnings("unchecked")
    public E valueAt(int index) {
        if (mGarbage) {
            gc();
        }

        return (E) mValues[index];
    }

    /**
     * Given an index in the range <code>0...size()-1</code>, sets a new
     * value for the <code>index</code>th key-value mapping that this
     * SparseArray stores.
     */
    public void setValueAt(int index, E value) {
        if (mGarbage) {
            gc();
        }

        mValues[index] = value;
    }

    /**
     * Returns the index for which {@link #keyAt} would return the
     * specified key, or a negative number if the specified
     * key is not mapped.
     */
    public int indexOfKey(int key) {
        if (mGarbage) {
            gc();
        }

        return ContainerHelpers.binarySearch(mKeys, mSize, key);
    }

    /**
     * Returns an index for which {@link #valueAt} would return the
     * specified key, or a negative number if no keys map to the
     * specified value.
     * <p>Beware that this is a linear search, unlike lookups by key,
     * and that multiple keys can map to the same value and this will
     * find only one of them.
     * <p>Note also that unlike most collections' {@code indexOf} methods,
     * this method compares values using {@code ==} rather than {@code equals}.
     */
    public int indexOfValue(E value) {
        if (mGarbage) {
            gc();
        }

        for (int i = 0; i < mSize; i++)
            if (mValues[i] == value)
                return i;

        return -1;
    }

    /**
     * Removes all key-value mappings from this SparseArray.
     */
    public void clear() {
        int n = mSize;
        Object[] values = mValues;

        for (int i = 0; i < n; i++) {
            //值空,利于jvm gc
            values[i] = null;
        }

        mSize = 0;
        mGarbage = false;
    }

    /**
     * Puts a key/value pair into the array, optimizing for the case where
     * the key is greater than all existing keys in the array.
     */
    public void append(int key, E value) {
        //若key小于等于已有的最大key,直接Put
        if (mSize != 0 && key <= mKeys[mSize - 1]) {
            put(key, value);
            return;
        }

        if (mGarbage && mSize >= mKeys.length) {
            gc();
        }

        //若key大于了现有的所有key,就不用走put的二分查找过程了,直接append
        //即comments说的优化
        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
        mValues = GrowingArrayUtils.append(mValues, mSize, value);
        mSize++;
    }

    /**
     * {@inheritDoc}
     *
     * <p>This implementation composes a string by iterating over its mappings. If
     * this map contains itself as a value, the string "(this Map)"
     * will appear in its place.
     */
    @Override
    public String toString() {
        if (size() <= 0) {
            return "{}";
        }

        StringBuilder buffer = new StringBuilder(mSize * 28);
        buffer.append('{');
        for (int i=0; i<mSize; i++) {
            if (i > 0) {
                buffer.append(", ");
            }
            int key = keyAt(i);
            buffer.append(key);
            buffer.append('=');
            Object value = valueAt(i);
            if (value != this) {
                buffer.append(value);
            } else {
                buffer.append("(this Map)");
            }
        }
        buffer.append('}');
        return buffer.toString();
    }
}

问题1,mGarbage标记结合DELETED,用以提升性能,体现在哪里?
    将可能的多次gc操作变为一次完成。比如用户调用了多次removeAt操作,若不用这种策略,则会每次remove都对应一次gc即用一次循环来'挤压'空间。
    采用这种flag优化后,多次remove仅多次设置了标志,在gc触发时,仅需要一次循环就可以将空间'挤压'好。

问题2,sparseArray的存储结构,两个数组加一个size有的特性?
    特性一,key数组是有序的,key在数组的index和vale在数组的index一样
    特性二,sparseArray每次gc过程,保证了他的有效值(size)区间内没有无效值,或者说'无缝隙',有效值是连续的
    特性三,如文档所说,当map Integers to Objects时,相对hashMap,sparseArray被设计成更加的内存高效,
           sparseArray避免了自动装箱机制和用额外的对象来存储每个映射。

问题3,ArrayUtils.newUnpaddedObjectArray(initialCapacity);有什么特性?
    最终调用的是VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen);
    参考 http://androidxref.com/5.0.0_r2/xref/libcore/libart/src/main/java/dalvik/system/VMRuntime.java#260

问题4, 二分查找的返回值含义,若值存在则返回该值的Index,否则返回和该值的插入点相关的一个值(-(insertion point) - 1)
   (the index of the search key, if it is contained in the list; otherwise, (-(insertion point) - 1).)

原文地址:https://github.com/cheyiliu/All-in-One/wiki/SparseArray
分享到:
评论

相关推荐

    android源码游戏源代码跳动的心

    《Android源码游戏源代码:跳动的心》 在Android平台上开发游戏,源代码的掌握是开发者提升技能、创新设计的关键。"跳动的心"是一款基于Android平台的游戏,其源代码为我们提供了深入理解Android游戏开发的宝贵资源...

    Android应用源码之小米便签源代码分享.zip

    在本压缩包“Android应用源码之小米便签源代码分享.zip”中,我们得到了小米公司官方便签应用的源代码。这对于Android开发者,尤其是那些对Android应用开发、UI设计、数据存储、网络通信以及性能优化感兴趣的开发者...

    android数独游戏源代码

    源代码可能使用二维数组或更复杂的数据结构(如`SparseArray`)来存储和操作数独的当前状态。 4. **算法实现**:游戏的核心算法包括数独的生成、解决和验证。生成算法可能使用回溯法,解决算法可能是递归或迭代的...

    Android应用源码之扫雷游戏源码.rar

    源码可能使用了缓存技术,如使用SparseArray存储已知的非雷格子信息,以减少内存消耗和查找时间。 7. **多线程**:考虑到用户可能会在游戏过程中进行其他操作,源码可能会涉及多线程处理,比如在后台线程生成雷区,...

    android 4.0 Gallery源码

    《Android 4.0 Gallery源码深度解析》 在Android操作系统的历史中,Android 4.0(冰淇淋三明治)是一个重要的里程碑,它引入了许多新特性和改进,其中包括对用户界面和应用程序的重大调整。Gallery应用作为系统内置...

    2048游戏的android源代码

    《深入剖析2048游戏Android源代码》 2048是一款广受欢迎的数字合成游戏,其简单易上手的玩法和极具挑战性的高分追求吸引了无数玩家。本资源包含的是2048游戏在Android平台上的源代码,通过分析这份源代码,我们可以...

    Android4.4原生Laucnher3源代码2014年6月

    《Android4.4原生Launcher3源代码解析》 Android操作系统是全球最受欢迎的智能手机平台之一,而Launcnher3则是其核心组件之一,负责用户界面的管理和应用程序启动。2014年6月发布的Android4.4原生Launcher3源代码,...

    Android应用源码之CBReader资讯阅读-IT计算机-毕业设计.zip

    CBReader是一款基于Android平台的资讯阅读应用源码,适合用于毕业设计学习,旨在帮助学生或开发者了解和掌握Android应用的开发流程和关键技术。这个项目涵盖了Android应用开发中的多个重要知识点,包括用户界面设计...

    Android源码——蘑菇街界面设计源码.zip

    【Android源码——蘑菇街界面设计源码】是一个专门针对Android平台的开源项目,它提供了蘑菇街应用的界面设计源代码。这个项目对于Android开发者,尤其是那些对UI/UX设计感兴趣的开发者来说,是一个宝贵的资源。通过...

    Android应用源码之携程、去哪儿日历源码.zip

    携程和去哪儿的日历组件需要兼容多种Android设备,源码中可能包含了针对不同API级别的兼容性代码,如`Support Library`或`AndroidX`的使用。 9. **测试与调试**: 为了保证日历功能的稳定性和正确性,源码中应...

    Android源码——小米便签源码.zip

    【Android源码——小米便签源码.zip】这个压缩包文件包含了小米公司开发的便签应用的源代码,是深入理解Android应用开发和小米产品设计思路的重要资源。在这个项目中,我们可以学习到许多关于Android应用架构、UI...

    Android应用源码之策略游戏-回到战国源码.zip

    10. **Android性能优化**:源码中可能包含了内存优化、绘制优化、启动速度优化等方面的实践,例如使用WeakReference防止内存泄漏,使用SparseArray替代HashMap以减少内存占用。 11. **测试与调试**:源码可能包含了...

    Android安卓应用源码-图片缓存&展示类源代码(25例).zip

    本资源“Android安卓应用源码-图片缓存&展示类源代码(25例)”提供了一系列用于处理图片缓存和展示的示例代码,这对于开发者深入理解和实践图片管理具有极大的帮助。以下是对这些知识点的详细说明: 1. **图片缓存...

    Android应用源码之listview快速滑动,修改默认的滑动条.zip

    本资源"Android应用源码之listview快速滑动,修改默认的滑动条.zip"提供了关于如何优化ListView性能以及自定义滚动条的实践案例,对于Android开发者来说,这是一份非常有价值的参考资料。 首先,ListView快速滑动...

    Android应用源码之测试反应能力源码.zip

    在Android应用开发中,测试反应能力的源码通常是指用于衡量用户与应用交互速度和响应时间的代码。这种测试对于优化用户体验和确保应用流畅运行至关重要。在这个名为"Android应用源码之测试反应能力源码.zip"的压缩包...

    Android应用源码之按字母索引滑动.zip

    在Android应用开发中,"Android应用源码之按字母索引滑动.zip"是一个非常有价值的资源,它展示了如何实现一个按字母索引的滑动效果,这种效果常见于联系人应用或者任何需要按照字母顺序快速查找条目的应用。这个源码...

    Android程序研发源码Android 蘑菇街界面设计源码.zip

    10. **性能优化**:蘑菇街作为一款成熟的应用,其源码中肯定包含了性能优化的实践,如使用`SparseArray`替代`HashMap`、避免内存泄漏、减少UI绘制次数等。 通过以上分析,我们不仅可以学习到Android界面设计的基础...

    深入分析Android系统中SparseArray的

    在Android开发中,SparseArray是一种优化过的数据结构,主要用于存储整数到对象的映射关系。与HashMap不同,SparseArray是专门为Android系统设计的,它在处理整数键时提供了更好的性能,特别是在内存管理和查找效率...

    Android4.1.1原版Contacts代码

    《深入解析Android 4.1.1原版Contacts代码》 Contacts是Android系统中的核心组件之一,它负责管理和展示设备上的联系人信息。在Android 4.1.1版本中,Contacts应用程序经过优化,提供了更加高效和用户友好的体验。...

    Android 团购信息源码.zip

    Android Studio提供了丰富的调试工具,如Logcat用于日志输出,单元测试和集成测试框架如JUnit和Espresso用于代码验证和功能测试。 总的来说,通过分析这个"Android团购信息源码",开发者不仅能掌握Android应用的...

Global site tag (gtag.js) - Google Analytics