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

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

Ответ 1

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

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

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

  • Обтекание в String, но тогда вы должны быть осторожны в вопросах кодирования (вам нужно убедиться, что байт → String → byte дает вам одинаковые байты).
  • Использовать List<Byte> (может быть дорогостоящим в памяти).
  • Сделайте свой собственный класс упаковки, написав hashCode и equals, чтобы использовать содержимое массива байтов.

Ответ 2

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

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

EDIT: как упоминалось в комментариях, вы также можете использовать ByteBuffer для этого (в частности, метод ByteBuffer#wrap(byte[])). Я не знаю, действительно ли это правильно, учитывая все дополнительные возможности, которые ByteBuffer имеют, что вам не нужно, но это вариант.

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

Arrays.toString(байт)

Ответ 10

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

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

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

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

Ответ 11

Вот решение с использованием 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;
    }
}

Ответ 12

Кроме того, мы можем создать свой собственный ByteHashMap, как это,

ByteHashMap byteMap = new ByteHashMap();
byteMap.put(keybyteArray,valueByteArray);

Вот полная реализация

public class ByteHashMap implements Map<byte[], byte[]>, Cloneable,
        Serializable {

    private Map<ByteArrayWrapper, byte[]> internalMap = new HashMap<ByteArrayWrapper, byte[]>();

    public void clear() {
        internalMap.clear();
    }

    public boolean containsKey(Object key) {
        if (key instanceof byte[])
            return internalMap.containsKey(new ByteArrayWrapper((byte[]) key));
        return internalMap.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return internalMap.containsValue(value);
    }

    public Set<java.util.Map.Entry<byte[], byte[]>> entrySet() {
        Iterator<java.util.Map.Entry<ByteArrayWrapper, byte[]>> iterator = internalMap
                .entrySet().iterator();
        HashSet<Entry<byte[], byte[]>> hashSet = new HashSet<java.util.Map.Entry<byte[], byte[]>>();
        while (iterator.hasNext()) {
            Entry<ByteArrayWrapper, byte[]> entry = iterator.next();
            hashSet.add(new ByteEntry(entry.getKey().data, entry
                    .getValue()));
        }
        return hashSet;
    }

    public byte[] get(Object key) {
        if (key instanceof byte[])
            return internalMap.get(new ByteArrayWrapper((byte[]) key));
        return internalMap.get(key);
    }

    public boolean isEmpty() {
        return internalMap.isEmpty();
    }

    public Set<byte[]> keySet() {
        Set<byte[]> keySet = new HashSet<byte[]>();
        Iterator<ByteArrayWrapper> iterator = internalMap.keySet().iterator();
        while (iterator.hasNext()) {
            keySet.add(iterator.next().data);
        }
        return keySet;
    }

    public byte[] put(byte[] key, byte[] value) {
        return internalMap.put(new ByteArrayWrapper(key), value);
    }

    @SuppressWarnings("unchecked")
    public void putAll(Map<? extends byte[], ? extends byte[]> m) {
        Iterator<?> iterator = m.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<? extends byte[], ? extends byte[]> next = (Entry<? extends byte[], ? extends byte[]>) iterator
                    .next();
            internalMap.put(new ByteArrayWrapper(next.getKey()), next
                    .getValue());
        }
    }

    public byte[] remove(Object key) {
        if (key instanceof byte[])
            return internalMap.remove(new ByteArrayWrapper((byte[]) key));
        return internalMap.remove(key);
    }

    public int size() {
        return internalMap.size();
    }

    public Collection<byte[]> values() {
        return internalMap.values();
    }

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

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

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

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

    private final class ByteEntry implements Entry<byte[], byte[]> {
        private byte[] value;
        private byte[] key;

        public ByteEntry(byte[] key, byte[] value) {
            this.key = key;
            this.value = value;
        }

        public byte[] getKey() {
            return this.key;
        }

        public byte[] getValue() {
            return this.value;
        }

        public byte[] setValue(byte[] value) {
            this.value = value;
            return value;
        }

    }
}