Вопрос по hashmap, java – Использование байтового массива в качестве ключа карты

70

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

Ваш Ответ

10   ответов
39

te [] с компаратором)

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
+1 для самой легкой оболочки байтового массива (я думаю ...)
Это было бы хорошим решением, но если вы хотите сериализовать карту (как в моем случае), вы не можете использовать этот подход.
Это работает нормально с ByteBuffer.wrap (), но будьте осторожны, если содержимое ByteBuffer было создано с использованием пары вызовов put () для создания байтового массива составного ключа. В этом случае за последним вызовом put () должен следовать вызов rewind () - в противном случае equals () возвращает true, даже если базовые байтовые массивы содержат разные данные.
56

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

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

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

Wrapping in a String, but then you have to be careful about encoding issues (you need to make certain that the byte -> String -> byte gives you the same bytes). Use List<Byte> (can be expensive in memory). Do your own wrapping class, writing hashCode and equals to use the contents of the byte array.
Опция класса упаковки / обработки проста и должна быть очень удобочитаемой.
Я решил проблему с переносом строк, используя шестнадцатеричное кодирование. В качестве альтернативы вы можете использовать кодировку base64.
12

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

спасибо, отличное решение!
Ответ @ vinchan был бы более уместным, так как не было бы проблемы с нулевым начальным байтом.
Звучит привлекательно, но это неправильно, поскольку два массива отличаются только начальными нулевыми элементами (скажем,{0,100} а также{100}) даст тот же BigInteger
Хороший вопрос @leonbloy. Может быть обходной путь: добавив к нему некоторую фиксированную ненулевую начальную байтовую константу, но это потребует написания оболочки вокруг конструктора BigInteger и вернет нас обратно к ответу Джона.
3

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

Да, использовать 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 и что у вас есть детерминированный порядок

0

интерфейса 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;
    }
}
1

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

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

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

1

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

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

Я полагаю, моя формулировка была немного неправильной. Я учел ситуацию, когда один и тот же байтовый массив используется как для вставки в хеш-карту, так и для извлечения из хеш-карты. В этом случае «оба» байтовые массивы идентичны и имеют одинаковый хэш-код.
s / не обязательно / не /
0

поскольку вы должны использовать Arrays.equals и Array.hashCode вместо реализаций массива по умолчанию

И как бы вы заставили HashMap использовать их?
см. ответ Джона Скита (оболочка байтового массива)
0

ованием Base32 или Base64, например:

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

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

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

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

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

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

При условии, что выactually хочу равенства, я предлагаю вам создать свою собственную обертку, которая содержит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 конструктор, если вы хотите, но, очевидно, это будет пустой тратой производительности, если вы знаете,won't меняет содержимое байтового массива.

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

@dfa: & quot; instanceof & quot; test обрабатывает пустой случай
Ой, прости, Джон; Я пропустил эту часть вашего ответа.
Несколько других вещей, которые вы могли бы добавить к реализации оболочки: 1. Возьмите копию байта [] в конструкции, гарантируя, что объект является неизменным, то есть нет никакой опасности, что хэш-код вашего ключа со временем изменится. 2. Предварительно вычислите и сохраните хэш-код один раз (при условии, что скорость важнее, чем затраты на хранение).
Просто хотел бы отметить, что класс java.nio.ByteBuffer, по сути, делает все, что делает ваша обертка, хотя с тем же предостережением, что вы должны использовать его только в том случае, если содержимое байтового массива не изменится. Вы можете изменить свой ответ, чтобы упомянуть его.
@ Adamski: Я упоминаю возможность копирования в конце ответа. В некоторых случаях это правильно, но не в других. Я, вероятно, хотел бы сделать это опцией (возможно, статическими методами вместо конструкторов - copyOf и wrapperAround). Обратите внимание, чтоwithout копируя, вы можете изменять базовый массив до тех пор, пока вы сначала не возьмете хеш и не проверите на равенство, что может быть полезно в некоторых ситуациях.

Похожие вопросы