web-dev-qa-db-fra.com

SparseArray vs HashMap

Je peux penser à plusieurs raisons pour lesquelles HashMaps avec des clés entières sont bien meilleurs que SparseArrays:

  1. La documentation de Android relative à une SparseArray indique: "Elle est généralement plus lente qu'un système traditionnel HashMap".
  2. Si vous écrivez du code en utilisant HashMaps plutôt que SparseArrays, votre code fonctionnera avec d'autres implémentations de Map et vous pourrez utiliser toutes les API Java conçues pour Maps.
  3. Si vous écrivez du code en utilisant HashMaps plutôt que SparseArrays, votre code fonctionnera dans des projets non-Android.
  4. La carte remplace equals() et hashCode() alors que SparseArray ne le fait pas.

Pourtant, chaque fois que j'essaie d'utiliser un HashMap avec des clés entières dans un projet Android, IntelliJ me dit que je devrais utiliser plutôt un SparseArray. Je trouve cela vraiment difficile à comprendre. Est-ce que quelqu'un connaît des raisons impérieuses d'utiliser SparseArrays?

156
Paul Boddington

SparseArray peut être utilisé pour remplacer HashMap lorsque la clé est un type primitif. Il existe certaines variantes pour différents types de clé/valeur, même si elles ne sont pas toutes accessibles au public.

Les avantages sont:

  • Sans allocation
  • Pas de boxe

Désavantages:

  • Généralement plus lent, non indiqué pour les grandes collections
  • Ils ne fonctionneront pas dans un projet non-Android

HashMap peut être remplacé par le suivant:

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 

En termes de mémoire, voici un exemple de SparseIntArray vs HashMap<Integer, Integer> pour 1000 éléments:

SparseIntArray:

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

Classe = 12 + 3 * 4 = 24 octets
Tableau = 20 + 1000 * 4 = 4024 octets
Total = 8 072 octets

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 octets
Entrée = 32 + 16 + 16 = 64 octets
Tableau = 20 + 1000 * 64 = 64024 octets
Total = 64 136 octets

Source: Souvenirs Android de Romain Guy extrait de la diapositive 90.

Les nombres ci-dessus correspondent à la quantité de mémoire (en octets) allouée sur le tas par la machine virtuelle Java. Ils peuvent varier en fonction de la JVM spécifique utilisée.

Le package Java.lang.instrument contient des méthodes utiles pour des opérations avancées, telles que la vérification de la taille d'un objet avec getObjectSize(Object objectToSize).

Des informations supplémentaires sont disponibles sur le site officiel documentation Oracle .

