SparseArray vs HashMap

177

Eu posso pensar em várias razões pelas quais HashMaps com chaves inteiras são muito melhores que SparseArrays:

  1. A documentação do Android para um SparseArraydiz "Geralmente é mais lenta que uma tradicional HashMap".
  2. Se você escrever código usando HashMaps em vez de SparseArrays, seu código funcionará com outras implementações do Map e você poderá usar todas as APIs Java projetadas para o Maps.
  3. Se você escrever código usando HashMaps em vez de SparseArrays, seu código funcionará em projetos não Android.
  4. Substituições de mapa equals()e hashCode()considerando SparseArrayque não.

No entanto, sempre que tento usar um HashMapcom chaves inteiras em um projeto Android, o IntelliJ diz que eu deveria usar um SparseArray. Acho isso realmente difícil de entender. Alguém conhece algum motivo convincente para usar SparseArrays?

Paul Boddington
fonte

Respostas:

235

SparseArraypode ser usado para substituir HashMapquando a chave é do tipo primitivo. Existem algumas variantes para diferentes tipos de chave / valor, mesmo que nem todas elas estejam disponíveis publicamente.

Os benefícios são:

  • Livre de alocação
  • Sem boxe

Desvantagens:

  • Geralmente mais lento, não indicado para coleções grandes
  • Eles não funcionarão em um projeto que não seja Android

HashMap pode ser substituído pelo seguinte:

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 

Em termos de memória, aqui está um exemplo de SparseIntArrayvs HashMap<Integer, Integer>para 1000 elementos:

SparseIntArray:

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

Classe = 12 + 3 * 4 = 24 bytes
Matriz = 20 + 1000 * 4 = 4024 bytes
Total = 8.072 bytes

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

Classe = 12 + 8 * 4 = 48 bytes
Entrada = 32 + 16 + 16 = 64 bytes
Matriz = 20 + 1000 * 64 = 64024 bytes
Total = 64.136 bytes

Fonte: Memórias Android de Romain Guy, do slide 90.

Os números acima são a quantidade de memória (em bytes) alocada no heap pela JVM. Eles podem variar dependendo da JVM específica usada.

O java.lang.instrumentpacote contém alguns métodos úteis para operações avançadas, como verificar o tamanho de um objeto getObjectSize(Object objectToSize).

Informações adicionais estão disponíveis na documentação oficial do Oracle .

Classe = 12 bytes + (n variáveis ​​de instância) * 4 bytes
Matriz = 20 bytes + (n elementos) * (tamanho do elemento)
Entrada = 32 bytes + (tamanho do 1º elemento) + (tamanho do segundo elemento)

Sarpe
fonte
15
Alguém pode me orientar de onde vêm "12 + 3 * 4" e "20 + 1000 * 4"?
Marian Paździoch
5
@ MarianPaździoch, ele mostrou uma apresentação ( speakerdeck.com/romainguy/android-memories ) em que uma classe ocupa 12 bytes + 3 variáveis ​​de 4 bytes, uma matriz (referência) ocupa 20 bytes (dlmalloc - 4, sobrecarga de objeto - 8, largura e preenchimento) 8)
CoolMind
1
Para constar, outra desvantagem importante do SparseArray é que, como um objeto Android, ele precisa ser ridicularizado para testes de unidade. Sempre que possível, agora uso os próprios objetos de Java para simplificar os testes.
David G
@ DavidD Você pode apenas usar o plugin unmock para zombar das dependências do Android.
tempestade de neve
1
Mesmo se você não estiver executando o Android, copiar a classe para o seu projeto não é difícil, depende apenas de outras 3 classes. A licença da APL significa que está tudo bem em fazer isso, independentemente da licença em que você estiver trabalhando.
Yann TM
35

Eu vim aqui apenas querendo um exemplo de como usar SparseArray. Esta é uma resposta suplementar para isso.

Criar um SparseArray

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

A SparseArraymapeia números inteiros para alguns Object, para que você possa substituir Stringno exemplo acima por qualquer outro Object. Se você estiver mapeando números inteiros para números inteiros, use SparseIntArray.

Adicionar ou atualizar itens

Use put(ou append) para adicionar elementos à matriz.

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

Observe que as intchaves não precisam estar em ordem. Isso também pode ser usado para alterar o valor em uma intchave específica .

Remover itens

Use remove(ou delete) para remover elementos da matriz.

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

O intparâmetro é a chave inteira.

Valores de pesquisa para uma chave int

Use getpara obter o valor de alguma chave inteira.

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

Você pode usar get(int key, E valueIfKeyNotFound)se quiser evitar obter nullas chaves ausentes.

Iterar sobre os itens

Você pode usar keyAte valueAtalgum índice para percorrer a coleção, pois SparseArraymantém um índice separado e distinto das intchaves.

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

Observe que as chaves são ordenadas em valor crescente, não na ordem em que foram adicionadas.

Suragch
fonte
18

No entanto, sempre que tento usar um HashMap com chaves inteiras em um projeto Android, o intelliJ diz que eu deveria usar um SparseArray.

É apenas um aviso desta documentação da matriz esparsa:

Ele pretende ser mais eficiente em termos de memória do que usar um HashMap para mapear Inteiros para Objetos

Ele SparseArrayé feito para ser eficiente em termos de memória do que usar o HashMap regular, ou seja, não permite várias lacunas na matriz, não como o HashMap. Não há nada com o que se preocupar. Você pode usar o HashMap tradicional se não desejar se preocupar com a alocação de memória para o dispositivo.

