web-dev-qa-db-fra.com

Quelle est l'importance du facteur de charge dans HashMap?

HashMap a deux propriétés importantes: size et load factor. J'ai parcouru la documentation Java et il est indiqué que 0.75f est le facteur de charge initial. Mais je ne trouve pas l'utilisation réelle de celui-ci.

Quelqu'un peut-il décrire quels sont les différents scénarios dans lesquels nous devons définir un facteur de charge et quelles sont quelques exemples de valeurs idéales pour différents cas?

211
Priyank Doshi

Le documentation l'explique assez bien:

Une instance de HashMap a deux paramètres qui affectent ses performances: la capacité initiale et le facteur de charge. La capacité correspond au nombre de compartiments dans la table de hachage et la capacité initiale est simplement la capacité au moment de la création de la table de hachage. Le facteur de charge est une mesure de la capacité de la table de hachage à être pleine avant que sa capacité ne soit automatiquement augmentée. Lorsque le nombre d'entrées dans la table de hachage dépasse le produit du facteur de charge et de la capacité actuelle, la table de hachage est réorganisée (c'est-à-dire que les structures de données internes sont reconstruites) afin que la table de hachage ait environ deux fois le nombre de compartiments.

En règle générale, le facteur de charge par défaut (0,75) offre un bon compromis entre les coûts d'espace et de temps. Des valeurs plus élevées réduisent la surcharge d'espace, mais augmentent le coût de la recherche (reflété dans la plupart des opérations de la classe HashMap, y compris les opérations get et put). Le nombre prévu d'entrées dans la carte et son facteur de charge doivent être pris en compte lors de la définition de sa capacité initiale, de manière à minimiser le nombre d'opérations de remise en état. Si la capacité initiale est supérieure au nombre maximal d'entrées divisé par le facteur de charge, aucune opération de ré-acheminement ne se produira.

Comme pour toutes les optimisations de performances, il est judicieux d’éviter d’optimiser les choses prématurément (c’est-à-dire sans données fiables sur l’emplacement des goulots d’étranglement).

242
NPE

La capacité initiale par défaut de la HashMap prend est de 16 et le facteur de charge est de 0,75f (soit 75% de la taille actuelle de la carte). Le facteur de charge représente le niveau auquel la capacité HashMap devrait être doublée.

Par exemple produit de la capacité et du facteur de charge sous la forme 16 * 0.75 = 12. Cela signifie qu’après avoir stocké la douzième paire clé - valeur dans la HashMap, sa capacité devient 32.

134
user2791282

En fait, d'après mes calculs, le facteur de charge "parfait" est plus proche de log 2 (~ 0,7). Bien que tout facteur de charge inférieur à cette valeur donne de meilleures performances. Je pense que 0,75 a probablement été tiré d'un chapeau.

Preuve:

Le chaînage peut être évité et la prédiction de branche exploitée en prédisant si un compartiment est vide ou non. Un seau est probablement vide si la probabilité qu'il soit vide dépasse 0,5.

Soit s la taille et n le nombre de clés ajoutées. En utilisant le théorème binomial, la probabilité qu'un seau soit vide est:

P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)

Ainsi, un seau est probablement vide s'il y a moins de

log(2)/log(s/(s - 1)) keys

Lorsque s atteint l'infini et si le nombre de clés ajoutées est tel que P(0) = 0,5, alors n/s s'approche de log (2) rapidement:

lim (log(2)/log(s/(s - 1)))/s as s -> infinity = log(2) ~ 0.693...
34
HelloWorld

Quel est le facteur de charge?

La quantité de capacité qui doit être épuisée pour que HashMap augmente sa capacité?

Pourquoi facteur de charge?

Le facteur de charge est par défaut égal à 0,75 de la capacité initiale (16). Par conséquent, 25% des compartiments seront libres avant une augmentation de la capacité. De nombreux nouveaux compartiments avec de nouveaux codes de hachage pointant vers eux existent donc juste après l'augmentation nombre de seaux.

