SparseArrayとHashMap


177

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

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

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

回答:


235

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

利点は次のとおりです。

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

欠点:

  • 一般的に低速で、大規模なコレクションには表示されません
  • 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要素のSparseIntArrayvsの例を次に示しHashMap<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バイト

出典:スライド90のRomain GuyによるAndroid Memories

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

java.lang.instrumentパッケージが持つオブジェクトのサイズを確認するなどの高度な操作のためのいくつかの有用なメソッドが含まれていますgetObjectSize(Object objectToSize)

追加情報は、オラクルの公式ドキュメントから入手できます

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


15
「12 + 3 * 4」と「20 + 1000 * 4」がどこから来たかを誰かが私に案内できますか?
MarianPaździoch15年

5
@MarianPaździoch、彼はプレゼンテーションが示しました(Speakerdeck.com/romainguy/android-memories)クラスが12バイト+ 4バイトの3つの変数を占め、配列(参照)が20バイトを占める(dlmalloc-4、オブジェクトオーバーヘッド-8、width&padding) -8)。
CoolMind 2016年

1
記録として、SparseArrayのもう1つの重要な欠点は、Androidオブジェクトとしてユニットテスト用にモックする必要があることです。可能な場合は、Java独自のオブジェクトを使用してテストを簡略化します。
David G

@DavidG unmockプラグインを使用して、Androidの依存関係をモックすることができます。
ブリザード2017

1
Androidを使用していない場合でも、クラスをプロジェクトにコピーするのは難しくありません。他の3つのクラスにのみ依存しています。APLライセンスとは、使用しているライセンスが何であれ、それを実行しても問題ないことを意味します。
Yann TM

35

使い方の例が欲しいだけで来ましたSparseArray。これはそのための補足的な答えです。

SparseArrayを作成する

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

A 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

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

アイテムを反復する

keyAtとキーを区別する個別のインデックスを維持するvalueAtため、コレクションをループするために、いくつかのインデックスを使用できます。SparseArrayint

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

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


18

しかし、Androidプロジェクトで整数キーを含むHashMapを使用しようとすると、intelliJは代わりにSparseArrayを使用するように指示します。

スパース配列のこのドキュメントからの警告のみです:

これは、HashMapを使用して整数をオブジェクトにマップするよりもメモリ効率を高めることを目的としています。

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


5
メモリの節約に関するポイントは明らかに有効ですが、AndroidがSparseArray <T>でMap <Integer、T>を実装できなかったので、メモリ効率の高いMap実装を実現できなかった理由は理解できません。
Paul Boddington、2014

3
@PaulBoddington SparseArrayは、キー整数がオートボックスになるのを防ぐことも覚えています。マップではなく、プリミティブ整数をInteger
オートボックス化

また、trueですが、putメソッドに署名put(int a、T t)を含めてputメソッドをオーバーロードした場合でも、キーが自動ボックス化されなくても、キーと値のペアをマップに配置できます。私は、Collections Frameworkが非常に強力(Javaを使用する最大の理由の1つ)であり、それを利用しないのは狂気だと思うだけです。
Paul Boddington、2014

6
@PaulBoddingtonコレクションはプリミティブではないオブジェクトに基づいているため、コレクションAPI内では機能しません
Rod_Algonquin

10

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

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

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

SparseArrayの主な関心は、オブジェクトではなくプリミティブをキーとして使用することでメモリを節約することです。


10

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

Isaac Taylorは、SparseArraysとHashmapsのパフォーマンスを比較しました。彼はそれを述べています

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

そして

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

Edgblogを比較すると、SparseArrayは、小さいキー(intとInteger)と、

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

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


4

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

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

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

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

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

SparseArrayのソースコードはかなりシンプルで理解しやすいので、他のプラットフォームに(簡単なCOPY&Pasteを介して)移動するのにほとんど労力を要しません。

mapはequals()とhashCode()をオーバーライドしますが、SparseArrayはオーバーライドしません

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

もう一つの重要な側面は、SparseArrayそれが唯一の間、すべての要素を格納する配列使用していることであるHashMap用途はEntry、これSparseArrayよりも大幅に少ないメモリのコストをHashMap参照してくださいこれを


1

コンパイラが警告を出すのは残念です。HashMapはアイテムを保存するためにかなり使いすぎていると思います。

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.