Rod_Algonquin
fonte
5
Os pontos sobre economia de memória são obviamente válidos, mas eu nunca entendi por que o Android não poderia ter feito o SparseArray <T> implementar o Mapa <Inteiro, T> para obter uma implementação do Mapa com memória eficiente - o melhor dos dois mundos.
Paul Boddington
3
O @PaulBoddington também lembra que SparseArrayimpede que o número inteiro da chave seja a caixa Automático, que é outra operação e desempenho de custo. ao invés de Mapa será Autobox o inteiro primitivoInteger
Rod_Algonquin
Também é verdade, mas se eles sobrecarregassem o método put incluindo um com assinatura put (int a, T t), você ainda seria capaz de colocar pares de valores-chave no mapa sem que as chaves fossem colocadas automaticamente em caixa. Eu apenas acho que o Collections Framework é tão poderoso (uma das melhores razões para usar Java) que é loucura não tirar proveito dele.
Paul Boddington
6
@PaulBoddington coleções são baseadas em objetos não na primitiva por isso vai trabalhar dentro da API Collections
Rod_Algonquin
10

Uma matriz esparsa em Java é uma estrutura de dados que mapeia chaves para valores. Mesma idéia que um mapa, mas implementação diferente:

  1. Um mapa é representado internamente como uma matriz de listas, onde cada elemento nessas listas é um par de chave e valor. Tanto a chave quanto o valor são instâncias de objeto.

  2. Uma matriz esparsa é simplesmente composta de duas matrizes: uma matriz de chaves (primitivas) e uma matriz de valores (objetos). Pode haver lacunas nesses índices de matrizes, daí o termo matriz "esparsa".

O principal interesse do SparseArray é que ele economize memória usando primitivas em vez de objetos como chave.

Ganesh Katikar
fonte
10

Depois de pesquisar no Google, tento adicionar algumas informações às respostas já publicadas:

Isaac Taylor fez uma comparação de desempenho para SparseArrays e Hashmaps. Ele afirma que

o Hashmap e o SparseArray são muito semelhantes para tamanhos de estrutura de dados abaixo de 1.000

e

quando o tamanho foi aumentado para a marca de 10.000, o [...] Hashmap tem melhor desempenho com a adição de objetos, enquanto o SparseArray tem melhor desempenho ao recuperar objetos. [...] Com um tamanho de 100.000, o [...] Hashmap perde o desempenho muito rapidamente

Uma comparação no Edgblog mostra que um SparseArray precisa de muito menos memória que um HashMap devido à chave menor (int vs Integer) e ao fato de que

uma instância HashMap.Entry deve acompanhar as referências para a chave, o valor e a próxima entrada. Além disso, ele também precisa armazenar o hash da entrada como int.

Como conclusão, eu diria que a diferença pode importar se você deseja armazenar muitos dados no seu Mapa. Caso contrário, apenas ignore o aviso.

Sebastian
fonte
4

A documentação do Android para um SparseArray diz "Geralmente é mais lento que um HashMap tradicional".

Sim está certo. Mas quando você tem apenas 10 ou 20 itens, a diferença de desempenho deve ser insignificante.

Se você escrever código usando HashMaps em vez de SparseArrays, seu código funcionará com outras implementações do Map e você poderá usar todas as APIs java projetadas para o Maps

Acho que na maioria das vezes usamos apenas HashMappara pesquisar um valor associado a uma chave, enquanto isso SparseArrayé realmente bom nisso.

Se você escrever código usando HashMaps em vez de SparseArrays, seu código funcionará em projetos que não são do Android.

O código fonte do SparseArray é bastante simples e fácil de entender, para que você pague apenas pouco esforço para movê-lo para outras plataformas (através de um simples COPY & Paste).

O mapa substitui equals () e hashCode () enquanto SparseArray não

Tudo o que posso dizer é (para a maioria dos desenvolvedores) quem se importa?

Outro aspecto importante SparseArrayé que ele usa apenas uma matriz para armazenar todos os elementos enquanto HashMapusa Entry, portanto, SparseArraycusta significativamente menos memória que a HashMap, veja isso

suitianshi
fonte
1

É lamentável que o compilador emita um aviso. Eu acho que o HashMap tem sido muito usado para armazenar itens.

SparseArrays tem o seu lugar. Como eles usam um algoritmo de pesquisa binária para encontrar um valor em uma matriz, é necessário considerar o que está fazendo. A pesquisa binária é O (log n) enquanto a pesquisa de hash é O (1). Isso não significa necessariamente que a pesquisa binária seja mais lenta para um determinado conjunto de dados. No entanto, conforme o número de entradas aumenta, o poder da tabela de hash assume o controle. Daí os comentários em que um número baixo de entradas pode ser igual e possivelmente melhor do que usar um HashMap.

Um HashMap é tão bom quanto o hash e também pode ser afetado pelo fator de carga (acho que nas versões posteriores eles ignoram o fator de carga para que possa ser melhor otimizado). Eles também adicionaram um hash secundário para garantir que o hash seja bom. Além disso, o motivo pelo qual o SparseArray funciona muito bem para relativamente poucas entradas (<100).

Eu sugeriria que, se você precisa de uma tabela de hash e deseja um melhor uso da memória para um número inteiro primitivo (sem boxing automático) etc., experimente o trove. ( http://trove.starlight-systems.com - licença LGPL). (Sem afiliação com o trove, assim como sua biblioteca)

Com a construção multi-dex simplificada, temos que você nem precisa reembalar trove para o que você precisa. (trove tem muitas classes)

Bruce
fonte