J'ai une liste (List<T> list
) Et je veux indexer ses objets par leurs identifiants à l'aide d'une carte (HashMap<Integer, T> map
). J'utilise toujours list.size()
comme capacité initiale dans le constructeur HashMap
, comme dans le code ci-dessous. Est-ce la meilleure capacité initiale à utiliser dans ce cas?
Remarque: je n'ajouterai jamais plus d'éléments à la carte.
List<T> list = myList;
Map<Integer, T> map = new HashMap<Integer, T>(list.size());
for(T item : list) {
map.put(item.getId(), item);
}
Si vous souhaitez éviter de ressasser le HashMap
et que vous savez qu'aucun autre élément ne sera placé dans le HashMap
, vous devez prendre en compte le facteur de charge ainsi que la capacité initiale. Le facteur de charge pour un HashMap
par défaut à 0,75 .
Le calcul pour déterminer si le ressassement est nécessaire se produit chaque fois qu'une nouvelle entrée est ajoutée, par ex. put
place une nouvelle clé/valeur. Donc, si vous spécifiez une capacité initiale de list.size()
, et un facteur de charge de 1, alors il se ressaisira après le dernier put
. Donc, pour éviter le ré-hachage, utilisez un facteur de charge de 1 et une capacité de list.size() + 1
.
[~ # ~] modifier [~ # ~]
En regardant le code source HashMap
, il va ressasser si la taille ancienne atteint ou dépasse le seuil, donc il ne sera pas ressasser sur le dernier put
. Il semble donc qu'une capacité de list.size()
devrait être correcte.
HashMap<Integer, T> map = new HashMap<Integer, T>(list.size(), 1.0);
Voici la partie pertinente du code source de HashMap
:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
Le mot clé "capacité" est incorrect par définition et n'est pas utilisé de la manière généralement attendue.
Par défaut, le "facteur de charge" d'un HashMap est de 0,75, ce qui signifie que lorsque le nombre d'entrées dans un HashMap atteint 75% de la capacité fournie, il redimensionnera le tableau et le remaniera.
Par exemple, si je le fais:
Map<Integer, Integer> map = new HashMap<>(100);
Lorsque j'ajoute la 75e entrée, la carte redimensionne la table d'entrée à 2 * map.size () (ou 2 * table.length). Nous pouvons donc faire quelques choses:
La meilleure option est la dernière des deux, laissez-moi vous expliquer ce qui se passe ici:
list.size() / 0.75
Cela renverra list.size () + 25% de list.size (), par exemple, si ma liste avait une taille de 100, elle renverrait 133. Nous y ajoutons ensuite 1 car la carte est redimensionnée si sa taille est égal à 75% de la capacité initiale, donc si nous avions une liste avec une taille de 100, nous définirions la capacité initiale à 134, cela signifierait que l'ajout des 100 entrées de la liste n'entraînerait aucun redimensionnement de la carte.
Résultat final:
Map<Integer, Integer> map = new HashMap<>(list.size() / 0.75 + 1);
Goyave Maps.newHashMapWithExpectedSize
utilise cette méthode d'aide pour calculer la capacité initiale pour le facteur de charge par défaut de 0.75
, basé sur un certain nombre de valeurs attendues:
/**
* Returns a capacity that is sufficient to keep the map from being resized as
* long as it grows no larger than expectedSize and the load factor is >= its
* default (0.75).
*/
static int capacity(int expectedSize) {
if (expectedSize < 3) {
checkArgument(expectedSize >= 0);
return expectedSize + 1;
}
if (expectedSize < Ints.MAX_POWER_OF_TWO) {
return expectedSize + expectedSize / 3;
}
return Integer.MAX_VALUE; // any large value
}
référence: source
Dans la documentation newHashMapWithExpectedSize
:
Crée une instance
HashMap
, avec une "capacité initiale" suffisamment élevée pour qu'elle contienne des élémentsexpectedSize
sans croissance. Ce comportement ne peut pas être largement garanti, mais il est observé que c'est vrai pour OpenJDK 1.6. Il ne peut pas non plus être garanti que la méthode n'est pas par inadvertance surdimensionnée la carte retournée.
Ce que tu fais est bien. De cette façon, vous êtes sûr que la carte de hachage a au moins une capacité suffisante pour les valeurs initiales. Si vous avez plus d'informations sur les modèles d'utilisation de la carte de hachage (exemple: est-elle mise à jour fréquemment? Y a-t-il de nombreux nouveaux éléments ajoutés fréquemment?), Vous souhaiterez peut-être définir une plus grande capacité initiale (par exemple, list.size() * 2
), mais jamais plus bas. Utilisez un profileur pour déterminer si la capacité initiale est insuffisante trop tôt.
MISE À JOUR
Merci à @PaulBellora d'avoir suggéré que la capacité initiale devrait être définie sur (int)Math.ceil(list.size() / loadFactor)
(généralement, le facteur de charge par défaut est de 0,75) afin d'éviter un redimensionnement initial.
Selon le documentation de référence de Java.util.HashMap :
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, afin de minimiser le nombre d'opérations de reprise. Si la capacité initiale est supérieure au nombre maximal d'entrées divisé par le facteur de charge, aucune opération de reprise ne se produira jamais.
Cela signifie, si vous savez à l'avance, combien d'entrées le HashMap doit stocker, vous pouvez empêcher le ressassement en choisissant une capacité initiale appropriée et un facteur de charge . Toutefois:
En règle générale, le facteur de charge par défaut (0,75) offre un bon compromis entre le temps et les coûts d'espace. Des valeurs plus élevées réduisent la surcharge d'espace mais augmentent le coût de recherche (reflété dans la plupart des opérations de la classe HashMap, y compris get et put).
La règle générale si vous ne connaissez pas les facteurs internes de charge/capacité:
initialCapacityToUse = (Expected No. of elements in map / 0.75) + 1
Avec cette valeur de capacité initiale, la reprise ne se produira pas pour le stockage étant donné le nombre attendu. d'éléments dans la carte.