SparseArray vs HashMap

177

Saya dapat memikirkan beberapa alasan mengapa HashMaps dengan kunci integer jauh lebih baik daripada SparseArrays:

  1. Dokumentasi Android untuk SparseArraymengatakan "Ini umumnya lebih lambat daripada tradisional HashMap".
  2. Jika Anda menulis kode menggunakan HashMaps daripada SparseArraykode Anda akan bekerja dengan implementasi lain dari Peta dan Anda akan dapat menggunakan semua Java API yang dirancang untuk Maps.
  3. Jika Anda menulis kode menggunakan HashMaps daripada SparseArraykode Anda akan bekerja di proyek non-android.
  4. Peta menimpa equals()dan hashCode()sementara SparseArraytidak.

Namun setiap kali saya mencoba menggunakan HashMapkunci integer dalam proyek Android, IntelliJ mengatakan bahwa saya harus menggunakan a SparseArray. Saya merasa ini sangat sulit untuk dipahami. Adakah yang tahu alasan kuat untuk menggunakan SparseArrays?

Paul Boddington
sumber

Jawaban:

235

SparseArraydapat digunakan untuk mengganti HashMapketika kuncinya adalah tipe primitif. Ada beberapa varian untuk tipe kunci / nilai yang berbeda, meskipun tidak semuanya tersedia untuk umum.

Manfaatnya adalah:

  • Bebas alokasi
  • Tidak ada tinju

Kekurangan:

  • Umumnya lebih lambat, tidak diindikasikan untuk koleksi besar
  • Mereka tidak akan bekerja di proyek non-Android

HashMap dapat diganti dengan yang berikut ini:

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 

Dalam hal memori, berikut adalah contoh SparseIntArrayvs HashMap<Integer, Integer>untuk 1000 elemen:

SparseIntArray:

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

Kelas = 12 + 3 * 4 = 24 byte
Array = 20 + 1000 * 4 = 4024 byte
Total = 8.072 byte

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;
}

Kelas = 12 + 8 * 4 = 48 byte
Entri = 32 + 16 + 16 = 64 byte
Array = 20 + 1000 * 64 = 64024 byte
Total = 64.136 byte

Sumber: Android Memories oleh Romain Guy dari slide 90.

Angka-angka di atas adalah jumlah memori (dalam byte) yang dialokasikan pada heap oleh JVM. Mereka dapat bervariasi tergantung pada JVM spesifik yang digunakan.

The java.lang.instrumentpaket berisi beberapa metode membantu untuk operasi canggih seperti memeriksa ukuran suatu objek dengan getObjectSize(Object objectToSize).

Info tambahan tersedia dari dokumentasi resmi Oracle .

Kelas = 12 byte + (n variabel instan) * 4 byte
Array = 20 byte + (n elemen) * (ukuran elemen)
Entri = 32 byte + (ukuran elemen 1) + (ukuran elemen 2)

Sarpe
sumber
15
Adakah yang bisa membimbing saya dari mana "12 + 3 * 4" dan "20 + 1000 * 4" berasal?
Marian Paździoch
5
@ MarianPaździoch, ia menunjukkan presentasi ( speakerdeck.com/romainguy/android-memories ) di mana kelas menempati 12 byte + 3 variabel 4 byte, array (referensi) menempati 20 byte (dlmalloc - 4, overhead objek - 8, lebar & bantalan) - 8).
CoolMind
1
Sebagai catatan, kelemahan utama lain dari SparseArray adalah bahwa sebagai objek Android perlu diejek untuk pengujian unit. Jika memungkinkan saya sekarang menggunakan objek Java sendiri untuk menyederhanakan pengujian.
David G
@ DavidG Anda hanya dapat menggunakan plugin unmock untuk mengejek dependensi android.
badai salju
1
Bahkan jika Anda tidak menggunakan Android, menyalin kelas ke proyek Anda tidak sulit, itu hanya tergantung pada 3 kelas lainnya. Lisensi APL berarti tidak apa-apa untuk melakukan itu, lisensi apa pun yang Anda kerjakan.
Yann TM
35

Saya datang ke sini hanya ingin contoh cara menggunakan SparseArray. Ini adalah jawaban tambahan untuk itu.

