web-dev-qa-db-fra.com

Quelle implémentation de Map <K, V> dois-je utiliser si ma carte doit être plus petite que rapide?

J'utilise habituellement HashMap dans mes programmes, car je sais que c'est généralement le plus efficace (s'il est utilisé correctement) et qu'il est facile de gérer de grandes cartes. Je connais EnumMap qui est très utile pour les clés d'énumération, mais souvent, je génère une petite carte qui ne deviendra jamais très grande, qui risque d'être ignorée très bientôt et qui n'a pas de problèmes d'accès simultané.

HashMap<K,V> est-il trop compliqué pour ces petites utilisations locales et temporaires? Existe-t-il une autre implémentation simple que je peux utiliser dans ces cas?

Je pense que je recherche une implémentation Map analogue à ArrayList pour List. Est-ce qu'il existe?


Ajouté plus tard après les réponses:

Voici un scénario où une implémentation lente mais très simple pourrait être meilleur - quand j'ai beaucoup, beaucoup de ces Maps. Supposons, par exemple, que j'ai un million environ de ces minuscules cartes, chacune avec une poignée (souvent moins de trois) d'entrées. J'ai un faible taux de référence - peut-être que je ne les mentionne pas réellement avant qu'ils ne soient jetés la plupart du temps. Est-il toujours vrai que HashMap est le meilleur choix pour eux?

L'utilisation des ressources est plus que de la rapidité - j'aimerais par exemple quelque chose qui ne fragmente pas beaucoup le tas et rende les GC trop longs.

Il se peut que HashMap soit la bonne réponse, mais il ne s’agit pas d’une optimisation prématurée (ou du moins, il se peut que ce ne soit pas le cas).


Ajouté beaucoup plus tard après réflexion:

J'ai décidé de coder à la main ma propre SmallMap. Il est facile d’en faire un avec AbstractMap. J'ai également ajouté quelques constructeurs afin qu'une SmallMap puisse être construite à partir d'une Map existante.

En cours de route, je devais décider comment représenter Entrys et implémenter SmallSet pour la méthode entrySet.

J'ai beaucoup appris en codant (et en testant cela) et je souhaite le partager au cas où quelqu'un d'autre en voudrait un. C'est sur github ici .

28
Steve Powell

Il n'y a pas de petite implémentation standard de Map en Java. HashMap est l’une des meilleures implémentations Map et est difficile à battre. Cependant, dans le très petit domaine d’exigences - où l’utilisation du tas et la rapidité de construction est primordiale - il est possible de faire mieux.

J'ai implémenté SmallCollections sur GitHub pour montrer comment faire. Je voudrais aimer quelques commentaires sur si j'ai réussi. Ce n’est pas du tout certain que je l’ai.

Bien que les réponses proposées ici aient parfois été utiles, elles ont eu tendance, en général, à mal comprendre le problème. En tout cas, répondre à ma propre question m’a finalement été beaucoup plus utile que d’en recevoir une.

La question ici a atteint son objectif, et c'est pourquoi j'ai «répondu moi-même».

18
Steve Powell

Je pense que ceci est une optimisation prématurée. Avez-vous des problèmes de mémoire? Des problèmes de performances liés à la création de trop de cartes? Sinon, je pense que HashMap va bien. 

De plus, en regardant l'API, je ne vois rien de plus simple qu'un HashMap

Si vous rencontrez des problèmes, vous pouvez lancer votre propre implémentation de Map, qui possède des éléments internes très simples. Mais je doute que vous fassiez mieux que les implémentations par défaut de Map, et que vous avez le surcoût de vous assurer que votre nouvelle classe fonctionne. Dans ce cas, il pourrait y avoir un problème avec votre conception.

12
hvgotcodes

HashMap est peut-être la collection la plus légère et la plus simple.

Parfois, la solution la plus efficace consiste à utiliser un POJO. par exemple. si vos clés sont des noms de champs et/ou vos valeurs sont des primitives.

4
Peter Lawrey

HashMap est un bon choix car il offre un casage moyen O(1) met et obtient. Cela ne garantit pas que les commandes soient semblables aux implémentations de SortedMap (TreeMap O(log n) met et obtient), mais si vous n’êtes pas tenu de commander, alors HashMap est préférable.

2
Dev

Je suis d'accord avec @hvgotcodes sur le fait qu'il s'agit d'une optimisation prématurée, mais il est toujours bon de connaître tous les outils de la boîte à outils.

Si vous faites beaucoup d'itérations sur ce qui est dans une carte, un LinkedHashMap est généralement beaucoup plus rapide qu'un HashMap, si vous avez beaucoup de threads travaillant avec la carte en même temps, un ConcurrentHashMap est souvent un meilleur choix. Je ne crains pas que toute implémentation de Map soit inefficace pour de petits ensembles de données. C'est généralement l'inverse: une carte mal construite devient facilement inefficace avec de grandes quantités de données si vous avez de mauvaises valeurs de hachage ou si quelque chose fait en sorte que le nombre de compartiments qui lui sont associés est insuffisant.

Bien sûr, il y a des cas où un HashMap n'a aucun sens, comme si vous aviez trois valeurs que vous indexerez toujours avec les touches 0, 1 et 2, mais je suppose que vous comprenez que :-)

1
Fredrik

Android a un ArrayMap dans le but de minimiser la mémoire. En plus d'être dans le noyau, il se trouve dans la bibliothèque de support v4, qui devrait théoriquement pouvoir compiler pour les JRE Oracle ou OpenJDK. Voici un lien vers la source de ArrayMap dans un fork de la bibliothèque de support v4 sur github .

1
Travis Wellman

HashMap utilise plus ou moins de mémoire (lors de la création) en fonction de la façon dont vous l'initialisez: plus de compartiments signifient plus d'utilisation de la mémoire, mais un accès plus rapide pour de grandes quantités d'éléments ; si vous n'avez besoin que d'un petit nombre d'éléments, vous pouvez l'initialiser avec une petite valeur, ce qui produira moins de compartiments qui seront toujours rapides (puisqu'ils recevront chacun quelques éléments). Il n'y a pas de gaspillage de mémoire si vous le définissez correctement (le compromis est essentiellement l'utilisation de la mémoire par rapport à la vitesse).

