web-dev-qa-db-ja.com

SparseArrayとHashMap

整数キーを持つHashMapsがSparseArraysよりもはるかに優れている理由はいくつか考えられます。

  1. SparseArrayのAndroidドキュメントには、「従来のHashMapより一般に遅い」と書かれています。
  2. HashMapsではなくSparseArraysを使用してコードを記述する場合、コードはMapの他の実装で機能し、Maps用に設計されたすべてのJava AP​​Iを使用できます。
  3. HashMapsではなくSparseArraysを使用してコードを記述する場合、コードはAndroid以外のプロジェクトで機能します。
  4. マップはequals()およびhashCode()をオーバーライドしますが、SparseArrayはオーバーライドしません。

しかし、Androidプロジェクトで整数キーでHashMapを使用しようとするたびに、IntelliJは代わりにSparseArrayを使用するように指示します。これを理解するのは本当に難しいと思います。 SparseArraysを使用する説得力のある理由を誰か知っていますか?

151
Paul Boddington

SparseArray は、キーがプリミティブ型の場合に HashMap を置き換えるために使用できます。すべてのキー/値タイプが公開されているわけではありませんが、さまざまなキー/値タイプ用のバリアントがいくつかあります。

利点は次のとおりです。

  • 割り当てなし
  • ボクシングなし

欠点:

  • 一般に低速で、大規模なコレクションには表示されません
  • Android以外のプロジェクトでは機能しません

HashMapは、次のものに置き換えることができます。

SparseArray          <Integer, Object>
SparseBooleanArray   <Integer, Boolean>
SparseIntArray       <Integer, Integer>
SparseLongArray      <Integer, Long>
LongSparseArray      <Long, Object>
LongSparseLongArray  <Long, Long>   //this is not a public class                                 
                                    //but can be copied from  Android source code 

メモリに関しては、1000個の要素に対するSparseIntArrayHashMap<Integer, Integer>の例を次に示します。

SparseIntArray

class SparseIntArray {
    int[] keys;
    int[] values;
    int size;
}

クラス= 12 + 3 * 4 = 24バイト
配列= 20 + 1000 * 4 = 4024バイト
合計= 8,072バイト

HashMap

class HashMap<K, V> {
    Entry<K, V>[] table;
    Entry<K, V> forNull;
    int size;
    int modCount;
    int threshold;
    Set<K> keys
    Set<Entry<K, V>> entries;
    Collection<V> values;
}

クラス= 12 + 8 * 4 = 48バイト
エントリ= 32 + 16 + 16 = 64バイト
配列= 20 + 1000 * 64 = 64024バイト
合計= 64,136バイト

出典: Romain GuyによるAndroid Memories スライド90から。

上記の数値は、JVMによってヒープに割り当てられたメモリの量(バイト単位)です。それらは、使用される特定のJVMによって異なる場合があります。

Java.lang.instrumentパッケージには、オブジェクトのサイズをgetObjectSize(Object objectToSize)でチェックするなどの高度な操作に役立つメソッドがいくつか含まれています。

追加情報は、公式の Oracleドキュメント から入手できます。

クラス= 12バイト+(nインスタンス変数)* 4バイト
配列= 20バイト+(n要素)*(要素サイズ)
エントリ= 32バイト+(最初の要素サイズ)+(2番目の要素サイズ)

209
Sarpe

SparseArray の使用方法の例を求めてここに来ました。これはそのための補足的な答えです。

SparseArrayを作成する

SparseArray<String> sparseArray = new SparseArray<>();

SparseArrayは整数をいくつかのObjectにマップするため、上記の例のStringを他のObjectに置き換えることができます。整数を整数にマッピングする場合は、 SparseIntArray を使用します。

アイテムを追加または更新する

put (または append )を使用して、配列に要素を追加します。

sparseArray.put(10, "horse");
sparseArray.put(3, "cow");
sparseArray.put(1, "camel");
sparseArray.put(99, "sheep");
sparseArray.put(30, "goat");
sparseArray.put(17, "pig");

intキーは順番に並んでいる必要はありません。これは、特定のintキーの値を変更するためにも使用できます。

アイテムを削除

remove (または delete )を使用して、配列から要素を削除します。

sparseArray.remove(17); // "pig" removed

intパラメーターは整数キーです。

Intキーのルックアップ値

get を使用して、整数キーの値を取得します。

String someAnimal = sparseArray.get(99);  // "sheep"
String anotherAnimal = sparseArray.get(200); // null

欠落しているキーに対してnullを取得しないようにする場合は、 get(int key, E valueIfKeyNotFound) を使用できます。

アイテムを反復処理する

keyAtvalueAtキーとは別のインデックスを保持するため、 SparseArray および int を使用してコレクションをループできます。

int size = sparseArray.size();
for (int i = 0; i < size; i++) {

    int key = sparseArray.keyAt(i);
    String value = sparseArray.valueAt(i);

    Log.i("TAG", "key: " + key + " value: " + value);
}

// key: 1 value: camel
// key: 3 value: cow
// key: 10 value: horse
// key: 30 value: goat
// key: 99 value: sheep