Buat SparseArray

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

Sebuah SparseArraypeta bilangan bulat ke beberapa Object, sehingga Anda dapat mengganti Stringdalam contoh di atas dengan yang lainnya Object. Jika Anda memetakan bilangan bulat ke bilangan bulat, gunakan SparseIntArray.

Tambahkan atau perbarui item

Gunakan put(atau append) untuk menambahkan elemen ke array.

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

Perhatikan bahwa inttombol tidak perlu berurutan. Ini juga dapat digunakan untuk mengubah nilai pada intkunci tertentu .

Hapus item

Gunakan remove(atau delete) untuk menghapus elemen dari array.

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

The intparameter adalah kunci integer.

Nilai pencarian untuk kunci int

Gunakan getuntuk mendapatkan nilai untuk beberapa kunci integer.

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

Anda dapat menggunakan get(int key, E valueIfKeyNotFound)jika Anda ingin menghindari nullkunci yang hilang.

Iterate atas item

Anda dapat menggunakan keyAtdan valueAtbeberapa indeks untuk mengulang koleksi karena SparseArraymempertahankan indeks terpisah berbeda dari inttombol.

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

Perhatikan bahwa kunci dipesan dalam nilai menaik, bukan dalam urutan yang ditambahkan.

Suragch
sumber
18

Namun setiap kali saya mencoba menggunakan HashMap dengan kunci integer dalam proyek android, intelliJ mengatakan bahwa saya harus menggunakan SparseArray sebagai gantinya.

Ini hanya peringatan dari dokumentasi ini tentang array yang jarang:

Ini dimaksudkan agar lebih hemat memori daripada menggunakan HashMap untuk memetakan Integer ke Objek

Ini SparseArraydibuat agar lebih efisien daripada menggunakan HashMap biasa, yang tidak memungkinkan banyak celah dalam array tidak seperti HashMap. Tidak ada yang perlu dikhawatirkan, Anda dapat menggunakan HashMap tradisional jika Anda ingin tidak khawatir tentang alokasi memori ke perangkat.

Rod_Algonquin
sumber
5
Poin-poin tentang menghemat memori jelas valid, tetapi saya tidak pernah mengerti mengapa android tidak bisa membuat SparseArray <T> mengimplementasikan Map <Integer, T> sehingga Anda mendapatkan implementasi Peta yang efisien memori - yang terbaik dari kedua dunia.
Paul Boddington
3
@PaulBoddington juga ingat SparseArraymencegah integer kunci menjadi Auto box yang merupakan operasi dan kinerja biaya lainnya. alih-alih Peta itu akan autobox bilangan bulat primitif keInteger
Rod_Algonquin
Juga benar, tetapi jika mereka membebani metode put dengan memasukkan satu dengan tanda tangan put (int a, T t) Anda masih bisa memasukkan pasangan nilai kunci ke dalam peta tanpa kunci kotak otomatis. Saya hanya berpikir bahwa Collections Framework sangat kuat (salah satu alasan terbaik untuk menggunakan Java) sehingga gila untuk tidak memanfaatkannya.
Paul Boddington
6
@PaulBoddington Collections didasarkan pada objek bukan pada primitif sehingga tidak akan berfungsi dalam Collections API
Rod_Algonquin
10

Jarang array di Jawa adalah struktur data yang memetakan kunci untuk nilai. Gagasan yang sama dengan Peta, tetapi implementasinya berbeda:

  1. Peta direpresentasikan secara internal sebagai array daftar, di mana setiap elemen dalam daftar ini adalah pasangan nilai kunci. Baik kunci maupun nilainya adalah instance objek.

  2. Array jarang dibuat hanya dari dua array: array kunci (primitif) dan array nilai (objek). Mungkin ada kesenjangan dalam indeks array ini, karenanya istilah "jarang" array.

Minat utama SparseArray adalah menyimpan memori dengan menggunakan primitif alih-alih objek sebagai kuncinya.

Ganesh Katikar
sumber
10

Setelah beberapa googling saya mencoba menambahkan beberapa informasi ke yang sudah diposting:

Isaac Taylor membuat perbandingan kinerja untuk SparseArrays dan Hashmaps. Dia menyatakan itu

