Использование байтового массива в качестве ключа карты - PullRequest
70 голосов
/ 29 июня 2009

Видите ли вы какие-либо проблемы с использованием байтового массива в качестве ключа карты? Я мог бы также сделать new String(byte[]) и хэш String, но проще использовать byte[].

Ответы [ 11 ]

72 голосов
/ 29 июня 2009

Это нормально, если вы хотите ссылочное равенство только для своего ключа - массивы не реализуют "равенство значений" так, как вам бы этого хотелось. Например:

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());

печатает что-то вроде:

false
1671711
11394033

(Фактические цифры не имеют значения; важен тот факт, что они разные.)

Предполагая, что вы на самом деле хотите равенства, я предлагаю вам создать собственную оболочку, которая содержит byte[] и соответствующим образом реализует равенство и генерацию хеш-кода:

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);
    }
}

Обратите внимание, что если вы измените значения в байтовом массиве после использования ByteArrayWrapper, в качестве ключа в HashMap (и т. Д.) У вас будут проблемы с поиском ключа снова ... вы можете взять копию данных в конструкторе ByteArrayWrapper, если хотите, но очевидно, что это будет пустой тратой производительности, если вы знаете, что не будет изменять содержимое байтового массива.

РЕДАКТИРОВАТЬ: Как уже упоминалось в комментариях, вы также можете использовать ByteBuffer для этого (в частности, его ByteBuffer#wrap(byte[]) метод). Я не знаю, действительно ли это правильно, учитывая все дополнительные способности, которые есть у ByteBuffer, которые вам не нужны, но это вариант.

57 голосов
/ 29 июня 2009

Проблема в том, что byte[] использует идентификатор объекта для equals и hashCode, так что

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

не будет совпадать с HashMap. Я вижу три варианта:

  1. Обтекание в String, но тогда вы должны быть осторожны с проблемами кодирования (вам нужно убедиться, что byte -> String -> byte дает вам те же байты).
  2. Используйте List<Byte> (может быть дорого в памяти).
  3. Создайте свой собственный класс переноса, написав hashCode и equals, чтобы использовать содержимое массива байтов.
41 голосов
/ 30 декабря 2012

Мы можем использовать для этого ByteBuffer (это в основном оболочка byte [] с компаратором)

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)));

напечатает

true
13 голосов
/ 12 ноября 2011

Вы можете использовать java.math.BigInteger. Имеет конструктор BigInteger(byte[] val). Это ссылочный тип, поэтому его можно использовать в качестве ключа для хэш-таблицы. И .equals() и .hashCode() определены как для соответствующих целых чисел, что означает, что BigInteger имеет согласованную семантику равных в виде массива byte [].

4 голосов
/ 24 июля 2016

Я очень удивлен, что ответы не указывают на самую простую альтернативу.

Да, использовать HashMap невозможно, но никто не запрещает вам использовать SortedMap в качестве альтернативы. Единственное, что нужно написать - это Comparator, который должен сравнивать массивы. Он не так эффективен, как HashMap, но если вы хотите простую альтернативу, начните (вы можете заменить SortedMap на Map, если хотите скрыть реализацию):

 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;
    }
  }

Эту реализацию можно настроить для других массивов, единственное, о чем вы должны знать, это то, что равные массивы (= одинаковая длина с равными членами) должны возвращать 0 и что у вас есть детерминированный порядок

1 голос
/ 22 октября 2015

Вы должны использовать создание класса, например ByteArrKey, перегрузить хэш-код и аналогичные методы, помните контракт между ними.

Это даст вам большую гибкость, поскольку вы можете пропустить 0 записей, которые добавляются в конец байтового массива, особенно если вы копируете только некоторую часть из другого байтового буфера.

Таким образом, вы решите, насколько оба объекта ДОЛЖНЫ быть равны.

1 голос
/ 29 июня 2009

Я считаю, что массивы в Java не обязательно реализуют методы hashCode() и equals(Object) интуитивно. То есть два одинаковых байтовых массива не обязательно будут использовать один и тот же хэш-код, и они не обязательно будут претендовать на то, что они равны. Без этих двух характеристик ваш HashMap будет вести себя неожиданно.

Поэтому я рекомендую против , используя byte[] в качестве ключей в HashMap.

0 голосов
/ 30 января 2018

Вот решение с использованием TreeMap, интерфейса Comparator и java-метода java.util.Arrays.equals (byte [], byte []);

ПРИМЕЧАНИЕ: порядок на карте не имеет отношения к этому методу

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;
    }
}
0 голосов
/ 07 марта 2014

Вы также можете преобразовать byte [] в «безопасную» строку, используя Base32 или Base64, например:

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

Конечно, есть много вариантов выше, как:

String key = org.apache.commons.codec.binary.Base64.encodeBase64(keyValue);
0 голосов
/ 17 ноября 2009

Arrays.toString (байт)

...