SparseArray vs HashMap


177

Я можу придумати кілька причин, чому HashMaps з цілими ключами набагато краще, ніж SparseArrays:

  1. Документація на Android SparseArrayговорить: "Зазвичай це повільніше, ніж традиційне HashMap".
  2. Якщо ви пишете код за допомогою HashMaps, а не SparseArrays, ваш код буде працювати з іншими реалізаціями Map, і ви зможете використовувати всі Java API, призначені для Maps.
  3. Якщо ви пишете код за допомогою HashMaps, а не SparseArrays, ваш код буде працювати в не андроїд-проектах.
  4. Карта переорієнтована, equals()а hashCode()тоді SparseArrayні.

Але коли я намагаюся використовувати HashMapцілі клавіші в проекті Android, IntelliJ каже мені, що я повинен використовувати SparseArrayзамість цього. Мені це дійсно важко зрозуміти. Хтось знає якісь вагомі причини використання SparseArrays?

Відповіді:


235

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 

Що стосується пам’яті, ось приклад SparseIntArrayvs HashMap<Integer, Integer>для 1000 елементів:

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 байт

Джерело: Android Memories Romain Guy зі слайда 90.

Цифри вище - це об'єм пам'яті (у байтах), виділеного на купі JVM. Вони можуть відрізнятися залежно від конкретного використовуваного СП.

java.lang.instrumentПакет містить деякі корисні методи для складних операцій , таких як перевірка розмірів об'єкта з getObjectSize(Object objectToSize).

Додаткову інформацію можна отримати з офіційної документації Oracle .

Клас = 12 байт + (n змінних екземплярів) * 4 байти
масив = 20 байт + (n елементів) * (розмір елемента)
Введення = 32 байти + (розмір 1-го елемента) + (розмір 2-го елемента)


15
Хтось може мене направити, звідки беруться ті "12 + 3 * 4" та "20 + 1000 * 4"?
Marian Paździoch

