web-dev-qa-db-fra.com

Comment hashCode () est-il calculé en Java?

Quelle valeur la méthode hashCode() renvoie-t-elle en java?

J'ai lu qu'il s'agissait d'une référence mémoire d'un objet ... Lorsque j'imprime la valeur de hachage pour new Integer(1), elle vaut 1; pour String("a") est 97. 

Je suis confus: est-ce ASCII ou quel type de valeur est?

47
Jothi

Un hashcode est une valeur entière représentant l'état de l'objet sur lequel il a été appelé. C'est pourquoi une Integer définie sur 1 renverra un hashcode de "1" car un Integer's hashcode et sa valeur sont identiques. Le hashcode d'un caractère est égal à son code de caractère ASCII. Si vous écrivez un type personnalisé, vous êtes responsable de la création d'une bonne implémentation hashCode qui représentera le mieux l'état de l'instance actuelle.

43
Andrew Hare

La valeur renvoyée par hashCode() n’est en aucun cas garantie comme étant l’adresse mémoire de l’objet. Je ne suis pas sûr de l'implémentation de la classe Object, mais gardez à l'esprit que la plupart des classes remplaceront hashCode(), de sorte que deux instances sémantiquement équivalentes (mais qui ne sont pas la même instance) seront hachées à la même valeur. Cela est particulièrement important si les classes peuvent être utilisées dans une autre structure de données, telle que Set, reposant sur hashCode étant cohérent avec equals.

Il n'y a pas de hashCode() qui identifie de manière unique une instance d'objet, quoi qu'il arrive. Si vous souhaitez un code de hachage basé sur le pointeur sous-jacent (dans l’implémentation de Sun, par exemple), utilisez System.identityHashCode(). Cela déléguera la méthode par défaut hashCode, qu’elle ait été remplacée ou non.

Néanmoins, même System.identityHashCode() peut renvoyer le même hachage pour plusieurs objets. Voir les commentaires pour une explication, mais voici un exemple de programme qui génère en permanence des objets jusqu'à ce qu'il en trouve deux avec le même System.identityHashCode(). Lorsque je l'exécute, il trouve rapidement deux System.identityHashCode() qui correspondent, en moyenne après avoir ajouté environ 86 000 objets wrapper longs (et les wrappers Integer pour la clé) à une carte.

public static void main(String[] args) {
    Map<Integer,Long> map = new HashMap<>();
    Random generator = new Random();
    Collection<Integer> counts = new LinkedList<>();

    Long object = generator.nextLong();
    // We use the identityHashCode as the key into the map
    // This makes it easier to check if any other objects
    // have the same key.
    int hash = System.identityHashCode(object);
    while (!map.containsKey(hash)) {
        map.put(hash, object);
        object = generator.nextLong();
        hash = System.identityHashCode(object);
    }
    System.out.println("Identical maps for size:  " + map.size());
    System.out.println("First object value: " + object);
    System.out.println("Second object value: " + map.get(hash));
    System.out.println("First object identityHash:  " + System.identityHashCode(object));
    System.out.println("Second object identityHash: " + System.identityHashCode(map.get(hash)));
}

Exemple de sortie:

Identical maps for size:  105822
First object value: 7446391633043190962
Second object value: -8143651927768852586
First object identityHash:  2134400190
Second object identityHash: 2134400190
44
danben

Si vous voulez savoir comment ils sont implémentés, je vous suggère de lire la source. Si vous utilisez un IDE, vous pouvez simplement + sur une méthode qui vous intéresse et voir comment une méthode est implémentée. Si vous ne pouvez pas faire cela, vous pouvez google pour la source.

Par exemple, Integer.hashCode () est implémenté en tant que

   public int hashCode() {
       return value;
   }

et String.hashCode ()

   public int hashCode() {
       int h = hash;
       if (h == 0) {
           int off = offset;
           char val[] = value;
           int len = count;

           for (int i = 0; i < len; i++) {
               h = 31*h + val[off++];
           }
           hash = h;
       }
       return h;
   }
20
Peter Lawrey

La méthode hashCode() est souvent utilisée pour identifier un objet. Je pense que l'implémentation Object renvoie le pointeur (pas un vrai pointeur, mais un identifiant unique ou quelque chose comme ça) de l'objet. Mais la plupart des classes remplacent la méthode. Comme la classe String. Deux objets String n'ont pas le même pointeur mais ils sont égaux:

new String("a").hashCode() == new String("a").hashCode()

