深入分析Android系统中SparseArray的源码

前言
昨晚想在Android应用中增加一个int映射到String的字典表,使用HashMap实现的时候,Eclipse给出了一个警告,昨晚项目上线紧张,我直接给忽略了,今天看了一下具体的Eclipse提示如下:

  Use new SparseArray<String> (...) instead for better performance

这个警告的意思是使用SparseArray来替代,以获取更好的性能。

源码
因为SparseArray整体代码比较简单,先把源码展示出来,然后再分析为什么使用SparseArray会比使用HashMap有更好的性能。

public class SparseArray<E> implements Cloneable {
    private static final Object DELETED = new Object();
    private boolean mGarbage = false; 

    private int[] mKeys;
    private Object[] mValues;
    private int mSize; 

    /**
     * Creates a new SparseArray containing no mappings.
     */
    public SparseArray() {
      this(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) {
        mKeys = ContainerHelpers.EMPTY_INTS;
        mValues = ContainerHelpers.EMPTY_OBJECTS;
      } else {
        initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
        mKeys = new int[initialCapacity];
        mValues = new Object[initialCapacity];
      }
      mSize = 0;
    } 

    @Override
    @SuppressWarnings("unchecked")
    public SparseArray<E> clone() {
      SparseArray<E> clone = null;
      try {
        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) {
      int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 

      if (i < 0 || mValues[i] == DELETED) {
        return valueIfKeyNotFound;
      } else {
        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) {
        if (mValues[i] != DELETED) {
          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) {
      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);
      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; 

      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) {
      int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 

      if (i >= 0) {
        mValues[i] = value;
      } else {
        i = ~i; 

        if (i < mSize && mValues[i] == DELETED) {
          mKeys[i] = key;
          mValues[i] = value;
          return;
        } 

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

          // Search again because indices may have changed.
          i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
        } 

        if (mSize >= mKeys.length) {
          int n = ArrayUtils.idealIntArraySize(mSize + 1); 

          int[] nkeys = new int[n];
          Object[] nvalues = new Object[n]; 

          // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
          System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
          System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 

          mKeys = nkeys;
          mValues = nvalues;
        } 

        if (mSize - i != 0) {
          // Log.e("SparseArray", "move " + (mSize - i));
          System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
          System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
        } 

        mKeys[i] = key;
        mValues[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++) {
        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) {
      if (mSize != 0 && key <= mKeys[mSize - 1]) {
        put(key, value);
        return;
      } 

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

      int pos = mSize;
      if (pos >= mKeys.length) {
        int n = ArrayUtils.idealIntArraySize(pos + 1); 

        int[] nkeys = new int[n];
        Object[] nvalues = new Object[n]; 

        // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
        System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
        System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 

        mKeys = nkeys;
        mValues = nvalues;
      } 

      mKeys[pos] = key;
      mValues[pos] = value;
      mSize = pos + 1;
    } 

    /**
     * {@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();
    }
  }

首先,看一下SparseArray的构造函数:

 /**
   * Creates a new SparseArray containing no mappings.
   */
  public SparseArray() {
    this(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) {
      mKeys = ContainerHelpers.EMPTY_INTS;
      mValues = ContainerHelpers.EMPTY_OBJECTS;
    } else {
      initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
      mKeys = new int[initialCapacity];
      mValues = new Object[initialCapacity];
    }
    mSize = 0;
  }

从构造方法可以看出,这里也是预先设置了容器的大小,默认大小为10。

再来看一下添加数据操作:

  /**
   * 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) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 

    if (i >= 0) {
      mValues[i] = value;
    } else {
      i = ~i; 

      if (i < mSize && mValues[i] == DELETED) {
        mKeys[i] = key;
        mValues[i] = value;
        return;
      } 

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

        // Search again because indices may have changed.
        i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
      } 

      if (mSize >= mKeys.length) {
        int n = ArrayUtils.idealIntArraySize(mSize + 1); 

        int[] nkeys = new int[n];
        Object[] nvalues = new Object[n]; 

        // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
        System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
        System.arraycopy(mValues, 0, nvalues, 0, mValues.length); 

        mKeys = nkeys;
        mValues = nvalues;
      } 

      if (mSize - i != 0) {
        // Log.e("SparseArray", "move " + (mSize - i));
        System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
        System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
      } 

      mKeys[i] = key;
      mValues[i] = value;
      mSize++;
    }
  }

再看查数据的方法:

  /**
   * 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) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key); 

    if (i < 0 || mValues[i] == DELETED) {
      return valueIfKeyNotFound;
    } else {
      return (E) mValues[i];
    }
  }

可以看到,在put数据和get数据的过程中,都统一调用了一个二分查找算法,其实这也就是SparseArray能够提升效率的核心。

 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
  }

个人认为(lo + hi) >>> 1的方法有些怪异,直接用 lo + (hi - lo) / 2更好一些。

(0)

相关推荐

  • Android中ArrayList和数组相互转换

    List-–>数组 在大家开发中应该经常碰到List与数组类型之间的相互转换,举一个简单的例子: package test.test1; import java.util.ArrayList; import java.util.List; public class Test { /** * @param args */ public static void main(String[] args) { List list=new ArrayList(); list.add("王利虎"

  • Android自定义Spinner下拉列表(使用ArrayAdapter和自定义Adapter实现)

    今天学习了Spinner组件的使用,非常好用的一款组件,相当于从下拉列表中选择项目,今天收获颇多,下面给大家演示一下Spinner的使用(分别使用ArrayAdapter和自定义Adapter实现),具体内容如下. (一):使用ArrayAdapter进行适配数据: ①:首先定义一个布局文件: <span style="font-size:16px;"><?xml version="1.0" encoding="utf-8"?&

  • Android ArrayMap源代码分析

    分析源码之前先来介绍一下ArrayMap的存储结构,ArrayMap数据的存储不同于HashMap和SparseArray. Java提供了HashMap,但是HashMap对于手机端而言,对空间的利用太大,所以Android提供了SparseArray和ArrayMap.二者都是基于二分查找,所以数据量大的时候,最坏效率会比HashMap慢很多.因此建议数量在千以内比较合适.  一.SparseArray SparseArray对应的key只能是int类型,它不会对key进行装箱操作.它使用了

  • 详解Android自定义控件属性TypedArray以及attrs

    最近在研究android自定义控件属性,学到了TypedArray以及attrs.大家也可以结合<理解Android中的自定义属性>这篇文章进行学习,后续一篇还有应用. 1.attrs文件编写 <?xml version="1.0" encoding="utf-8"?> <resources> <attr name="titleText" format="string" /> &

  • Android string-array数据源简单使用

    在Android中,用string-array是一种简单的提取XML资源文件数据的方法. 例子如下: 把相应的数据放到values文件夹的arrays.xml文件里 <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="city"> <item>厦门市</item> <item>福州市&l

  • Android中SparseArray性能优化的使用方法

    之前一篇文章研究完横向二级菜单,发现其中使用了SparseArray去替换HashMap的使用.于是乎自己查了一些相关资料,自己同时对性能进行了一些测试.首先先说一下SparseArray的原理. SparseArray(稀疏数组).他是Android内部特有的api,标准的jdk是没有这个类的.在Android内部用来替代HashMap<Integer,E>这种形式,使用SparseArray更加节省内存空间的使用,SparseArray也是以key和value对数据进行保存的.使用的时候只

  • 解析Android中string-array数据源的简单使用

    在Android中,用string-array是一种简单的提取XML资源文件数据的方法.例子如下:把相应的数据放到values文件夹的arrays.xml文件里 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?> <resources>     <string-array name="city">         <item>厦门市</item&

  • Android中GridView和ArrayAdapter用法实例分析

    本文实例分析了Android中GridView和ArrayAdapter用法.分享给大家供大家参考,具体如下: GridView是一个表格化的二维排版的View,当GridView的文字放不下时会出现scrolling的效果,GridView中的元素命名为Item,要将Item放入GridView,需要ArrayAdapter对象. 例子如下: import android.app.Activity; import android.os.Bundle; import android.view.V

  • 深入分析Android系统中SparseArray的源码

    前言 昨晚想在Android应用中增加一个int映射到String的字典表,使用HashMap实现的时候,Eclipse给出了一个警告,昨晚项目上线紧张,我直接给忽略了,今天看了一下具体的Eclipse提示如下: Use new SparseArray<String> (...) instead for better performance 这个警告的意思是使用SparseArray来替代,以获取更好的性能. 源码 因为SparseArray整体代码比较简单,先把源码展示出来,然后再分析为什么

  • Linux CentOS6.6系统中安装mysql源码包的方法

    这里以CentOS6.6系统中安装MySQL的源码包,进行讲解. 1. mysql源码包的下载 mysql安装包的官方下载地址为:http://dev.mysql.com/downloads/mysql/5.6.html#downloads 打开该下载地址后,在 "Select Version:"处,选择要下载的mysql的版本,我选择的是5.6.34:在"Select Platform:"处,选择适用的操作系统类型,由于是下载源码包,故这里我们要选择Source

  • 深入分析Android加载so文件源码

    Android系统中使用ndk进行编程,有很多的好处(Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成:代码的保护:由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大:可以方便地使用C/C++开源库:便于移植,用C/C++写的库可以方便在其他平台上再次使用:提供程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能). 要使用ndk进行编程,在Java层就必须要对so进行加载.Java层加载so的函数有两个: Sys

  • Android开发中线程池源码解析

    线程池(英语:thread pool):一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的代价.线程池不仅能够保证内核的充分利用,还能防止过分调度.可用线程数量应该取决于可用的并发处理器.处理器内核.内存.网络sockets等的数量. 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销.----摘自维基百科 我们在Android或者Java开发中

  • 详解Android中的Toast源码

    Toast源码实现 Toast入口     我们在应用中使用Toast提示的时候,一般都是一行简单的代码调用,如下所示: [java] view plaincopyprint?在CODE上查看代码片派生到我的代码片 Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); makeText就是Toast的入口,我们从makeText的源码来深入理解Toast的实现.源码如下(frameworks/base/core/java/android

  • Android 自定义相机及分析源码

    Android 自定义相机及分析源码 使用Android 系统相机的方法: 要想让应用有相机的action,咱们就必须在清单文件中做一些声明,好让系统知道,如下 <intent-filter> <action android:name="android.intent.action.IMAGE_CAPTURE" /> <category android:name="android.intent.category.DEFAULT" />

  • 一文理解Android系统中强指针的实现

    强指针和弱指针基础 android中的智能指针包括:轻量级指针.强指针.弱指针. 强指针:它主要是通过强引用计数来进行维护对象的生命周期. 弱指针:它主要是通过弱引用计数来进行维护所指向对象的生命周期. 如果在一个类中使用了强指针或者弱指针的技术,那么这个类就必须从RefBase这个类进行做继承,因为强指针和弱指针是通过RefBase这个类来提供实现的引用计数器. 强指针和弱指针关系相对于轻量级指针来说更加亲密,因此他们一般是相互配合使用的. 强指针原理分析 以下针对源码的分析都是来源于andr

  • 自定义Android六边形进度条(附源码)

    本文实例讲述了Android自定义圆形进度条,分享给大家供大家参考.具体如下: 大家也可以参考这两篇文章进行学习: <自定义Android圆形进度条(附源码)>   <Android带进度的圆形进度条> 运行效果截图如下: 主要代码: package com.sxc.hexagonprogress; import java.util.Random; import android.content.Context; import android.content.res.ColorSta

  • Android Handler,Message,MessageQueue,Loper源码解析详解

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文< Android中Handler的使用>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功能.但

  • Android线程间通信Handler源码详解

    目录 前言 01. 用法 02.源码 03.结语 前言 在[Android]线程间通信 - Handler之使用篇主要讲了 Handler 的创建,发送消息,处理消息 三个步骤.那么接下来,我们也按照这三个步骤,从源码中去探析一下它们具体是如何实现的.本篇是关于创建源码的分析. 01. 用法 先回顾一下,在主线程和非主线程是如何创建 Handler 的. //主线程 private val mHandler: Handler = object : Handler(Looper.getMainLo

随机推荐