Maintenant, pourquoi devriez-vous garder beaucoup de compartiments libres et quel est l'impact de leur maintien sur la performance?

Si vous définissez le facteur de charge sur 1,0, il se peut que quelque chose de très intéressant se produise.

Supposons que vous ajoutiez un objet x à votre hashmap dont le hashCode est 888 & dans votre hashmap, le compartiment représentant le hashcode est libre. Le objet x est ajouté au compartiment, mais indiquez à nouveau si vous l'êtes. en ajoutant un autre objet y dont le hashCode est également 888, votre objet y sera ajouté à coup sûr MAIS à la fin du compartiment ( car les seaux ne sont rien d'autre que l'implémentation linkedList stockant clé, valeur & next ) maintenant cela a un impact sur les performances! Puisque votre objet y n'est plus présent dans la tête du compartiment si vous effectuez une recherche, le temps pris ne sera pas O (1) cette fois, cela dépend combien d'articles y a-t-il dans le même seau. C'est ce qu'on appelle une collision de hachage et cela se produit même lorsque votre facteur de charge est inférieur à 1.

Corrélation entre performance, collision de hachage et facteur de charge?

facteur de charge inférieur = plus de compartiments libres = moins de risques de collision = haute performance = grand espace requis.

Corrigez-moi si je me trompe quelque part.

25
Sujal Mandal

De la documentation :

Le facteur de charge est une mesure de la capacité de la table de hachage à être pleine avant que sa capacité ne soit automatiquement augmentée

Cela dépend vraiment de vos besoins particuliers, il n’existe pas de "règle empirique" pour spécifier un facteur de charge initial.

17
Óscar López

Si les seaux deviennent trop pleins, alors nous devons regarder à travers

une très longue liste chaînée.

Et c'est une sorte de vaincre le point.

Alors, voici un exemple où j'ai quatre seaux.

J'ai éléphant et blaireau dans mon HashSet jusqu'à présent.

C'est une très bonne situation, non?

Chaque élément a zéro ou un élément.

Maintenant, nous mettons deux éléments supplémentaires dans notre HashSet.

     buckets      elements
      -------      -------
        0          elephant
        1          otter
         2          badger
         3           cat

Ce n'est pas si mal non plus.

Chaque seau n'a qu'un seul élément. Donc, si je veux savoir, est-ce que cela contient du panda?

Je peux très rapidement regarder le seau numéro 1 et ce n'est pas

là et

Je savais que ce n'était pas dans notre collection.

Si je veux savoir s'il contient du chat, je regarde seau

numéro 3,

Je trouve le chat, je sais très vite si c'est dans notre

collection.

Et si j'ajoutais du koala, eh bien ce n'est pas si grave.

             buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala 
         2          badger
         3           cat

Peut-être que maintenant, au lieu d'être dans le seau numéro 1, je regarde seulement

un élément,

J'ai besoin de regarder deux.

Mais au moins je n'ai pas à regarder l'éléphant, le blaireau et

chat.

Si je cherche encore du panda, ça ne peut être que dans un seau

numéro 1 et

Je n'ai pas à regarder autre chose que la loutre et

koala.

Mais maintenant je mets alligator dans le seau numéro 1 et vous pouvez

voir peut-être où cela se passe.

Que si le numéro de seau 1 ne cesse de devenir de plus en plus grand et

plus gros, alors je dois essentiellement regarder à travers tous

ces éléments pour trouver

quelque chose qui devrait être dans le seau numéro 1.

            buckets      elements
      -------      -------
        0          elephant
        1          otter -> koala ->alligator
         2          badger
         3           cat

Si je commence à ajouter des chaînes à d'autres compartiments,

à droite, le problème ne fait que grandir dans tous les

seau unique.

Comment pouvons-nous empêcher nos seaux de devenir trop pleins?

La solution ici est que

          "the HashSet can automatically

        resize the number of buckets."

Il y a le HashSet se rend compte que les seaux deviennent

