Использование массива байтов в качестве ключа карты

StackOverflow https://stackoverflow.com/questions/1058149

  •  21-08-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

Проблема в том, что 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 использовать содержимое байтового массива.

Другие советы

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

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;
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top