En ce qui concerne la fragmentation du tas et le gaspillage du cycle GC, etc., une implémentation de Map ne peut pas faire grand chose à leur sujet; tout revient à la façon dont vous le définissez. Comprenez bien que cela ne concerne pas l'implémentation de Java, mais le fait que générique (comme dans, par exemple, ne peut présumer rien des valeurs de clé comme EnumMap fait) des hashtables (pas HashTables) sont les meilleures implémentations possibles d'une structure de carte.

1
Viruzzo

Il existe une alternative appelée AirConcurrentMap qui utilise plus de 1 Ko d'entrées que la carte que j'ai trouvée et utilise plus efficacement la mémoire. Elle est plus rapide que ConcurrentSkipListMap pour les opérations basées sur des clés et plus rapide que toute carte pour les itérations. C’est un navigateur ordonné, à savoir NavigableMap et ConcurrentMap. Il est gratuit pour une utilisation non commerciale sans source et sous licence commerciale avec ou sans source. Voir boilerbay.com pour les graphiques. Divulgation complète: je suis l'auteur.

AirConcurrentMap est conforme aux normes et est donc compatible avec tous les plugs, même pour une carte classique. 

Les itérateurs sont déjà très rapides, en particulier pour les entrées 1K. Les numérisations à haute vitesse utilisent un modèle 'visiteur' avec un rappel de visite unique (k, v) qui atteint la vitesse des flux parallèles Java 8. L'analyse parallèle AirConcurrentMap dépasse d'environ 4x les flux parallèles Java 8. Le visiteur threadé ajoute des méthodes split () et merge () au visiteur mono-thread, rappelant celui de map/reduction:

static class ThreadedSummingVisitor<K> extends ThreadedMapVisitor<K, Long> {
    private long sum;
    // This is idiomatic
    long getSum(VisitableMap<K, Long> map) {
        sum = 0;
        map.getVisitable().visit(this);
        return sum;
    }

    @Override
    public void visit(Object k, Long v) {
        sum += ((Long)v).longValue();
    }

    @Override
    public ThreadedMapVisitor<K, Long> split() {
        return new ThreadedSummingVisitor<K>();
    }

    @Override
    public void merge(ThreadedMapVisitor<K, Long> visitor) {
        sum += ((ThreadedSummingVisitor<K>)visitor).sum;
    }
}
...
// The threaded summer can be re-used in one line now.
long sum = new ThreadedSummingVisitor().getSum((VisitableMap)map);
0
Roger Deran

J'étais aussi intéressé et juste pour une expérience, j'ai créé une carte qui stocke des clés et des valeurs dans des champs et permet jusqu'à 5 entrées. Il consomme 4 moins de mémoire et fonctionne 16 fois plus rapidement que HashMap https://github.com/stokito/jsmallmap

0
stokito