SparseArray vs HashMap

Я могу думать о нескольких причинах, по которым HashMap с целыми ключами намного лучше, чем SparseArray s:

  • Документация Android для SparseArray гласит: "Это обычно медленнее, чем традиционный HashMap".
  • Если вы пишете код с помощью HashMap, а не SparseArray, ваш код будет работать с другими реализациями Map, и вы сможете использовать все Java-API, разработанные для Maps.
  • Если вы пишете код с помощью HashMap, а не SparseArray, ваш код будет работать в проектах, отличных от android.
  • Переопределяет карты equals() и hashCode(), тогда как SparseArray не работает.

Однако всякий раз, когда я пытаюсь использовать HashMap с целыми ключами в проекте Android, IntelliJ говорит мне, что вместо этого я должен использовать SparseArray. Мне это очень сложно понять. Кто-нибудь знает какие-либо веские причины для использования SparseArray s?

Ответ 1

Разреженные массивы могут использоваться для замены хеш-карт, когда ключ является примитивным типом. Существуют несколько вариантов для разных типов ключей/значений, хотя не все из них доступны для публики.

Преимущества:

  • Выделение свободной
  • Нет бокса

Недостатки:

  • Обычно медленнее, не указывается для больших коллекций.
  • Они не будут работать в проекте, отличном от 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 

В терминах памяти приведен пример SparseIntArray vs HashMap для 1000 элементов

SparseIntArray:

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

Класс = 12 + 3 * 4 = 24 байт
Array = 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 байт
Array = 20 + 1000 * 64 = 64024 байта
Всего = 64,136 байт

Источник: Android Memories от Romain Guy от слайда 90.

Цифры выше - это объем памяти (в байтах), выделенный в куче JVM. Они могут варьироваться в зависимости от конкретной используемой JVM.

В пакете java.lang.instrument содержатся некоторые полезные методы для расширенной работы, такие как проверка размера объекта с помощью getObjectSize (Object objectToSize).

Дополнительная информация доступна из официальной документации Oracle

Класс = 12 байт + (n переменных экземпляра) * 4 байт
Array = 20 байт + (n элементов) * (размер элемента)
Entry = 32 байт + (размер 1-го элемента) + (размер элементов 2ns)

Ответ 2

Однако всякий раз, когда я пытаюсь использовать HashMap с целыми ключами в android project, intelliJ говорит мне, что я должен использовать SparseArray.

Это только предупреждение из этой документации этого разреженного массива:

Он предназначен для большей эффективности памяти, чем использование HashMap для map Целые числа с объектами

SparseArray считается эффективным с точки зрения памяти, чем использование обычного HashMap, то есть не допускает, чтобы множественные пробелы в массиве не были похожи на HashMap. Вам не о чем беспокоиться, вы можете использовать традиционную HashMap, если не хотите беспокоиться о распределении памяти на устройстве.

Ответ 3

Редкий массив в Java - это структура данных, которая отображает ключи к значениям. Такая же идея, как карта, но другая реализация:

  • Карта представлена ​​внутренне как массив списков, где каждый элемент в этих списках представляет собой пару ключей, значений. И ключ, и значение являются объектными экземплярами.

  • Редкий массив просто состоит из двух массивов: массивов (примитивов) и массива значений (объектов). В этих показателях массивов могут быть пробелы, поэтому термин "разреженный" массив.

Основной интерес SparseArray заключается в том, что он сохраняет память, используя в качестве ключа примитивы вместо объектов.

Ответ 4

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

Исаак Тейлор сделал сравнение производительности для SparseArrays и Hashmaps. Он утверждает, что

Hashmap и SparseArray очень похожи для структуры данных размеры менее 1000

и

когда размер увеличен до отметки 10 000 [...] Hashmap имеет большую производительность с добавлением объектов, в то время как SparseArray имеет более высокая производительность при извлечении объектов. [...] При размере 100 000 [...] Hashmap теряет производительность очень быстро

Сравнение Edgblog показывает, что для SparseArray требуется гораздо меньше памяти, чем HashMap из-за меньшего ключа (int vs Integer) и тот факт, что

экземпляр HashMap.Entry должен отслеживать ссылки для ключ, значение и следующую запись. Кроме того, он также должен хранить хэш записи как int.

В качестве вывода я бы сказал, что разница может иметь значение, если вы собираетесь хранить много данных на своей карте. В противном случае просто игнорируйте предупреждение.

Ответ 5

Я пришел сюда просто для примера, как использовать 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 некоторый индекс для цикла через коллекцию, потому что 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

Обратите внимание, что клавиши упорядочены по возрастанию, а не в том порядке, в котором они были добавлены.

Ответ 6

В документации по Android для SparseArray говорится: "Обычно медленнее традиционной HashMap".

Да, правильно. Но когда у вас есть только 10 или 20 элементов, разница в производительности должна быть незначительной.

Если вы пишете код, используя HashMaps, а не SparseArrays, ваш код будет работать с другими реализациями Карты, и вы сможете используйте все API-интерфейсы java для карт

Я думаю, что чаще всего мы используем HashMap для поиска значения, связанного с ключом, тогда как SparseArray действительно хорош в этом.

Если вы пишете код, используя HashMaps, а не SparseArrays, ваш код будет работать в не-андроидных проектах.

Исходный код SparseArray довольно прост и понятен, так что вы только платите мало усилий, перекладывая его на другие платформы (через простой COPY & Paste).

Переопределяет карты equals() и hashCode(), тогда как SparseArray не

Все, что я могу сказать, это (для большинства разработчиков), которые заботятся?

Еще один важный аспект SparseArray заключается в том, что он использует только массив для хранения всех элементов, а HashMap использует Entry, поэтому SparseArray стоит значительно меньше памяти, чем HashMap, см. this

Ответ 7

В моем тесте указано, что Sparse имеет ту же скорость, что и Hash при добавлении и 5!!! раз быстрее при поиске с помощью get (int). Однако на настольном процессоре скорость может отличаться.

Ответ 8

Несчастливо, что компилятор выдает предупреждение. Я полагаю, что HashMap был использован для хранения элементов.

SparseArrays имеют свое место. Учитывая, что они используют алгоритм бинарного поиска, чтобы найти значение в массиве, вам нужно рассмотреть, что вы делаете. Бинарный поиск - O (log n), а поиск хэша - O (1). Это не обязательно означает, что бинарный поиск медленнее для заданного набора данных. Однако, по мере роста количества записей, власть хэш-таблицы берет верх. Следовательно, комментарии, где небольшое количество записей может быть равно и, возможно, лучше, чем использование HashMap.

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

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

Благодаря упрощенному многоуровневому зданию вам даже не нужно переупаковывать то, что вам нужно. (у группы есть много классов)