Hashmap dan SparseArray sangat mirip untuk ukuran struktur data di bawah 1.000

dan

ketika ukuran telah ditingkatkan menjadi tanda 10.000 [...] Hashmap memiliki kinerja yang lebih besar dengan menambahkan objek, sedangkan SparseArray memiliki kinerja yang lebih besar ketika mengambil objek. [...] Pada ukuran 100.000 [...] Hashmap kehilangan kinerja dengan sangat cepat

Perbandingan di Edgblog menunjukkan bahwa SparseArray membutuhkan lebih sedikit memori daripada HashMap karena kunci yang lebih kecil (int vs Integer) dan fakta bahwa

turunan HashMap.Entry harus melacak referensi untuk kunci, nilai dan entri berikutnya. Plus itu juga perlu menyimpan hash entri sebagai int.

Sebagai kesimpulan saya akan mengatakan bahwa perbedaannya bisa berarti jika Anda akan menyimpan banyak data di Peta Anda. Jika tidak, abaikan saja peringatan itu.

Sebastian
sumber
4

Dokumentasi android untuk SparseArray mengatakan "Ini umumnya lebih lambat daripada HashMap tradisional".

Ya itu benar. Tetapi ketika Anda hanya memiliki 10 atau 20 item, perbedaan kinerja seharusnya tidak signifikan.

Jika Anda menulis kode menggunakan HashMaps daripada SparseArrays kode Anda akan bekerja dengan implementasi lain dari Peta dan Anda akan dapat menggunakan semua API java yang dirancang untuk Peta

Saya pikir paling sering kita hanya menggunakan HashMapuntuk mencari nilai yang terkait dengan kunci sementara SparseArraysangat bagus dalam hal ini.

Jika Anda menulis kode menggunakan HashMaps daripada SparseArrays kode Anda akan berfungsi di proyek non-android.

Kode sumber SparseArray cukup sederhana dan mudah dipahami sehingga Anda hanya perlu sedikit upaya memindahkannya ke platform lain (melalui COPY & Paste sederhana).

Override peta sama dengan () dan kode hash () sedangkan SparseArray tidak

Yang bisa saya katakan adalah, (untuk sebagian besar pengembang) siapa yang peduli?

Aspek penting lainnya SparseArrayadalah bahwa ia hanya menggunakan array untuk menyimpan semua elemen saat HashMapdigunakan Entry, jadi SparseArraybiaya memori yang signifikan lebih sedikit daripada a HashMap, lihat ini

suitianshi
sumber
1

Sangat disayangkan bahwa kompiler mengeluarkan peringatan. Saya kira HashMap telah digunakan secara berlebihan untuk menyimpan item.

SparseArrays memiliki tempat mereka. Mengingat mereka menggunakan algoritma pencarian biner untuk menemukan nilai dalam array Anda harus mempertimbangkan apa yang Anda lakukan. Pencarian biner adalah O (log n) sedangkan pencarian hash adalah O (1). Ini tidak selalu berarti bahwa pencarian biner lebih lambat untuk set data yang diberikan. Namun, saat jumlah entri bertambah, kekuatan tabel hash mengambil alih. Karenanya komentar di mana jumlah entri yang rendah dapat sama dan mungkin lebih baik daripada menggunakan HashMap.

HashMap hanya sebagus hash dan juga dapat dipengaruhi oleh load factor (saya pikir di versi selanjutnya mereka mengabaikan load factor sehingga bisa dioptimalkan dengan lebih baik). Mereka juga menambahkan hash sekunder untuk memastikan hash baik. Juga alasan SparseArray bekerja sangat baik untuk entri yang relatif sedikit (<100).

Saya akan menyarankan bahwa jika Anda memerlukan tabel hash dan ingin penggunaan memori yang lebih baik untuk bilangan bulat primitif (tidak ada tinju otomatis), dll., Cobalah trove. ( http://trove.starlight-systems.com - lisensi LGPL). (Tidak ada afiliasi dengan trove, seperti perpustakaan mereka)

Dengan bangunan multi-dex yang disederhanakan, kami memiliki Anda, Anda bahkan tidak perlu mengemas kembali untuk apa yang Anda butuhkan. (Trove memiliki banyak kelas)

Bruce
sumber