Использование массива байтов в качестве ключа карты
Вопрос
Видите ли вы какие-либо проблемы с использованием массива байтов в качестве ключа карты?я тоже мог бы сделать new String(byte[])
и хешировать по String
но его проще использовать byte[]
.
Решение
Проблема в том, что byte[]
использует идентификацию объекта для equals
и hashCode
, так что
byte[] b1 = {1, 2, 3}
byte[] b2 = {1, 2, 3}
не будет совпадать в HashMap
.Я вижу три варианта:
- Заворачивая в
String
, но тогда вам нужно быть осторожным с проблемами кодирования (вам необходимо убедиться, что byte -> String -> byte дает вам одни и те же байты). - Использовать
List<Byte>
(может быть дорого в памяти). - Проведите свой собственный урок упаковки, написав
hashCode
иequals
использовать содержимое байтового массива.
Другие советы
Это нормально, если вам нужно только равенство ссылок для вашего ключа - массивы не реализуют «равенство значений» так, как вам, вероятно, хотелось бы.Например:
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
Есть, которые вам не нужны, но это вариант.
Для этого мы можем использовать 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
Вы могли бы использовать java.math.BigInteger
.Оно имеет BigInteger(byte[] val)
конструктор.Это ссылочный тип, поэтому его можно использовать в качестве ключа для хеш-таблицы.И .equals()
и .hashCode()
определяются как для соответствующих целых чисел, что означает, что BigInteger имеет последовательную семантику равенства как массив byte[].
Я очень удивлен, что в ответах не указана самая простая альтернатива.
Да, использовать HashMap невозможно, но никто не мешает вам использовать SortedMap в качестве альтернативы.Единственное, нужно написать компаратор, который будет сравнивать массивы.Он не так эффективен, как 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 и что у вас есть детерминированный порядок.
Я считаю, что массивы в Java не обязательно реализуют hashCode()
и equals(Object)
методы интуитивно.То есть два идентичных байтовых массива не обязательно будут иметь один и тот же хеш-код и не обязательно будут претендовать на равенство.Без этих двух качеств ваш HashMap будет вести себя неожиданно.
Поэтому я рекомендую против с использованием byte[]
как ключи в HashMap.
Вам следует использовать создание класса что-то вроде ByteArrKey и перегрузку хэш-кода и равных методов, помните о контракте между ними.
Это даст вам большую гибкость, поскольку вы можете пропустить 0 записей, добавляемых в конец массива байтов, особенно если вы копируете только некоторую часть из другого байтового буфера.
Таким образом, вы решите, насколько оба объекта ДОЛЖНЫ быть равными.
Я вижу проблемы, поскольку вы должны использовать Arrays.equals и Array.hashCode вместо реализаций массива по умолчанию.
Arrays.toString(байты)
Вы также можете преобразовать 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);
Вот решение с использованием 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;
}
}