2009-06-29 49 views

Respuesta

51

El problema es que byte[] usos objeto de identidad para equals y hashCode, de manera que

byte[] b1 = {1, 2, 3} 
byte[] b2 = {1, 2, 3} 

no coincidirá en un HashMap. Veo tres opciones:

  1. de envolver en una String, pero entonces usted tiene que tener cuidado con codificación de temas (que necesita para asegurarse de que el byte -> String -> byte le da la misma bytes).
  2. Use List<Byte> (puede ser costoso en la memoria).
  3. Realice su propia clase de ajuste, escribiendo hashCode y equals para usar el contenido de la matriz de bytes.
+3

Resolví el problema de envoltura de cadenas usando codificación hexadecimal. Alternativamente, puedes usar la codificación base64. – metadaddy

+0

La opción de clase de embalaje/manejo es sencilla y debe ser muy legible. – ZX9

1

Creo que las matrices en Java no implementan necesariamente los métodos hashCode() y equals(Object) de forma intuitiva. Es decir, dos matrices de bytes idénticos no necesariamente compartirán el mismo código hash y no necesariamente afirmarán que son iguales. Sin estos dos rasgos, su HashMap se comportará de forma inesperada.

Por lo tanto, recomiendo contra usando byte[] como claves en un HashMap.

+4

s/no necesariamente/no/ –

+0

supongo que mi redacción estaba un poco lejos. Estaba contabilizando la situación en la que se usa LA MISMA matriz de bytes tanto para insertarla en el mapa HASH Y para recuperarla del mapa hash. En ese caso, las "dos" matrices de bytes son idénticas Y comparten el mismo código hash. –

71

Está bien siempre que solo desee la igualdad de referencia para su clave: las matrices no implementan la "igualdad de valor" de la manera que probablemente le gustaría. Por ejemplo:

byte[] array1 = new byte[1]; 
byte[] array2 = new byte[1]; 

System.out.println(array1.equals(array2)); 
System.out.println(array1.hashCode()); 
System.out.println(array2.hashCode()); 

imprime algo como:

false 
1671711 
11394033 

(. Los números reales son irrelevantes; el hecho de que son diferentes es importante)

Suponiendo que realidad quieren la igualdad , Le sugiero que cree su propio contenedor que contiene un byte[] e implemente la generación de código de igualdad y hash apropiadamente:

public final class ByteArrayWrapper 
{ 
    private final byte[] data; 

    public ByteArrayWrapper(byte[] data) 
    { 
     if (data == null) 
     { 
      throw new NullPointerException(); 
     } 
     this.data = data; 
    } 