Je pense que l’utilisation la plus courante de hashCode() est Hashtable, HashSet, etc.

Objet API Java hashCode ()

Edit: (en raison d'un vote négatif récent et basé sur un article que j'ai lu sur les paramètres de la machine virtuelle Java)

Le paramètre -XX:hashCode de la machine virtuelle Java vous permet de modifier le mode de calcul du hashCode (voir le numéro 222 de la lettre d'information des spécialistes Java). 

HashCode == 0: Renvoie simplement des nombres aléatoires sans relation avec où en mémoire l'objet est trouvé. Autant que je sache, le global la lecture-écriture de la graine n'est pas optimale pour les systèmes avec beaucoup de processeurs.

HashCode == 1: Compte les valeurs du code de hachage, ne sachant pas à quelle valeur ils commencent, mais cela semble assez élevé.

HashCode == 2: renvoie toujours le même code de hachage d'identité de 1 . Cela peut être utilisé pour tester le code qui repose sur l'identité de l'objet. Le raison pour laquelle JavaChampionTest a renvoyé l'URL de Kirk dans l'exemple ci-dessus est que tous les objets retournaient le même code de hachage.

HashCode == 3: Compte les valeurs du code de hachage à partir de zéro. Il ne semble pas être thread-safe, donc plusieurs threads pourraient générer objets avec le même code de hachage.

HashCode == 4: Cela semble avoir une relation avec l'emplacement de la mémoire à laquelle l'objet a été créé.

HashCode> = 5: il s'agit de l'algorithme par défaut pour Java 8 et possède un graine par fil. Il utilise le schéma xor-shift de Marsaglia pour produire nombres pseudo-aléatoires.

6
alexvetter

J'ai lu qu'il s'agissait d'une référence mémoire d'un objet ..

Object.hashCode() retournait une adresse mémoire il y a environ 14 ans. Pas depuis.

quel type de valeur est

Ce dont il s'agit dépend entièrement de la classe dont vous parlez et du fait qu'elle ait ou non remplacé `Object.hashCode ().

5
user207421

Object.hashCode (), si la mémoire est correctement utilisée (vérifiez le JavaDoc pour Java.lang.Object), dépend de l'implémentation et change en fonction de l'objet (la JVM de Sun tire la valeur de la valeur de la référence à l'objet ).

Notez que si vous implémentez un objet non trivial et que vous voulez le stocker correctement dans un HashMap ou un HashSet, vous DEVEZ redéfinir hashCode () et equals (). hashCode () peut faire tout ce que vous voulez (c'est tout à fait légal, mais sous-optimal de le renvoyer 1.), mais il est essentiel que si votre méthode equals () renvoie true, la valeur renvoyée par hashCode () pour les deux objets est égale.

La confusion et le manque de compréhension de hashCode () et d’égal () sont une source importante de bugs. Assurez-vous de bien vous familiariser avec les JavaDocs pour Object.hashCode () et Object.equals (), et je vous garantis que le temps passé sera rentable.

2
Ben Fowler

Depuis les sources OpenJDK (JDK8):

Utilisez la valeur par défaut de 5 pour générer des codes de hachage:

product(intx, hashCode, 5,                                                
      "(Unstable) select hashCode generation algorithm")       

Quelques données constantes et un nombre généré de manière aléatoire avec une graine lancée par thread:

// thread-specific hashCode stream generator state - Marsaglia shift-xor form
  _hashStateX = os::random() ;
  _hashStateY = 842502087 ;
  _hashStateZ = 0x8767 ;    // (int)(3579807591LL & 0xffff) ;
  _hashStateW = 273326509 ;

Ensuite, cette fonction crée le hashCode (la valeur par défaut est 5 comme spécifié ci-dessus):

static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
     // This form uses an unguarded global Park-Miller RNG,
     // so it's possible for two threads to race and generate the same RNG.
     // On MP system we'll have lots of RW access to a global, so the
     // mechanism induces lots of coherency traffic.
     value = os::random() ;
  } else
  if (hashCode == 1) {
     // This variation has the property of being stable (idempotent)
     // between STW operations.  This can be useful in some of the 1-0
     // synchronization schemes.
     intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;
     value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
  } else
  if (hashCode == 2) {
     value = 1 ;            // for sensitivity testing
  } else
  if (hashCode == 3) {
     value = ++GVars.hcSequence ;
  } else
  if (hashCode == 4) {
     value = cast_from_oop<intptr_t>(obj) ;
  } else {
     // Marsaglia's xor-shift scheme with thread-specific state
     // This is probably the best overall implementation -- we'll
     // likely make this the default in future releases.
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;
  }

  value &= markOopDesc::hash_mask;
  if (value == 0) value = 0xBAD ;
  assert (value != markOopDesc::no_hash, "invariant") ;
  TEVENT (hashCode: GENERATE) ;
  return value;
}

Nous pouvons donc voir qu'au moins dans JDK8, la valeur par défaut est définie sur un thread aléatoire spécifique.

2
Yarden Sheinfeld

Dans la mesure du possible, la méthode hashCode définie par la classe Object renvoie des entiers distincts pour des objets distincts. (Ceci est généralement implémenté en convertissant l'adresse interne de l'objet en un entier, mais cette technique d'implémentation n'est pas requise par le langage de programmation Java ™.)

https://docs.Oracle.com/javase/8/docs/api/Java/lang/Object.html#hashCode--

0
Sam
public static int murmur3_32(int paramInt1, char[] paramArrayOfChar, int paramInt2, int paramInt3) {
/* 121 */     int i = paramInt1;
/*     */     
/* 123 */     int j = paramInt2;
/* 124 */     int k = paramInt3;
/*     */     
/*     */     int m;
/* 127 */     while (k >= 2) {
/* 128 */       m = paramArrayOfChar[(j++)] & 0xFFFF | paramArrayOfChar[(j++)] << '\020';
/*     */       
/* 130 */       k -= 2;
/*     */       
/* 132 */       m *= -862048943;
/* 133 */       m = Integer.rotateLeft(m, 15);
/* 134 */       m *= 461845907;
/*     */       
/* 136 */       i ^= m;
/* 137 */       i = Integer.rotateLeft(i, 13);
/* 138 */       i = i * 5 + -430675100;
/*     */     }
/*     */     
/*     */ 
/*     */ 
/* 143 */     if (k > 0) {
/* 144 */       m = paramArrayOfChar[j];
/*     */       
/* 146 */       m *= -862048943;
/* 147 */       m = Integer.rotateLeft(m, 15);
/* 148 */       m *= 461845907;
/* 149 */       i ^= m;
/*     */     }
/*     */     
/*     */ 
/*     */ 
/* 154 */     i ^= paramInt3 * 2;
/*     */     
/*     */ 
/* 157 */     i ^= i >>> 16;
/* 158 */     i *= -2048144789;
/* 159 */     i ^= i >>> 13;
/* 160 */     i *= -1028477387;
/* 161 */     i ^= i >>> 16;
/*     */     
/* 163 */     return i;
/*     */   }

Si vous êtes vraiment curieux d'apprendre, lisez ce code disponible dans Hashing.class;

Ici, le premier paramètre HASHING_SEED est calculé sur la base du code ci-dessous. 

  { 
    long nanos = System.nanoTime();
    long now = System.currentTimeMillis();
    int SEED_MATERIAL[] = {
            System.identityHashCode(String.class),
            System.identityHashCode(System.class),
            (int) (nanos >>> 32),
            (int) nanos,
            (int) (now >>> 32),
            (int) now,
            (int) (System.nanoTime() >>> 2)
    };

    // Use murmur3 to scramble the seeding material.
    // Inline implementation to avoid loading classes
    int h1 = 0;

    // body
    for (int k1 : SEED_MATERIAL) {
        k1 *= 0xcc9e2d51;
        k1 = (k1 << 15) | (k1 >>> 17);
        k1 *= 0x1b873593;

        h1 ^= k1;
        h1 = (h1 << 13) | (h1 >>> 19);
        h1 = h1 * 5 + 0xe6546b64;
    }

    // tail (always empty, as body is always 32-bit chunks)

    // finalization

    h1 ^= SEED_MATERIAL.length * 4;

    // finalization mix force all bits of a hash block to avalanche
    h1 ^= h1 >>> 16;
    h1 *= 0x85ebca6b;
    h1 ^= h1 >>> 13;
    h1 *= 0xc2b2ae35;
    h1 ^= h1 >>> 16;

    HASHING_SEED = h1;
}

le second paramètre est le tableau de caractères de String, le troisième est toujours '0' et le quatrième est la longueur du tableau de caractères.

Et le calcul ci-dessus ne concerne que le code de hachage de chaîne.

Pour tout entier, son code de hachage sera sa valeur entière . Pour les caractères (deux lettres maximum), il s'agira d'un code ASCII.

0