67 votes

Mauvaise idée d’utiliser la clé de type chaîne dans HashMap ?

Je comprends que la classe String' hashCode() la méthode n'est pas garanti pour générer unique des codes de hachage pour distinctes de la Chaîne-s. Je vois beaucoup de l'utilisation de la mise clés de Chaîne dans la table de hachage-s (à l'aide de la Chaîne par défaut hashCode() la méthode). Beaucoup de cette utilisation pourrait entraîner de graves problèmes d'application si une carte, put le déplacement d'un HashMap entrée qui a été précédemment mis sur la carte avec une véritable distinctes de clés de la Chaîne.

Quelles sont les chances que vous allez courir dans le scénario où la Chaîne.hashCode() renvoie la même valeur distincte de la Chaîne-s? Comment les développeurs de contourner ce problème lorsque la clé est une Chaîne de caractères?

114voto

CPerkins Points 5209

Les développeurs n'ont pas à travailler autour de la question de collisions de hachage dans la table de hachage afin de réaliser le programme de justesse.

Il ya un couple de choses importantes à comprendre ici:

  1. Les Collisions sont inhérents à la fonction de hachage, et ils doivent l'être. Le nombre de valeurs possibles (Chaînes dans votre cas, mais elle s'applique à d'autres types d') est beaucoup plus importante que la gamme de nombres entiers.

  2. Chaque utilisation de hachage a une façon de gérer les collisions, et le Java Collections (y compris HashMap) n'est pas une exception.

  3. Le hachage n'est pas impliqué dans l'égalité des tests. Il est vrai que les objets égaux doivent avoir les mêmes hashcodes, mais l'inverse n'est pas vrai: beaucoup de valeurs ont le même hashcode. Donc, ne pas essayer en utilisant un hashcode comparaison comme un substitut pour l'égalité. Les Collections ne sont pas. Ils utilisent de hachage pour sélectionner une sous-collection (appelé un seau dans la Java des Collections de monde), mais ils utilisent .equals() pour vérifier l'égalité.

  4. Non seulement vous n'avez pas à vous soucier de collisions causant des résultats incorrects dans une collection, mais pour la plupart des applications, vous aussi *en général* de ne pas avoir à vous soucier de la performance de Java haché Collections de faire un assez bon travail de gestion de la hashcodes.

  5. Mieux encore, pour le cas vous m'avez demandé (Chaînes de caractères comme des touches de), vous n'avez même pas à vous soucier de la hashcodes eux-mêmes, parce que Java de la classe String génère une assez bonne hashcode. Alors ne la plupart des classes Java.

Un peu plus en détail, si vous le souhaitez:

