Marchez dans la fosse : utilisez un tableau d'octets comme clé de Map en Java

Cet article nous amènera à explorer : comment utiliser un tableau d'octets comme clé dans HashMap. Le mécanisme de HashMap nous empêche de le faire directement. Examinons pourquoi cela se produit et quelques solutions possibles à cette situation.

Comment fonctionne HashMap

HashMap est une structure de données qui utilise un mécanisme de hachage pour stocker et récupérer des valeurs. L'utilisation de codes de hachage pour stocker et récupérer des valeurs peut grandement améliorer les performances de HashMap, car il peut maintenir la complexité temporelle de la recherche de paires clé-valeur à O (1) O(1)Niveau O ( 1 ) . Bien sûr, cela nous oblige égalementhashCode()à répartir les codes de hachage aussi uniformément que possible lors de la mise en œuvre de la méthode, afin d'éviter les conflits de hachage et d'affecter l'efficacité de HashMap.

Lorsque nous appelons put(key, value)la méthode, HashMap calculera hashCode()le code de hachage par la méthode de la clé. Ce code de hachage est utilisé pour déterminer le bucket dans lequel la valeur est finalement stockée :

public V get(Object key) {
    
    
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

Lorsqu'une méthode est utilisée get(key)pour récupérer une valeur, elle passe par une série d'étapes de traitement : d'abord, le code de hachage est calculé à partir de la clé, puis le compartiment de hachage est trouvé. Ensuite, utilisez equals()la méthode pour vérifier si chaque entrée du compartiment est égale à la clé. Enfin, la valeur de l'entrée correspondante est renvoyée :

public V put(K key, V value) {
    
    
    return putVal(hash(key), key, value, false, true);
}

equalset hashCodeméthode

En programmation Java, equalsles méthodes et hashCodeles méthodes ont des règles qui doivent être suivies. Dans la structure de données HashMap, un aspect est particulièrement important : equalsles objets avec le même résultat de comparaison de méthodes doivent renvoyer la même valeur de hachage. Cependant, l'inverse n'est pas nécessairement vrai, c'est-à-dire que les objets avec la même valeur de hachage n'ont pas nécessairement le même equalsrésultat de comparaison de méthode. C'est aussi la raison pour laquelle nous pouvons stocker plusieurs objets dans le même bucket de HashMap.

Lors de l'utilisation de HashMap, il est recommandé de ne pas modifier la valeur de hachage de la clé. Bien que cela ne soit pas obligatoire, il est fortement recommandé de définir les clés comme des objets immuables. Si l'objet est immuable, hashCodesa valeur de hachage ne sera pas modifiée quelle que soit l'implémentation de la méthode.

Par défaut, la valeur de hachage est calculée en fonction de tous les champs de l'objet. Si nous devons utiliser des clés mutables, nous devons remplacer hashCodela méthode pour nous assurer que ses calculs n'impliquent pas de champs mutables. Afin de maintenir cette règle, nous devons également modifier la méthode equals.

Utiliser le tableau d'octets comme clé

Afin de pouvoir récupérer avec succès une valeur d'une carte, l'égalité doit être significative. C'est la principale raison pour laquelle l'utilisation de tableaux d'octets n'est pas vraiment une option. En Java, les tableaux utilisent l'identité d'objet pour déterminer l'égalité. Si nous créons un HashMap en utilisant un tableau d'octets comme clé, seul le même objet de tableau peut être utilisé pour récupérer la valeur.

Créons un exemple simple en utilisant des tableaux d'octets comme clés :

byte[] key1 = {
    
    1, 2, 3};
byte[] key2 = {
    
    1, 2, 3};
Map<byte[], String> map = new HashMap<>();
map.put(key1, "value1");
map.put(key2, "value2");

System.out.println(map.get(key1));
System.out.println(map.get(key2));
System.out.println(map.get(new byte[]{
    
    1, 2, 3}));

Nous avons deux clés identiques, mais nous ne pouvons rien récupérer en utilisant le tableau nouvellement créé avec la même valeur, et le résultat est le suivant :

value1
value2
null

Solution

utiliserString

StringL'égalité est basée sur le contenu du tableau de caractères :

public boolean equals(Object anObject) {
    
    
    if (this == anObject) {
    
    
        return true;
    }
    if (anObject instanceof String) {
    
    
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
    
    
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
    
    
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

Les chaînes sont également immuables et créer une chaîne à partir d'un tableau d'octets est très simple. Nous pouvons facilement encoder et décoder des chaînes en utilisant Base64, puis créer un HashMap qui utilise des chaînes comme clés au lieu de tableaux d'octets :

String key1 = Base64.getEncoder().encodeToString(new byte[]{
    
    1, 2, 3});
String key2 = Base64.getEncoder().encodeToString(new byte[]{
    
    1, 2, 3});
Map<String, String> map = new HashMap<>();
map.put(key1, "value1");
map.put(key2, "value2");

System.out.println(map.get(key1));
System.out.println(map.get(key2));
System.out.println(map.get(Base64.getEncoder().encodeToString(new byte[]{
    
    1, 2, 3})));

Le résultat de l'opération est le suivant :

value2
value2
value2

Remarque :String il y aura une perte de performances lorsque le tableau d'octets sera converti en . Par conséquent, cette solution n'est pas .

utiliserList

Semblable à String, List#equalsla méthode vérifiera l'égalité de chacun de ses éléments :

public boolean equals(Object o) {
    
    
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    while (e1.hasNext() && e2.hasNext()) {
    
    
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}

Une liste fonctionnera correctement comme clé HashMap si ces éléments ont une méthode equals() raisonnable et sont immuables. Nous devons juste nous assurer d'utiliser l'implémentation immuable de List :

List<Byte> key1 = ImmutableList.of((byte) 1, (byte) 2, (byte) 3);
List<Byte> key2 = ImmutableList.of((byte) 1, (byte) 2, (byte) 3);
Map<List<Byte>, String> map = new HashMap<>();
map.put(key1, "value1");
map.put(key2, "value2");

System.out.println(map.get(key1));
System.out.println(map.get(key2));
System.out.println(map.get(ImmutableList.of((byte) 1, (byte) 2, (byte) 3)));

Le résultat de l'opération est le suivant :

value2
value2
value2

Remarque : Une liste d'objets Byte occupera plus de mémoire qu'un tableau d'octets. Par conséquent, cette solution n'est pas .

classe personnalisée ( 推荐使用)

Nous pouvons également définir notre propre classe pour contrôler entièrement le calcul et l'égalité du code de hachage. De cette façon, nous pouvons nous assurer que la solution est rapide et n'a pas une grande empreinte mémoire.

finalCréons une classe avec un seul champ de tableau d'octets privé. Il n'aura pas de méthodes setter, seulement des méthodes getter, pour assurer une immuabilité complète.

Ensuite, implémentez vos propres méthodes equalset hashCode. Pour les méthodes, nous pouvons utiliser Arraysdes classes pour accomplir ces deux tâches. Le code final est le suivant :

public class BytesKey {
    
    
    private final byte[] array;

    public BytesKey(byte[] array) {
    
    
        this.array = array;
    }

    public byte[] getArray() {
    
    
        return array.clone();
    }

    @Override
    public boolean equals(Object o) {
    
    
        if (this == o) {
    
    
            return true;
        }
        if (o == null || getClass() != o.getClass()){
    
    
            return false;
        }
        BytesKey bytesKey = (BytesKey) o;
        return Arrays.equals(array, bytesKey.array);
    }

    @Override
    public int hashCode() {
    
    
        return Arrays.hashCode(array);
    }
}

Enfin, nous utilisons notre classe personnalisée comme clé du HashMap :

BytesKey key1 = new BytesKey(new byte[]{
    
    1, 2, 3});
BytesKey key2 = new BytesKey(new byte[]{
    
    1, 2, 3});
Map<BytesKey, String> map = new HashMap<>();
map.put(key1, "value1");
map.put(key2, "value2");

System.out.println(map.get(key1));
System.out.println(map.get(key2));
System.out.println(map.get(new BytesKey(new byte[]{
    
    1, 2, 3})));

Le résultat de l'opération est le suivant :

value2
value2
value2

Remarque : La classe personnalisée n'a ni Stringla perte de performances ni l'utilisation de la mémoire de la liste d'objets Byte. Cette solution est donc recommandée .

Résumer

Cet article traite des problèmes et des solutions rencontrés lors de l'utilisation d'un tableau d'octets comme clé lors de l'utilisation de HashMap.

Tout d'abord, nous allons examiner pourquoi vous ne pouvez pas utiliser directement des tableaux comme clés. Lors de l'utilisation de HashMap, nous devons nous assurer de l'unicité de chaque clé, et l'utilisation d'un tableau comme clé peut provoquer des conflits. En effet, la valeur hashCode d'un tableau est calculée en fonction de son adresse en mémoire, donc même si le contenu de deux tableaux est exactement le même, leurs emplacements en mémoire sont différents et leur hashCode sera différent. Par conséquent, l'utilisation directe d'un tableau en tant que clé peut entraîner un accès incorrect aux valeurs ou des écrasements inattendus.

Ensuite, nous présenterons la méthode d'utilisation de deux structures de données, String et List, comme solution temporaire. Ce sont à la fois des structures de données comparables et hachables qui peuvent garantir l'unicité. Mais cette méthode n'est pas une solution parfaite, car l'utilisation de String ou List comme clé entraînera une surcharge de performances ou occupera un espace mémoire inutile.

Enfin, nous résoudrons parfaitement ce problème en personnalisant la classe. Cette classe personnalisée contient un champ de tableau d'octets, ainsi que des remplacements hashCodeet equalsdes méthodes pour garantir l'unicité et l'exactitude. De cette façon, nous pouvons éviter les problèmes de performances et d'utilisation de la mémoire lors de l'utilisation de String ou List, et pouvons obtenir une plus grande efficacité tout en garantissant l'exactitude.

Je suppose que tu aimes

Origine blog.csdn.net/heihaozi/article/details/130398856
conseillé
Classement