    @Override 
    public boolean equals(Object other) 
    { 
     if (!(other instanceof ByteArrayWrapper)) 
     { 
      return false; 
     } 
     return Arrays.equals(data, ((ByteArrayWrapper)other).data); 
    } 

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

Tenga en cuenta que si cambia los valores dentro de la matriz de bytes después de usar el ByteArrayWrapper, como una llave en una HashMap (etc) que tendrá problemas mirando hacia arriba la tecla de nuevo ... se puede tomar una copia de la datos en el constructor ByteArrayWrapper si lo desea, pero obviamente eso supondrá un desperdicio de rendimiento si sabe que no va a cambiando el contenido de la matriz de bytes.

EDITAR: Como se mencionó en los comentarios, también puede usar ByteBuffer para esto (en particular, su método ByteBuffer#wrap(byte[])). No sé si es realmente lo correcto, dadas todas las habilidades adicionales que tiene ByteBuffer s que no necesita, pero es una opción.

+0

@ dfa: la prueba "instanceof" maneja el caso nulo. –

+3

Un par de otras cosas que podría agregar a la implementación del wrapper: 1. Tome una copia del byte [] en la construcción, garantizando así que el objeto sea inmutable, lo que significa que no hay peligro de que el código hash de la clave cambie con el tiempo. 2. Precalcule y almacene el código hash una vez (suponiendo que la velocidad es más importante que la sobrecarga de almacenamiento). – Adamski

+1

@Adamski: menciono la posibilidad de copiar al final de la respuesta. En algunos casos, es lo correcto, pero no en otros. Probablemente quiera hacer que sea una opción (posiblemente métodos estáticos en lugar de constructores - copyOf y wrapperAround). Tenga en cuenta que * sin * copia, puede cambiar la matriz subyacente hasta que primero tome el hash y verifique la igualdad, lo que podría ser útil en algunas situaciones. –

0

veo problemas, ya que se debe utilizar Arrays.equals y Array.hashCode, en lugar de las implementaciones de matriz por defecto

+0

¿Y cómo harías que el HashMap los use? –

+0

ver la respuesta de Jon Skeet (un contenedor de matriz de bytes) – dfa

0

Arrays.toString (bytes)

+1

Podría ser utilizado, pero no muy eficiente. Si desea ir de esta manera, es posible que desee utilizar la codificación base64 en su lugar. –

11

Usted podría utilizar java.math.BigInteger. Tiene un constructor BigInteger(byte[] val). Es un tipo de referencia, por lo que podría usarse como clave para hashtable. Y .equals() y .hashCode() se definen como números enteros respectivos, lo que significa que BigInteger tiene una semántica uniforme igual a la matriz byte [].

+0

gracias, gran solución! –

+11

Suena atractivo, pero está mal, ya que dos arreglos que difieren solo en los elementos iniciales cero (por ejemplo, '{0,100}' y '{100}' darán el mismo BigInteger – leonbloy

+0

Buen punto @leonbloy. Podría haber una solución: al agregarle una constante de bytes directos no nulos, pero requerirá escribir un contenedor alrededor del constructor BigInteger y nos devolverá a la respuesta de Jon. –

34

Podemos utilizar ByteBuffer para esto (Esto es básicamente el byte [] envoltorio con un comparador)

HashMap<ByteBuffer, byte[]> kvs = new HashMap<ByteBuffer, byte[]>(); 
byte[] k1 = new byte[]{1,2 ,3}; 
byte[] k2 = new byte[]{1,2 ,3}; 
byte[] val = new byte[]{12,23,43,4}; 

kvs.put(ByteBuffer.wrap(k1), val); 
System.out.println(kvs.containsKey(ByteBuffer.wrap(k2))); 

imprimirá

true 
+1

+1 para el contenedor de matriz de bytes más liviano (creo ...) – Nicholas

+3

Esto funciona bien con ByteBuffer.wrap(), pero tenga cuidado si el contenido de ByteBuffer se ha creado usando un par de llamadas put() para crear una clave compuesta matriz de bytes. En este caso, la última llamada a put() debe ir seguida de una llamada a rebobinar(); de lo contrario, equals() devuelve verdadero incluso cuando las matrices de bytes subyacentes contengan datos diferentes. – RenniePet

+0

Esta sería una buena solución, pero si desea serializar el mapa (como en mi caso) no puede usar este enfoque. –

0

También podría convertir el byte [] a un ' cadena segura' usando Base32 o base 64, por ejemplo:

byte[] keyValue = new byte[] {…}; 
String key = javax.xml.bind.DatatypeConverter.printBase64Binary(keyValue); 

por supuesto hay muchas variantes de lo anterior, li ke:

String key = org.apache.commons.codec.binary.Base64.encodeBase64(keyValue); 
1

Debe utilizar crear una clase como somthing ByteArrKey y código hash sobrecarga y métodos iguales, recordar el contrato entre ellos.

Esto le dará una mayor flexibilidad ya que puede omitir 0 entradas que se anexan al final de la matriz de bytes, especialmente si copia solo algunas partes del otro búfer de bytes.

De esta manera usted decidirá cómo ambos objetos DEBEN ser iguales.

2

Estoy muy sorprendido de que las respuestas no indiquen la alternativa más simple.

Sí, no es posible utilizar un HashMap, pero nadie le impide usar un SortedMap como alternativa. Lo único es escribir un comparador que necesita comparar las matrices. No es tan performante como un HashMap, pero si quieres una alternativa sencilla, aquí tienes (se puede sustituir SortedMap con el mapa Si desea ocultar la implementación):

private SortedMap<int[], String> testMap = new TreeMap<>(new ArrayComparator()); 

private class ArrayComparator implements Comparator<int[]> { 
    @Override 
    public int compare(int[] o1, int[] o2) { 
     int result = 0; 
     int maxLength = Math.max(o1.length, o2.length); 
     for (int index = 0; index < maxLength; index++) { 
     int o1Value = index < o1.length ? o1[index] : 0; 
     int o2Value = index < o2.length ? o2[index] : 0; 
     int cmp  = Integer.compare(o1Value, o2Value); 
     if (cmp != 0) { 
      result = cmp; 
      break; 
     } 
     } 
     return result; 
    } 
    } 

Esta aplicación se puede ajustar para otros arrays, lo único que debe tener en cuenta es que matrices iguales (= igual longitud con miembros iguales) deben devolver 0 y que tiene una orden de determinación

0

Aquí hay una solución usando TreeMap, interfaz de comparación y java method java. util.Arrays.equals (byte [], byte []);

NOTA: El orden en el mapa no es relevante con este método

SortedMap<byte[], String> testMap = new TreeMap<>(new ArrayComparator()); 

static class ArrayComparator implements Comparator<byte[]> { 
    @Override 
    public int compare(byte[] byteArray1, byte[] byteArray2) { 

     int result = 0; 

     boolean areEquals = Arrays.equals(byteArray1, byteArray2); 

     if (!areEquals) { 
      result = -1; 
     } 

     return result; 
    } 
} 
Cuestiones relacionadas