La façon de hachage œuvres (en particulier, dans le cas de haché collections de Java table de hachage, qui est ce que vous m'avez demandé) est-ce:

  • La table de hachage stocke les valeurs que vous donnez en une collection de sous-collections, appelé seaux. Ce sont effectivement mis en œuvre comme les listes chaînées. Il y a un nombre limité de ces: iirc, de 16 à commencer par défaut, et le nombre augmente à mesure que vous mettez plus d'objets dans la carte. Il devrait toujours y avoir plus de seaux de valeurs. Pour donner un exemple, en utilisant les valeurs par défaut, si vous ajoutez 100 entrées d'une table de hachage, il y aura 256 seaux.

  • Chaque valeur qui peut être utilisé comme une clé, une carte doit être en mesure de générer une valeur entière, appelé le hashcode.

  • La table de hachage utilise cette hashcode pour sélectionner un seau. En fin de compte, cela signifie de prendre la valeur entière modulo le nombre de seaux, mais avant cela, Java de la table de hachage est une méthode interne (appelés hash()), ce qui modifie la hashcode pour réduire certaines sources connues de l'agglomération.

  • Lorsque l'on cherche une valeur, la table de hachage sélectionne le seau, et recherche l'élément un par un linéaire de recherche de la liste liée, à l'aide de .equals().

Donc: vous n'avez pas à travailler autour de collisions pour la correction, et généralement vous n'avez pas à vous inquiéter au sujet de leur rendement, et si vous êtes en utilisant natif des classes Java (comme String), vous n'avez pas à vous soucier de la génération de la hashcode valeurs.

Dans le cas où vous n'avez qu'à écrire votre propre méthode hashcode (ce qui signifie que vous avez écrit une classe avec un composé de la valeur, comme un prénom/nom de la paire), les choses deviennent un peu plus compliqué. C'est tout à fait possible de se tromper ici, mais ce n'est pas la science de fusée. Tout d'abord, sachez ceci: la seule chose que vous devez faire afin d'assurer l'exactitude est de s'assurer que les objets égaux rendement égal hashcodes. Donc, si vous écrivez un hashcode() méthode pour votre classe, vous devez également écrire une méthode equals (), et vous devez examiner les mêmes valeurs de chaque.

Il est possible d'écrire un hashcode() méthode qui est mauvaise, mais bon, je veux dire qu'il serait à même de satisfaire les "objets égaux doivent rendement égal hashcodes" contrainte, mais toujours effectuer très mal, par le fait d'avoir beaucoup de collisions.

L'canonique dégénérer pire des cas, ce serait d'écrire une méthode qui retourne une valeur constante (par exemple, 3) pour tous les cas. Cela signifie que chaque valeur doit être haché dans le même seau.

Il serait encore du travail, mais les performances ne se dégradent que d'une liste chaînée.

De toute évidence, vous n'aurez pas à écrire une terrible hashcode() de la méthode. Si vous utilisez un décent IDE, il est capable de générer pour vous. Depuis StackOverflow aime code, voici le code pour le prénom/nom de la classe ci-dessus.


public class SimpleName {
    private String firstName;
    private String lastName;
    public SimpleName(String firstName, String lastName) {
    	super();
    	this.firstName = firstName;
    	this.lastName = lastName;
    }
    @Override
    public int hashCode() {
    	final int prime = 31;
    	int result = 1;
    	result = prime * result
    			+ ((firstName == null) ? 0 : firstName.hashCode());
    	result = prime * result
    			+ ((lastName == null) ? 0 : lastName.hashCode());
    	return result;
    }
    @Override
    public boolean equals(Object obj) {
    	if (this == obj)
    		return true;
    	if (obj == null)
    		return false;
    	if (getClass() != obj.getClass())
    		return false;
    	SimpleName other = (SimpleName) obj;
    	if (firstName == null) {
    		if (other.firstName != null)
    			return false;
    	} else if (!firstName.equals(other.firstName))
    		return false;
    	if (lastName == null) {
    		if (other.lastName != null)
    			return false;
    	} else if (!lastName.equals(other.lastName))
    		return false;
    	return true;
    }
}

4voto

coobird Points 70356

Je soupçonne fortement que l' HashMap.put méthode ne permet pas de déterminer si la clé est la même, simplement en regardant String.hashCode.

Il va certainement être une chance d'une collision de hachage, on pourrait donc s'attendre à ce que l' String.equals méthode sera également appelée à être sûr que l' Strings sont vraiment égaux, s'il y a bien un cas où les deux Strings ont la même valeur renvoyée par hashCode.

Par conséquent, la nouvelle clé String ne serait jugé de la même clé String que celui qui est déjà dans l' HashMap si et seulement si la valeur renvoyée par hashCode est égal à égal, et l' equals méthode renvoie true.

Aussi d'ajouter, cette pensée serait également vrai pour les classes autres que String, comme l' Object de la classe elle-même a déjà l' hashCode et equals méthodes.

Modifier

Donc, pour répondre à la question, non, ce ne serait pas une mauvaise idée d'utiliser un String pour une clé à un HashMap.

4voto

Michael Borgwardt Points 181658

Ce n'est pas un problème, c'est juste la façon dont les tables de hashage de travail. C'est prouvable impossible d'avoir différents hashcodes pour toutes les chaînes distinctes, parce qu'il y a beaucoup plus de chaînes distinctes que des entiers.

Comme d'autres l'ont écrit, de hachage les collisions sont résolues par la méthode equals (). Le seul problème, c'est celui de la dégénérescence de la table de hachage, menant à de mauvaises performances. C'est pourquoi Java est HashMap a un facteur de charge, un ratio entre les seaux et les éléments insérés, qui, lorsque le seuil est dépassé, la redéfinition de la table avec deux fois le nombre de compartiments.

En général, cela fonctionne très bien, mais seulement si la fonction de hachage est bon, c'est à dire ne pas entraîner de plus que l'statistiquement attendus nombre de collisions pour votre jeu de données d'entrée. String.hashCode() est bon dans ce domaine, mais que ce n'était pas toujours le cas. Prétendument, avant de Java 1.2 uniquement inclus tous les n-ième caractère. Cela a été plus rapide, mais il a provoqué prévisible collisions pour l'ensemble de la Chaîne de partage n-ième caractère très mauvais si vous êtes unluck assez pour avoir une telle entrée régulière, ou si quelqu'un veut faire une attaque DOS sur votre application.

4voto

dberm22 Points 1370

Je vous orienter vers la réponse ici. Alors que ce n'est pas une mauvaise idée d'utiliser des chaînes de caractères( @CPerkins expliqué pourquoi, parfaitement), de stocker les valeurs dans une table de hachage avec entier touches est mieux, car il est généralement plus rapide (bien que de manière imperceptible) et a moins de chance (en fait, pas de chance) de collisions.

Voir ce tableau de collisions à l'aide de 216553 clés dans chaque cas, (vol à partir de ce post, reformaté pour notre discussion)

Hash           Lowercase      Random UUID  Numbers 
=============  =============  ===========  ==============
Murmur            145 ns      259 ns          92 ns
                    6 collis    5 collis       0 collis
FNV-1a            152 ns      504 ns          86 ns
                    4 collis    4 collis       0 collis
FNV-1             184 ns      730 ns          92 ns
                    1 collis    5 collis       0 collis*
DBJ2a             158 ns      443 ns          91 ns
                    5 collis    6 collis       0 collis***
DJB2              156 ns      437 ns          93 ns
                    7 collis    6 collis       0 collis***
SDBM              148 ns      484 ns          90 ns
                    4 collis    6 collis       0 collis**
CRC32             250 ns      946 ns         130 ns
                    2 collis    0 collis       0 collis

Avg Time per key    0.8ps       2.5ps         0.44ps
Collisions (%)      0.002%      0.002%         0%

Bien sûr, le nombre d'entiers est limité à 2^32, où il n'y a pas de limite pour le nombre de cordes (et il n'y a pas de limite théorique à la quantité de touches qui peuvent être stockées dans un HashMap). Si vous utilisez un long (ou même un float), les collisions sont inévitables, et donc pas de "meilleure" qu'une chaîne de caractères. Cependant, même en dépit de collisions de hachage, put() et get() sera toujours de mettre/obtenir la bonne paire clé-valeur (Voir modifier ci-dessous).

En fin de compte, il n'a vraiment pas d'importance, donc utiliser tout ce qui est plus pratique. Mais si le confort ne fait aucune différence, et vous n'avez pas l'intention d'avoir plus de 2^32 entrées, je vous suggère d'utiliser ints que les clés.


MODIFIER

Alors que le ci-dessus est certainement vrai, n'utilisez JAMAIS de "StringKey".hashCode() pour générer une clé à la place de l'original, String - clé pour des raisons de performances - 2 cordes différentes peuvent avoir le même hashCode, provoquant l'écrasement sur votre put() méthode. Java mise en œuvre de l' HashMap est assez intelligent pour gérer les chaînes (de tout type de clé, en fait) avec le même hashcode automatiquement, de sorte qu'il est sage de laisser Java gérer ces choses pour vous.

2voto

Keith Randall Points 17518

Vous parlez de collisions de hachage. Collisions de hachage sont un problème, quel que soit le type qui aurait hashCode. Toutes les classes qui utilisent le code de hachage (par exemple HashMap) gérer les collisions de hachage très bien. Par exemple, HashMap peut stocker plusieurs objets par seau.

Ne vous inquiétez pas à ce sujet sauf si vous appelez hashCode vous-même. Collisions de hachage, bien que rares, ne cassent rien.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X