SparseArrayとHashMap
まず、公式の紹介を見てみましょう.
/**
* 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.
*
* 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%.
*
* 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.
*
* It is possible to iterate over the items in this container using
* {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
* keyAt(int)
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 valueAt(int)
.
SparseArrayはHashmapメモリよりも使用効率が高く、主にkeyの自動箱詰めを許可する一方で、追加のEntryに依存する必要はありません.このコンテナは、キー値ペアが配列のデータ構造を維持し、keyを二分法で検索するので、大量のデータを格納するのに適していないことに注意してください.SparseArrayは、処理するたびに二分法の検索が必要になるため、追加削除の効率が従来のHashmapよりも遅い.データ量が100未満の場合、このパフォーマンスの差は明らかではなく、50%未満になります.パフォーマンスを向上させるために、SparseArrayはkeyを削除するときに最適化しました.彼はすぐにkeyを配列から削除するのではなく、削除状態としてマークします.その後、それを多重化するか、後で専門的に収集された容器に移動して、配列を拡張する必要があるときに使用することができます.
くだらないことは言わないで、まずデータ構造を見てみましょう.
private int[] mKeys;
private Object[] mValues;
注記で述べたように、SparseArrayは主に2つの配列でkeyとvalueを格納し、keyはintのみである.
データ構造を見終わったので、追加削除を見てみましょう.まず増加を見ると、2つの方法があります.1つはappendで、もう1つはputです.まずputの方法を見てみましょう.
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);
}
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}
まず二分検索を行い,key配列に対応するkeyが存在するか否かを判断し,ヒットif条件が存在する場合value値を直接対応インデックス位置にコピーすることが分かる.存在しない場合は、返されるインデックスの位置を二分検索して、対応する位置のvalueがdeletedとマークされているかどうかを判断し、そうであればkeyとvalueをそれぞれ対応するインデックス位置に付与する.返されるインデックスの位置が現在のsizeよりも大きい場合は、ゴミ回収があるかどうかを判断し、ある場合はgcを行い、二分法で対応するインデックスを再検索し、最後に対応するkeyとvalueを対応するインデックスの位置に挿入します.
再看append方法:
public void append(int key, E value) {
if (mSize != 0 && key <= mKeys[mSize - 1]) {
put(key, value);
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
}
mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
mValues = GrowingArrayUtils.append(mValues, mSize, value);
mSize++;
}
size>0でkeyが最後の要素のkeyより小さい場合、putメソッドが呼び出されることがわかる.そうでなければgarbageが以前に存在しsizeが配列の長さ以上である場合はgcを行い,対応するkey,valueを配列の後ろに追加する.ではgc法は何をしましたか?
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);
}
すでにDELETEと表記されている対応するインデックス位置のkey,valueを再割り当て,すなわち配列を圧縮していることがわかる.
追加を見終わったら、削除方法を見続けます.削除シリーズの方法は全部で6つあります.
public void remove(int key) {
delete(key);
}
public void removeAt(int index) {
if (mValues[index] != DELETED) {
mValues[index] = DELETED;
mGarbage = true;
}
}
public void removeAtRange(int index, int size) {
final int end = Math.min(mSize, index + size);
for (int i = index; i < end; i++) {
removeAt(i);
}
}
public E removeReturnOld(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
final E old = (E) mValues[i];
mValues[i] = DELETED;
mGarbage = true;
return old;
}
}
return null;
}
public void delete(int key) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
if (mValues[i] != DELETED) {
mValues[i] = DELETED;
mGarbage = true;
}
}
}
public void clear() {
int n = mSize;
Object[] values = mValues;
for (int i = 0; i < n; i++) {
values[i] = null;
}
mSize = 0;
mGarbage = false;
}
は,実際にはほとんどが同じであり,主に対応インデックスまたはkey対応valueの値をDeleteとする.clearメソッドは、すべてのvalueの値を空にし、sizeを0に設定することです.
再参照修正:setValue Atメソッド
public void setValueAt(int index, E value) {
if (mGarbage) {
gc();
}
mValues[index] = value;
}
は簡単で、gcが必要かどうかを判断し、valuesに対応するインデックスに値を割り当てることです.
最後にクエリー方法です. クエリー方法が多いです.
public E get(int key) {
return get(key, null);
}
@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];
}
}
ジルコニウムの2つの方法は主にkeyによってvalueを取得する.
*/
public int indexOfKey(int key) {
if (mGarbage) {
gc();
}
return ContainerHelpers.binarySearch(mKeys, mSize, key);
}
public int indexOfValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++) {
if (mValues[i] == value) {
return i;
}
}
return -1;
}
public int indexOfValueByValue(E value) {
if (mGarbage) {
gc();
}
for (int i = 0; i < mSize; i++) {
if (value == null) {
if (mValues[i] == null) {
return i;
}
} else {
if (value.equals(mValues[i])) {
return i;
}
}
}
return -1;
}
第1の方法はkey対応インデックスを取得することであり,後の2つの方法は対応valueのインデックスを取得することであり,両者の違いは1つはequalsで判断し,もう1つは==で判断することである.
には最後の2つの方法があります:keyAtとvalueAt
public int keyAt(int index) {
if (mGarbage) {
gc();
}
return mKeys[index];
}
public E valueAt(int index) {
if (mGarbage) {
gc();
}
return (E) mValues[index];
}
は、インデックスに対応するkeyとvalueをそれぞれ返します.
全体的に見ると、SparseArrayはHashMapのデータ構造よりずっと簡単です.hashmapのnodeノードがなく、反復器も必要ありません.二分法検索は検索に有利です.欠点はkeyがintタイプのみをサポートしていることです.明らかに私たちの実際の開発では足りないので、ArrayMapもあります.ArrayMapのソースコードは分析しません.実際には、その使い方やデータの格納はSparseArrayと同様に、2つの配列で行われ、二分検索にも基づいています.異なる点は、ArrayMapのkeyは任意のタイプであってもよいことである.