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);
}
equals
et hashCode
méthode
En programmation Java, equals
les méthodes et hashCode
les méthodes ont des règles qui doivent être suivies. Dans la structure de données HashMap, un aspect est particulièrement important : equals
les 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 equals
ré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, hashCode
sa 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 hashCode
la 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
String
L'é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#equals
la 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.
final
Cré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 equals
et hashCode
. Pour les méthodes, nous pouvons utiliser Arrays
des 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 String
la 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 hashCode
et equals
des 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.