trop plein.

Il perd cet avantage de cette recherche unique

éléments.

Et ça va juste créer plus de seaux (généralement deux fois plus qu'avant) et

puis placez les éléments dans le bon seau.

Alors, voici notre mise en œuvre de base HashSet avec séparé

chaînage. Maintenant, je vais créer un "HashSet auto-redimensionnant".

Ce HashSet va se rendre compte que les seaux sont

devenir trop plein et

il a besoin de plus de seaux.

loadFactor est un autre champ de notre classe HashSet.

loadFactor représente le nombre moyen d'éléments par

seau,

au-dessus de laquelle nous voulons redimensionner.

loadFactor est un équilibre entre l'espace et le temps.

Si les seaux deviennent trop pleins, nous redimensionnons.

Cela prend du temps, bien sûr, mais

cela peut nous faire gagner du temps sur la route si les seaux sont un

un peu plus vide.

Voyons un exemple.

Voici un hachage, nous avons ajouté quatre éléments jusqu'à présent.

Éléphant, chien, chat et poisson.

          buckets      elements
      -------      -------
        0          
        1          elephant
         2          cat ->dog
         3           fish
          4         
           5

À ce stade, j’ai décidé que le loadFactor, le

seuil,

le nombre moyen d'éléments par seau que je vais bien

avec, vaut 0,75.

Le nombre de seaux est buckets.length, qui est 6, et

à ce stade, notre HashSet a quatre éléments, de sorte que le

la taille actuelle est 4.

Nous redimensionnerons notre HashSet, c'est-à-dire que nous ajouterons plus de compartiments,

lorsque le nombre moyen d'éléments par seau dépasse

le loadFactor.

C’est quand la taille actuelle divisée par buckets.length est

supérieur à loadFactor.

À ce stade, le nombre moyen d’éléments par seau

est 4 divisé par 6.

4 éléments, 6 seaux, soit 0,67.

C'est moins que le seuil que j'ai fixé à 0,75, donc nous sommes

d'accord.

Nous n'avons pas besoin de redimensionner.

Mais maintenant, ajoutons la marmotte.

                  buckets      elements
      -------      -------
        0          
        1          elephant
         2        woodchuck-> cat ->dog
         3           fish
          4         
           5

Woodchuck se retrouverait dans le seau numéro 3.

À ce stade, la taille actuelle est 5.

Et maintenant, le nombre moyen d'éléments par seau

est le currentSize divisé par buckets.length.

C'est 5 éléments divisés par 6 seaux est 0.83.

Et cela dépasse le loadFactor qui était de 0,75.

Afin de résoudre ce problème, afin de rendre le

des seaux peut-être un peu

plus vide de telle sorte que des opérations comme déterminer si un

seau contient

un élément sera un peu moins complexe, je veux redimensionner

mon HashSet.

Le redimensionnement du HashSet s'effectue en deux étapes.

Je vais d’abord doubler le nombre de seaux, j’avais 6 seaux,

maintenant je vais avoir 12 seaux.

Notez ici que le loadFactor que j'ai défini à 0,75 reste le même.

Mais le nombre de seaux changés est de 12,

le nombre d'éléments est resté le même, soit 5.

5 divisé par 12 correspond à environ 0,42, ce qui est bien inférieur à notre

facteur de charge,

alors tout va bien maintenant.

Mais nous n’avons pas fini car certains de ces éléments sont en

le mauvais seau maintenant.

Par exemple, l'éléphant.

L'éléphant était dans le seau numéro 2 parce que le nombre de

personnages en éléphant

était 8.

Nous avons 6 seaux, 8 moins 6 est 2.

C'est pourquoi il s'est retrouvé dans le numéro 2.

Mais maintenant que nous avons 12 seaux, 8 mod 12 est 8, alors

l'éléphant n'appartient plus au seau numéro 2.

L'éléphant appartient au seau numéro 8.

Qu'en est-il de la marmotte?

Woodchuck a été à l'origine de tout ce problème.

Woodchuck a fini dans le seau numéro 3.

Parce que 9 mod 6 est 3.

Mais maintenant nous faisons 9 mod 12.

9 mod 12 est 9, marmotte se rend au godet numéro 9.

Et vous voyez l'avantage de tout cela.

Le seau numéro 3 ne comporte plus que deux éléments, alors qu’il en avait trois auparavant.

Alors voici notre code,

où nous avions notre HashSet avec chaînage séparé qui

n'a pas fait de redimensionnement.

Maintenant, voici une nouvelle implémentation dans laquelle nous utilisons le redimensionnement.

La plupart de ce code est le même,

nous allons encore déterminer s'il contient la

valeur déjà.

Si ce n'est pas le cas, nous déterminerons le seau

devrait aller dans et

puis ajoutez-le à ce compartiment, ajoutez-le à cette LinkedList.

Mais maintenant, nous incrémentons le champ currentSize.

currentSize était le champ qui gardait la trace du nombre

d'éléments dans notre HashSet.

Nous allons l'incrémenter et ensuite nous allons regarder

à la charge moyenne,

le nombre moyen d'éléments par seau.

Nous ferons cette division ici.

Nous devons faire un peu de casting ici pour nous assurer que

que nous obtenons un double.

Et puis, nous comparerons cette charge moyenne au terrain

que j'ai défini comme

0,75 quand j'ai créé ce HashSet, par exemple, qui était

le loadFactor.

Si la charge moyenne est supérieure à la loadFactor,

cela signifie qu'il y a trop d'éléments par seau sur

moyenne, et je dois réinsérer.

Alors, voici notre mise en œuvre de la méthode pour réinsérer

tous les éléments.

Tout d'abord, je vais créer une variable locale appelée oldBuckets.

Ce qui fait référence aux seaux tels qu'ils sont actuellement

avant que je commence à tout redimensionner.

Remarque Je ne crée pas encore un nouveau tableau de listes chaînées.

Je suis en train de renommer les compartiments oldBuckets.

Maintenant rappelez-vous que les seaux étaient un champ dans notre classe, je vais

maintenant créer un nouveau tableau

des listes chaînées, mais cela aura deux fois plus d'éléments

comme il l'a fait la première fois.

Maintenant, je dois réellement faire la réinsertion,

Je vais parcourir tous les vieux seaux.

Chaque élément dans oldBuckets est une LinkedList de chaînes

c'est un seau.

Je vais passer par ce seau et obtenir chaque élément dans ce

seau.

Et maintenant je vais le réinsérer dans le nouveauBuckets.

Je vais obtenir son hashCode.

Je vais trouver de quel index il s'agit.

Et maintenant je reçois le nouveau seau, la nouvelle LinkedList de

des cordes et

Je vais l'ajouter à ce nouveau seau.

Donc, pour récapituler, les HashSets que nous avons vu sont des tableaux de Linked

Des listes ou des seaux.

Un auto-redimensionnement HashSet peut réaliser en utilisant un certain ratio ou

Je choisirais une taille de table de n * 1,5 ou n + (n >> 1), ce qui donnerait un facteur de charge de .66666 ~ sans division, ce qui est lent sur la plupart des systèmes, en particulier sur les systèmes portables où il n'y a pas de division le matériel.

1
Brett Greenfield

Compréhension complète du facteur de charge et du rehashing: here

0
shatakshi

Pour HashMap DEFAULT_INITIAL_CAPACITY = 16 et DEFAULT_LOAD_FACTOR = 0.75f ​​ cela signifie que MAX nombre de TOUTES les entrées dans le HashMap = 16 * 0.75 = 12 . Lorsque le treizième élément sera ajouté, la capacité (taille du tableau) de HashMap sera doublée! Perfect illustration a répondu à cette question: enter image description here l'image est prise à partir d'ici:

https://javabypatel.blogspot.com/2015/10/what-is-load-factor-and-rehashing-in-hashmap.html

0
provisota