Classe = 12 octets + (n variables d'instance) * 4 octets
Tableau = 20 octets + (n éléments) * (taille de l'élément)
Entrée = 32 octets + (taille du premier élément) + (taille du deuxième élément)

215
Sarpe

Je suis venu ici juste pour vouloir un exemple d'utilisation SparseArray . Ceci est une réponse supplémentaire pour cela.

Créer un SparseArray

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

Un SparseArray mappe des entiers avec un certain Object, de sorte que vous puissiez remplacer String dans l'exemple ci-dessus par un autre Object. Si vous mappez des entiers à des entiers, utilisez SparseIntArray .

Ajouter ou mettre à jour des éléments

Utilisez put (ou append ) pour ajouter des éléments au tableau.

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

Notez que les touches int n'ont pas besoin d'être en ordre. Ceci peut également être utilisé pour changer la valeur d'une clé int particulière.

Supprimer des éléments

Utilisez remove (ou delete ) pour supprimer des éléments du tableau.

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

Le paramètre int est la clé entière.

Rechercher des valeurs pour une clé int

Utilisez get pour obtenir la valeur d'une clé entière.

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

Vous pouvez utiliser get(int key, E valueIfKeyNotFound) si vous souhaitez éviter d'obtenir null pour les clés manquantes.

Itérer sur les objets

Vous pouvez utiliser keyAt et valueAt un index pour parcourir la collection, car SparseArray conserve un index distinct, distinct des clés 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

Notez que les clés sont classées par ordre croissant, pas dans l'ordre dans lequel elles ont été ajoutées.

29
Suragch

Pourtant, chaque fois que j'essaie d'utiliser un HashMap avec des clés entières dans un projet Android, IntelliJ me dit que je devrais plutôt utiliser un SparseArray.

Ce n'est qu'un avertissement de cette documentation de son tableau fragmenté:

Il est conçu pour être plus efficace en termes de mémoire que l'utilisation d'un objet HashMap pour mapper des entiers sur des objets.

La SparseArray est conçue pour être efficace en mémoire par rapport à l'utilisation de HashMap standard, c'est-à-dire qu'elle n'autorise pas plusieurs espaces vides dans le tableau, contrairement à HashMap. Il n'y a rien à craindre, vous pouvez utiliser le HashMap traditionnel si vous ne souhaitez pas vous soucier de l'allocation de mémoire au périphérique.

17
Rod_Algonquin

Après quelques recherches sur Google, j'essaie d'ajouter des informations aux réponses déjà publiées:

Isaac Taylor a comparé les performances de SparseArrays et de Hashmaps. Il affirme que

hashmap et SparseArray sont très similaires pour les tailles de structure de données inférieures à 1 000.

et

lorsque la taille a été augmentée à 10 000 [...] marques, le tableau de hachage offre de meilleures performances lors de l'ajout d'objets, tandis que SparseArray offre de meilleures performances lors de la récupération d'objets. [...] À une taille de 100 000 [...], Hashmap perd très rapidement ses performances

Une comparaison sur Edgblog montre qu'un SparseArray nécessite beaucoup moins de mémoire qu'un HashMap en raison de la plus petite clé (int vs Integer) et du fait que

une instance HashMap.Entry doit garder une trace des références pour la clé, la valeur et l'entrée suivante. De plus, il doit également stocker le hachage de l'entrée sous forme d'int.

En conclusion, je dirais que la différence pourrait avoir une importance si vous allez stocker beaucoup de données dans votre carte. Sinon, ignorez simplement l'avertissement.

10
Sebastian

Un tableau fragmenté dans Java est une structure de données qui mappe les clés aux valeurs. Même idée qu'une carte, mais implémentation différente:

  1. Une carte est représentée en interne sous la forme d'un tableau de listes, où chaque élément de ces listes est une paire clé-valeur. La clé et la valeur sont des instances d'objet.

  2. Un tableau fragmenté est simplement constitué de deux tableaux: un tableau de clés (primitives) et un tableau de valeurs (objets). Il peut y avoir des lacunes dans les indices de ces tableaux, d'où le terme "tableau épars".

L'intérêt principal de SparseArray est qu'il économise de la mémoire en utilisant des primitives au lieu d'objets comme clé.

10
Ganesh Katikar

La documentation de Android d'un SparseArray indique "En règle générale, il est plus lent qu'un HashMap traditionnel".

Oui c'est vrai. Mais lorsque vous ne disposez que de 10 ou 20 éléments, la différence de performances doit être insignifiante.

Si vous écrivez du code en utilisant HashMaps plutôt que SparseArrays, votre code fonctionnera avec d'autres implémentations de Map et vous pourrez utiliser toutes les API Java conçues pour Maps.

Je pense que le plus souvent, nous utilisons uniquement HashMap pour rechercher une valeur associée à une clé alors que SparseArray est vraiment bon à cela.

Si vous écrivez du code en utilisant HashMaps plutôt que SparseArrays, votre code fonctionnera dans des projets non-Android.

Le code source de SparseArray est assez simple et facile à comprendre, de sorte que vous ne payez que peu d'effort pour le déplacer vers d'autres plates-formes (via un simple COPY & Paste).

La carte remplace equals () et hashCode () alors que SparseArray ne le fait pas

Tout ce que je peux dire, c'est (à la plupart des développeurs) qui s'en soucie?

Un autre aspect important de SparseArray est qu’il utilise uniquement un tableau pour stocker tous les éléments tandis que HashMap utilise Entry, de sorte que SparseArray coûte beaucoup moins cher en mémoire qu’un HashMap , voir this

4
suitianshi

Il est regrettable que le compilateur envoie un avertissement. Je suppose que HashMap a été largement utilisé pour stocker des éléments.

Les SparseArrays ont leur place. Étant donné qu’ils utilisent un algorithme de recherche binaire pour trouver une valeur dans un tableau, vous devez tenir compte de ce que vous faites. La recherche binaire est O (log n) tandis que la recherche de hachage est O (1). Cela ne signifie pas nécessairement que la recherche binaire est plus lente pour un ensemble de données donné. Cependant, à mesure que le nombre d'entrées augmente, la puissance de la table de hachage prend le dessus. D'où les commentaires où un faible nombre d'entrées peut être égal et éventuellement meilleur que d'utiliser un HashMap.

Un hashmap est aussi bon que le hachage et peut également être affecté par le facteur de charge (je pense que dans les versions ultérieures, ils ignorent le facteur de charge afin qu'il puisse être mieux optimisé). Ils ont également ajouté un hash secondaire pour s'assurer que le hash est bon. La raison pour laquelle SparseArray fonctionne également très bien pour relativement peu d'entrées (<100).

Je suggérerais que si vous avez besoin d'une table de hachage et souhaitez une meilleure utilisation de la mémoire pour les entiers primitifs (pas de boxe automatique), etc., essayez-le. ( http://trove.starlight-systems.com - licence LGPL). (Aucune affiliation avec trésor, tout comme leur bibliothèque)

Avec le bâtiment multi-dex simplifié, nous n’avons même pas besoin de reconditionner ce que vous avez besoin. (trove a beaucoup de classes)

1
Bruce