5
@ MarianPaździoch, він показав презентацію ( speakerdeck.com/romainguy/android-memories ), де клас займає 12 байт + 3 змінні по 4 байти, масив (посилання) займає 20 байт (dlmalloc - 4, накладні об'єкти - 8, ширина та набивання - 8).
CoolMind

1
Для запису, ще одним ключовим недоліком SparseArray є те, що як Android-об’єкт його потрібно знущатись для тестування одиниць. Де можливо, зараз я використовую власні об’єкти Java для спрощення тестування.
Девід Г

@DavidG Ви можете просто використовувати розблокувати плагін для глузування залежностей від Android.
хуртовина

1
Навіть якщо ви не займаєтесь Android, копіювання класу у ваш проект не є складним, це залежить лише від 3 інших класів. Ліцензія APL означає, що це робити добре, незалежно від того, з якою ліцензією ви працюєте.
Ян ТМ

35

Я прийшов сюди просто бажаючи приклад, як користуватися 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

Ви можете використовувати, get(int key, E valueIfKeyNotFound)якщо хочете уникнути отримання nullвідсутніх ключів.

Ітерація над предметами

Ви можете використовувати keyAtіvalueAt деякий індекс, щоб провести цикл через колекцію, оскільки 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

Зауважте, що ключі впорядковуються у зростаючому значенні, а не в тому порядку, в якому вони були додані.


18

Але коли я намагаюся використовувати HashMap з цілими ключами в проекті для Android, intelliJ каже мені, що я повинен використовувати SparseArray замість цього.

Це лише попередження з цієї документації про розріджений масив:

Він призначений для ефективнішої пам'яті, ніж використання HashMap для зіставлення цілих об’єктів

Це SparseArrayзроблено для ефективної пам'яті, ніж використання звичайного HashMap, тобто не допускає декількох прогалин у масиві, як HashMap. Не варто хвилюватися з цього приводу, ви можете використовувати традиційний HashMap, якщо не хочете турбуватися про розподіл пам'яті для пристрою.


5
Пункти щодо збереження пам’яті, очевидно, справедливі, але я ніколи не розумів, чому андроїд не міг змусити SparseArray <T> реалізувати Map <Integer, T>, щоб ви отримали ефективну пам’ять реалізацію карти - найкращу в обох світі.
Пол Боддінгтон

3
@PaulBoddington також пам’ятає, що SparseArrayзапобігає тому, щоб ключовим цілим числом було автоматичне поле, що є ще однією операцією та ефективністю витрат. а не Map це автоматично заповнить первісне ціле числоInteger
Rod_Algonquin

Також вірно, але якби вони перевантажили метод put, включивши один з поставленим підписом (int a, T t), ви все одно зможете помістити пари "ключ-значення" на карту, не будучи автоматичними коробками. Я просто думаю, що Framework Collections є настільки потужним (одна з найкращих причин використання Java), що божевіллям не користуватися цим.
Пол Боддінгтон

6
@PaulBoddington Колекції базуються на об'єктах, а не на примітивних, тому він не буде працювати в API колекцій
Rod_Algonquin

10

Рідкий масив на Java - це структура даних, яка відображає ключі до значень. Та сама ідея, що і карта, але інша реалізація:

  1. Карта представлена ​​внутрішньо як масив списків, де кожен елемент у цих списках є ключовою, ціннісною парою. І ключ, і значення є екземплярами об'єкта.

  2. Рідкий масив просто складається з двох масивів: масиву ключів (примітивів) та масиву значень (об'єктів). У цих індексах масивів можуть бути прогалини, звідси і термін "розріджений" масив.

Основний інтерес SparseArray полягає в тому, що він зберігає пам'ять, використовуючи примітиви замість об'єктів в якості ключа.


10

Після деякого гуглювання я намагаюся додати деяку інформацію до вже опублікованих дзвінків:

Ісаак Тейлор зробив порівняння продуктивності для SparseArrays та Hashmaps. Він заявляє, що

Hashmap і SparseArray дуже схожі за розмірами структури даних до 1000

і

коли розмір збільшено до позначки 10 000 [...], Hashmap має більшу продуктивність при додаванні об'єктів, тоді як SparseArray має більшу продуктивність при отриманні об'єктів. [...] При розмірі 100 000 [...] Hashmap дуже швидко втрачає продуктивність

Порівняння Edgblog показує, що SparseArray потребує набагато менше пам'яті, ніж HashMap через менший ключ (int vs Integer) і той факт, що

Екземпляр HashMap.Entry повинен відслідковувати посилання на ключ, значення та наступний запис. Крім того, він також повинен зберігати хеш записи як int.

Як висновок я б сказав, що різниця може мати значення, якщо ви збираєтеся зберігати багато даних у своїй карті. В іншому випадку просто ігноруйте попередження.


4

В документації на Android для SparseArray написано: "Зазвичай це повільніше, ніж у традиційного HashMap".

Так, це правильно. Але якщо у вас є лише 10 або 20 предметів, різниця в продуктивності повинна бути незначною.

Якщо ви пишете код за допомогою HashMaps, а не SparseArrays, ваш код буде працювати з іншими реалізаціями Map, і ви зможете використовувати всі API Java, призначені для Карт

Я думаю, що найчастіше ми використовуємо лише HashMapдля пошуку значення, пов’язаного з ключем, а SparseArrayв цьому справді добре.

Якщо ви пишете код за допомогою HashMaps, а не SparseArrays, ваш код буде працювати в не андроїд-проектах.

Вихідний код SparseArray досить простий і зрозумілий, так що ви лише докладете невеликих зусиль для переміщення його на інші платформи (за допомогою простого COPY & Paste).

Переорієнтування на карту дорівнює рівню () та хеш-коду (), тоді як SparseArray - ні

Все, що я можу сказати, - це більшість розробників?

Ще один важливий аспект - SparseArrayце те, що він використовує лише масив для зберігання всіх елементів при HashMapвикористанні Entry, тому SparseArrayкоштує значно менше пам'яті, ніж a HashMap, дивіться це


1

Прикро, що компілятор видає попередження. Я думаю, що HashMap було використане для зберігання предметів.

SparseArrays мають своє місце. Враховуючи, що вони використовують двійковий алгоритм пошуку, щоб знайти значення в масиві, ви повинні врахувати, що ви робите. Двійковий пошук - O (log n), а пошук хешу - O (1). Це не обов'язково означає, що двійковий пошук повільніший для даного набору даних. Однак у міру зростання кількості записів сила хеш-таблиці переймає силу. Звідси коментарі, де низька кількість записів може дорівнювати і, можливо, бути кращою, ніж використання HashMap.

HashMap є настільки ж хорошим, як хеш, а також може впливати на коефіцієнт навантаження (я думаю, що в пізніших версіях вони ігнорують коефіцієнт навантаження, тому його можна краще оптимізувати). Вони також додали вторинний хеш, щоб переконатися, що хеш хороший. Також причина SparseArray дуже добре працює для порівняно небагато записів (<100).

Я б запропонував, якщо вам потрібна хеш-таблиця і ви хочете краще використовувати пам'ять для примітивного цілого числа (без автоматичного боксу) тощо, спробуйте спробувати. ( http://trove.starlight-systems.com) - ліцензія LGPL). (Ніякої приналежності до Trove, як і їх бібліотеки)

Завдяки спрощеній багатоповерховій будівлі, у нас вам навіть не потрібно перепаковувати багаж для того, що вам потрібно. (у Trove багато класів)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.