キーは、追加された順序ではなく、昇順で並べられていることに注意してください。

25
Suragch

しかし、Androidプロジェクトで整数キーでHashMapを使用しようとするたびに、intelliJは代わりにSparseArrayを使用する必要があることを通知します。

これは単なる警告です ドキュメント スパース配列:

HashMapを使用して整数をオブジェクトにマッピングするよりもメモリ効率が高いことを目的としています

SparseArrayは、通常のHashMapを使用するよりもメモリ効率がよくなるように作成されます。つまり、HashMapのように配列内に複数のギャップを許可しません。デバイスへのメモリ割り当てを気にしない場合は、従来のHashMapを使用できますので、心配する必要はありません。

17
Rod_Algonquin

いくつかのグーグルの後、私はすでに投稿されたanwersにいくつかの情報を追加しようとします:

Isaac Taylor SparseArraysとHashmapsのパフォーマンスを比較しました。彼は次のように述べています

hashmapとSparseArrayは、1,000未満のデータ構造サイズについて非常に似ています

そして

サイズが10,000マークまで増加した場合[...]ハッシュマップはオブジェクトを追加する際のパフォーマンスが向上し、SparseArrayはオブジェクトを取得する際のパフォーマンスが向上します。 [...] 100,000のサイズで[...]ハッシュマップのパフォーマンスが急速に低下します

Edgblog の比較では、SparseArrayが必要とするメモリがHashMapよりもはるかに少ないことを示しています。これは、キーが小さいため(int対Integer)、

hashMap.Entryインスタンスは、キー、値、および次のエントリの参照を追跡する必要があります。さらに、エントリのハッシュをintとして保存する必要もあります。

結論として、マップに大量のデータを保存する場合、違いが重要になると思います。それ以外の場合は、警告を無視してください。

10
Sebastian

Javaのスパース配列は、キーを値にマッピングするデータ構造です。マップと同じアイデアですが、実装が異なります。

  1. Mapは内部的にリストの配列として表され、これらのリストの各要素はキーと値のペアです。キーと値の両方がオブジェクトインスタンスです。

  2. スパース配列は、(プリミティブ)キーの配列と(オブジェクト)値の配列という2つの配列で構成されています。これらの配列インデックスにはギャップが存在する可能性があるため、「疎」配列という用語が使用されます。

SparseArrayの主な目的は、オブジェクトの代わりにプリミティブをキーとして使用してメモリを節約することです。

9
Ganesh Katikar

SparseArrayのAndroidドキュメントには、「従来のHashMapよりも一般に遅い」と書かれています。

はい、そうです。ただし、アイテムが10個または20個しかない場合、パフォーマンスの違いはわずかです。

SparseArraysではなくHashMapsを使用してコードを記述する場合、コードはMapの他の実装で機能し、Maps用に設計されたJava AP​​Iをすべて使用できます。

ほとんどの場合、HashMapはキーに関連付けられた値を検索するためにのみ使用しますが、SparseArrayはこれが得意です。

SparseArraysではなくHashMapsを使用してコードを記述する場合、コードはAndroid以外のプロジェクトで機能します。

SparseArrayのソースコードは非常にシンプルで理解しやすいため、他のプラットフォームに移動するのに少しの労力しかかかりません(簡単なCOPY&Pasteを使用)。

マップはequals()およびhashCode()をオーバーライドしますが、SparseArrayはオーバーライドしません

私が言えることは、(ほとんどの開発者にとって)誰が気にするか?

SparseArrayのもう1つの重要な側面は、HashMapEntryを使用するのに対し、配列を使用してすべての要素を格納することです。したがって、SparseArrayHashMapthis を参照

2
suitianshi

コンパイラが警告を発行するのは残念です。 HashMapはアイテムの保存に過度に使用されていると思います。

SparseArraysには場所があります。バイナリ検索アルゴリズムを使用して配列内の値を見つける場合、何をしているのかを考慮する必要があります。バイナリ検索はO(log n)であり、ハッシュルックアップはO(1)です。これは、必ずしも特定のデータセットに対してバイナリ検索が遅いという意味ではありません。ただし、エントリの数が増えると、ハッシュテーブルの能力が引き継ぎます。したがって、エントリの数が少ない場合のコメントは、HashMapを使用するよりも優れている可能性があります。

HashMapはハッシュと同程度に優れているだけでなく、負荷係数の影響も受ける可能性があります(後のバージョンでは、負荷係数を無視するため、最適化が改善されると思います)。また、ハッシュが適切であることを確認するためにセカンダリハッシュを追加しました。また、SparseArrayが比較的少ないエントリ(<100)で本当にうまく機能する理由もあります。

ハッシュテーブルが必要で、プリミティブ整数(オートボクシングなし)などのメモリ使用量を増やしたい場合は、troveを試してみることをお勧めします。 ( http://trove.starlight-systems.com -LGPLライセンス)。 (彼らのライブラリのように、Troveとの提携はありません)

簡素化されたマルチデックスビルディングにより、必要なもののためにtroveを再パッケージ化する必要さえありません。 (troveには多くのクラスがあります)

1
Bruce