web-dev-qa-db-fra.com

Comment dois-je mapper long à int dans hashCode ()?

J'ai une gamme d'objets qui ont un champ long dont la valeur identifie de manière unique un objet particulier sur l'ensemble de mon système, un peu comme un GUID. J'ai remplacé Object.equals() pour utiliser cet identifiant à des fins de comparaison, car je veux qu'il fonctionne avec des copies de l'objet. Maintenant, je veux remplacer Object.hashCode(), ce qui signifie essentiellement mapper ma long à une valeur de retour int.

Si j'ai bien compris le but de hashCode, il est principalement utilisé dans les tables de hachage, donc une distribution uniforme serait souhaitable. Cela signifierait simplement retourner id % 2^32 suffirait. Est-ce tout, ou devrais-je être conscient d'autre chose?

45
Hanno Fietz

Depuis Java 8 vous pouvez utiliser

Long.hashCode(guid);

Pour les anciennes versions de Java vous pouvez utiliser ce qui suit:

Long.valueOf(guid).hashCode();

Notez que cette solution crée un nouvel objet pour la pile, tandis que le premier ne le fait pas (bien qu'il soit probable que Java optimise la création d'objet.)

En regardant les documents, les deux façons utilisent simplement l'algorithme suivant:

(int)(this.longValue()^(this.longValue()>>>32))

Ce sont des solutions décentes car elles utilisent la bibliothèque Java - toujours préférable de tirer parti de quelque chose qui a déjà été testé.

85
TofuBeer

C'est un peu mineur si vous n'utilisez pas Guava déjà, mais Guava peut faites-le pour vous bien:

public int hashCode() {
  return Longs.hashCode(id);
}

Cela vous donne l'équivalent de Long.valueOf(id).hashCode():

return (int) (value ^ (value >>> 32));

De plus, si vous deviez avoir d'autres valeurs ou objets qui faisaient partie du hashcode, vous pourriez simplement écrire

return Objects.hashCode(longValue, somethingElse, ...);

Le long serait placé automatiquement dans un Long afin que vous obteniez le code de hachage correct pour lui dans le cadre du code de hachage global.

9
ColinD

Vous avez bien compris l'objectif de hashCode. Oui, une distribution uniforme est souhaitable (bien que ce ne soit pas une exigence réelle).

Je voudrais suggerer ((id >> 32) ^ id).

L'expression ci-dessus:

  • Utilise tous les bits de la valeur d'origine, ne supprime aucune information à l'avance. Par exemple, selon la façon dont vous générez les ID, les bits supérieurs peuvent changer plus fréquemment (ou l'inverse).
  • N'introduit aucun biais vers les valeurs avec plus de uns (zéros), comme ce serait le cas si les deux moitiés étaient combinées avec une opération OR (AND).
5
Grodriguez

Java 8 ajoute Long.hashCode (long) au JDK.

Le code suivant pourrait donner des performances supérieures. Ce code réduit le calcul à 32 bits int au lieu de calculer avec 64 bits long. Cela peut faire la différence sur les architectures 32 bits et plus petites. Les processus 32 bits sur les machines x86 pourraient optimiser cela en une seule instruction qui enregistre simplement XORs 2.

return (int)(value ^ (value >>> 32));

Comme indiqué dans d'autres réponses, cela n'a pas un bon effet d'avalanche et pourrait donc conduire à des collisions. On pourrait aller avec des fonctions de hachage cryptographiques pour assurer un effet d'avalanche élevé. Cependant, il existe d'autres algorithmes tels que Murmur Hash (plus information ) qui ont un très bon effet d'avalanche mais ne consomment pas autant de temps CPU.

3
Nathan
int result = (int)((longVal >> 32) ^ longVal);

sera plus bien distribué, car modulo ne renverra pas de valeur différente si seuls les bits supérieurs de votre valeur longue ont changé.

1
codymanix

(l >> 32) ^ l Est un bon code de hachage dans la plupart des cas; en particulier lorsque le long a une distribution uniforme.

Comme c'était la réponse acceptée, je poste ceci pour clarifier certains de mes commentaires sur le moment où ce n'est PAS un bon code de hachage pour longtemps.

L'exemple que j'ai donné était une classe Point comme celle-ci:

public class Point {
    private final long coords; //x in high-bits, y in low
    public int getX() {
        return (int)(coords >> 32);
    }
    public int getY() {
        return (int)coords;
    }
    public int hashCode() {
        return (int)((coords >> 32) ^ (coords));
    }
}

Cela peut sembler artificiel, mais parfois vous avez plusieurs "champs" regroupés en un long.

Le champ coords représente donc 32 bits de x et 32 ​​bits de y. Alors, pourquoi est-ce un problème? Eh bien, ce n'est pas si chacun de x et y est réparti uniformément sur leurs 32 bits respectifs. Mais c'est peu probable dans la pratique. Ce qui est plus probable, c'est que X et Y sont délimités par un certain nombre. Disons 1024 car c'est 2 ^ 10. Cela signifie qu'au plus les 10 bits inférieurs de chaque X et Y sont définis:

00000000 00000000 000000XX XXXXXXXX 00000000 00000000 000000YY YYYYYYYY

Il existe 2 ^ 20 (1024 * 1024) combinaisons possibles. Mais que fait l'opération hashCode?

  00000000 00000000 000000XX XXXXXXXX 
^ 00000000 00000000 000000YY YYYYYYYY
-------------------------------------
= 00000000 00000000 000000?? ????????

Il y a au plus 2 ^ 10 (1024) valeurs hashCode possibles car seuls les 10 bits inférieurs peuvent être autre chose que zéro. Le rapport des valeurs de hachage aux valeurs réelles est 1024:(1024*1024) ou 1:1024. Donc, dès le départ, il y a une probabilité de 1/1024 que deux nombres aient le même hachage.

Calculons maintenant la probabilité d'une collision en appliquant les mathématiques à partir du problème d'anniversaire . Soit p(n) la probabilité qu'avec n valeurs il y ait au moins une collision. Nous savons que p (1025+) = 1 car il n'y a que 1024 valeurs.

p(n) = 1 - (n! * (1024 choose n))/1024^n

Cela revient à ce qui suit:

n: p(n)
1: 0.00000
2: 0.00098
3: 0.00293
4: 0.00585
5: 0.00973
6: 0.01457
...
38: 0.50096
...
79: 0.95444
...
148: 0.99999

Avec seulement 38 articles, il y a probablement une collision. Avec 148 objets, il y a 99,999% de chances (au moins une) collision. Avec 148 objets, chaque objet a 7% de chances d'entrer en collision avec un autre objet. Avec une fonction de hachage appropriée, prenant connaissance du domaine, ces chiffres pourraient facilement descendre à 0.

En d'autres termes, connaître votre domaine et comment les choses se produisent dans la pratique est essentiel pour créer un hachage performant. Les fonctions de bibliothèque essaient de faire le meilleur travail possible sans rien savoir de votre domaine et, pour être performantes, elles reposent généralement sur une distribution de données qui ne se produira pas en pratique.

1
Mark Peters