Java container analytic series (16) of memory optimization android SparseArray

HashMap disadvantages:

  1. Autoboxing performance penalty;
  2. Zipper method to resolve the conflict hash, hash conflict if more needs to traverse the list, resulting in decreased performance in Java 8, if the chain length> 8, will use the red-black tree instead of linked lists;
  3. Because of loadFactor, leading to (1 - loadFactor) * capacity would be a waste of space, capacity greater, more wasted space;
  4. The need to recalculate the hash, waste performance expansion;
  5. Each stored value by a Node, Node addition to saving the data itself, but also requires additional redundant data, such as a hash value, a pointer to the next node, and the like;

SparseArrayspecialty:

  1. keyOnly for the inttype, to avoid autoboxing;
  2. Use a binary search key, when you query the specified key need for binary search, the time complexity of the query is O (logN), add and delete and as is; it is not suitable for large amounts of data, the amount of data of 1,000 or less (up to hundreds of items ), the performance difference between it and the HashMap (difference) is less than 50%;
  3. No redundant data;
  4. To improve the performance, at the time of deleting data is not immediately adjust the compression array (packed array requires moving elements of the array), but the value tag is removed as DELETED position, then this position may be reused in the primary compression, clear out all DELETED;
  5. Can keyAt(int)and valueAt(int)to traverse SparseArrayin keyand value;
  6. Just copy the expansion array value, it does not require the hash calculation;

Overall, the relative HashMap, SparseArrayis 以时间换空间;

SparseArray source code analysis:

data structure:

SparseArrayUsing two arrays to store the key and value, respectively:

private int[] mKeys;// 存储key值的数组
private Object[] mValues;// 存储value值的数组  mKeys[i]---mValues[i]为一个键值对
private int mSize;// 存储键值对个数,确切地说,是当前存储的key值个数,key-value键值对均存储在数组中下标为 [0,mSize-1]之间的位置

private static final Object DELETED = new Object();// DELETED标记
private boolean mGarbage = false;// 用于判断是否需要进行压缩

Construction method:

public SparseArray() {
   this(10);// 默认初始容量:10
}

public SparseArray(int initialCapacity) {
   if (initialCapacity == 0) {
      mKeys = EmptyArray.INT;
      mValues = EmptyArray.OBJECT;
   } else {
      mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
      mKeys = new int[mValues.length];// key value数组大小一致
   }
   mSize = 0;
}

Add key-value:

public void put(int key, E value) {
   int i = ContainerHelpers.binarySearch(mKeys, mSize, key);// 通过二分法查找key在mKeys中的索引
   if (i >= 0) {
      mValues[i] = value;// 覆盖已有值
   } else {
     // 如果没有找到,那么i值为 ~(0) 或 ~(mSize - 1),这里再取反,值就成了 0 或  (mSize - 1),参考源码 ContainerHelpers.binarySearch(mKeys, mSize, key)
      i = ~i;
      if (i < mSize && mValues[i] == DELETED) {// 刚好是一个被删除的值直接替换
         mKeys[i] = key;
         mValues[i] = value;
         return;
      }
      // 需要通过gc(),腾出新的空间
      if (mGarbage && mSize >= mKeys.length) {
         gc();
         // gc() 后索引变了,需要重新找个位置,重新二分查找,确定key/value在数组中的索引
         i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
      }
      mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);// 插入新key
      mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);// 插入新值
      mSize++;
   }
}

The partial reference as follows:

// # ContainerHelpers.binarySearch(int[], int, int)
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
}

// # SparseArray.gc()
// 对 mKeys 和 mValues 进行重新排列,将mValues中标记为DELETED的值清除掉
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;// 更新mSize
}

// # GrowingArrayUtils.insert(int[], int, int, int);
public static int growSize(int currentSize) {
    return currentSize <= 4 ? 8 : currentSize * 2;// 扩容为之前的两倍
}

public static int[] insert(int[] array, int currentSize, int index, int element) {
    // 不需要扩容
    if (currentSize + 1 <= array.length) {
        // 将之后的所有元素向后移一位 这里的时间复杂度为O(N)
        System.arraycopy(array, index, array, index + 1, currentSize - index);
        array[index] = element;// 插入元素到指定位置
        return array;
    }
    // 扩容
    int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
    System.arraycopy(array, 0, newArray, 0, index);
    newArray[index] = element;// 插入元素到指定位置
    System.arraycopy(array, index, newArray, index + 1, array.length - index);
    return newArray;
}

Inquire:

public E get(int key) {
   return get(key, null);
}

// 获得指定key的值,如果没有,返回 valueIfKeyNotFound
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];
   }
}

delete:

public void remove(int key) {
   delete(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;// 需要垃圾回收,必要时检查该变量,并对 mKeys 和 mValues 进行重新排序
      }
   }
   // 这里并没有将 mSize - 1,因为如果将 mSize - 1,需要重新对 mKeys 和 mValues 进行重新移动
}

In summary, SparseArray time complexity:

  1. By: O (N); because of the need move the elements;
  2. Charles: O (logN) using a binary search;.
  3. Delete: O (logN); then find you want to delete the key, its value will be marked as DELETED;
  4. Change: O (logN); the same deletion, but the value to the new value of it;

If the key is long type, you can use LongParseArray, which is realized ParseArray same, except for the mKeys long []

If the key is int type:

  1. value of type int, may be used SparseIntArray
  2. value for the long type, may be used SparseLongArray
  3. value of the boolean type, may be used SparseBooleanArray

SparseArray on Android with more relevant, reference video:

SparseArray Family Ties

Guess you like

Origin www.cnblogs.com/jamesvoid/p/10937923.html