By shikhar


2009-06-29 13:01:36 8 Comments

Do you see any problem with using a byte array as Map key? I could also do new String(byte[]) and hash by String but it is more straightforward to use byte[].

11 comments

@matdev 2018-01-30 15:59:28

Here is a solution using TreeMap, Comparator interface and java method java.util.Arrays.equals(byte[], byte[]);

NOTE: The ordering in the map is not relevant with this method

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

@Thorsten S. 2016-07-24 13:44:08

I am very surprised that the answers are not pointing out the most simple alternative.

Yes, it is not possible to use a HashMap, but nobody prevents you from using a SortedMap as alternative. The only thing is to write a Comparator which needs to compare the arrays. It is not as performant as a HashMap, but if you want a simple alternative, here you go (you can replace SortedMap with Map if you want to hide the implementation):

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

This implementation can be adjusted for other arrays, the only thing you must be aware of is that equal arrays (= equal length with equal members) must return 0 and that you have a determistic order

@jmspaggi 2019-04-08 01:05:59

Nice solution with the huge benefit of not creating additional objects. Very small bug if arrays are not the same length but longest one only have 0 after shorter one length. Also, managing the order probably helps to speed up on the tree traversal. +1!

@Milind Patil 2015-10-22 11:58:07

You should use create a class somthing like ByteArrKey and overload hashcode and equal methods, remember the contract between them.

This will give you greater flexibility as you can skip 0 entries that are appended at the end of byte array, specially if you copy only some part form the other byte buffer.

This way you will decide how both objects SHOULD be equal.

@Christof R 2014-03-07 12:35:21

You could also convert the byte[] to a 'safe' string using Base32 or Base64, for example:

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

of course there are many variants of the above, like:

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

@Artem Oboturov 2011-11-12 00:11:52

You could use java.math.BigInteger. It has a BigInteger(byte[] val) constructor. It's a reference type, so could be used as a key for hashtable. And .equals() and .hashCode() are defined as for respective integer numbers, which means BigInteger has consistent equals semantics as byte[] array.

@Vsevolod Dyomkin 2012-11-24 22:08:33

thanks, awesome solution!

@leonbloy 2013-11-14 23:55:48

Sounds atractive, but it's wrong, as two arrays that differ only in leading zero elements (say, {0,100} and {100}) will give the same BigInteger

@Artem Oboturov 2013-11-15 06:08:31

Good point @leonbloy. There could be a workaround: by adding a some fixed non-null leading byte constant to it, but it will require to write a wrapper around the BigInteger constructor and will return us back to to Jon's response.

@Artem Oboturov 2013-11-15 06:33:35

@vinchan's response would be more appropriate as there would no zero-leading byte problem.

@byte_array 2012-12-30 00:32:15

We can use ByteBuffer for this (This is basically the byte[] wrapper with a comparator)

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

will print

true

@Nicholas 2014-03-20 18:33:27

+1 for most lightweight byte array wrapper (I think...)

@RenniePet 2015-05-30 08:46:57

This works OK with ByteBuffer.wrap(), but be careful if the ByteBuffer's contents have been created using a couple of put() calls to create a composite key byte array. In this case the last put() call must be followed by a rewind() call - otherwise equals() returns true even when the underlying byte arrays contain different data.

@501 - not implemented 2016-05-18 16:15:42

This would be a nice solution, but if you want to serialize the map (like in my case) you can't use this approach.

@LMD 2019-05-12 09:23:09

Note that : "Because buffer hash codes are content-dependent, it is inadvisable to use buffers as keys in hash maps or similar data structures unless it is known that their contents will not change. " (docs.oracle.com/javase/7/docs/api/java/nio/…)

@Jon Skeet 2009-06-29 13:06:12

It's okay so long as you only want reference equality for your key - arrays don't implement "value equality" in the way that you'd probably want. For example:

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

prints something like:

false
1671711
11394033

(The actual numbers are irrelevant; the fact that they're different is important.)

Assuming you actually want equality, I suggest you create your own wrapper which contains a byte[] and implements equality and hash code generation appropriately:

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

Note that if you change the values within the byte array after using the ByteArrayWrapper, as a key in a HashMap (etc) you'll have problems looking up the key again... you could take a copy of the data in the ByteArrayWrapper constructor if you want, but obviously that will be a waste of performance if you know you won't be changing the contents of the byte array.

EDIT: As mentioned in the comments, you could also use ByteBuffer for this (in particular, its ByteBuffer#wrap(byte[]) method). I don't know whether it's really the right thing, given all the extra abilities that ByteBuffers have which you don't need, but it's an option.

@Jon Skeet 2009-06-29 13:31:35

@dfa: The "instanceof" test handles the null case.

@Adamski 2009-06-29 13:35:24

A couple of other things you could add to the wrapper implementation: 1. Take a copy of the byte[] on construction therefore guaranteeing that the object is immutable, meaning there's no danger your key's hash code will change over time. 2. Pre-compute and store the hash code once (assuming speed is more important than storage overhead).

@Jon Skeet 2009-06-29 13:41:59

@Adamski: I mention the possibility of copying at the end of the answer. In some cases it's the right thing to do, but not in others. I'd probably want to make it an option (possibly static methods instead of constructors - copyOf and wrapperAround). Note that without copying, you can change the underlying array until you first take the hash and check for equality, which could be useful in some situations.

@Adamski 2009-06-29 13:51:13

Oops - Sorry Jon; I missed that part of your response.

@Ed Anuff 2010-11-28 19:41:09

Just wanted to point out that the java.nio.ByteBuffer class essentially does everything your wrapper does, although with the same caveat that you should only use it if the contents of the byte array won't be changing. You might want to amend your answer to make mention of it.

@df. 2009-11-17 03:15:07

Arrays.toString(bytes)

@Maarten Bodewes 2015-03-05 01:23:02

Could be used, but not very efficient. If you want to go this way you may want to use base64 encoding instead.

@Kathy Van Stone 2009-06-29 13:10:22

The problem is that byte[] uses object identity for equals and hashCode, so that

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

will not match in a HashMap. I see three options:

  1. 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).
  2. Use List<Byte> (can be expensive in memory).
  3. Do your own wrapping class, writing hashCode and equals to use the contents of the byte array.

@metadaddy 2012-04-08 08:52:15

I solved the string-wrapping problem by using hex-encoding. You could alternatively use base64 encoding.

@ZX9 2016-03-07 19:51:22

The wrapping/handling class option is straightforward and should be very readable.

@dfa 2009-06-29 13:06:48

I see problems since you should use Arrays.equals and Array.hashCode, in place of default array implementations

@Michael Borgwardt 2009-06-29 13:11:43

And how would you make the HashMap use those?

@dfa 2009-06-29 13:41:56

see Jon Skeet's answer (a byte array wrapper)

@Adam Paynter 2009-06-29 13:05:44

I believe that arrays in Java do not necessarily implement the hashCode() and equals(Object) methods intuitively. That is, two identical byte arrays will not necessarily share the same hash code and they will not necessarily claim to be equal. Without these two traits, your HashMap will behave unexpectedly.

Therefore, I recommend against using byte[] as keys in a HashMap.

@Michael Borgwardt 2009-06-29 13:11:06

s/not necessarily/not/

@Adam Paynter 2009-06-29 13:58:59

I suppose my wording was a bit off. I was accounting for the situation where THE SAME byte array is being used both for insertion into the hash map AND for retrieval from the hash map. In that case, "both" byte arrays are identical AND share the same hash code.

Related Questions

Sponsored Content

40 Answered Questions

[SOLVED] How do I efficiently iterate over each entry in a Java Map?

22 Answered Questions

34 Answered Questions

[SOLVED] Create ArrayList from array

50 Answered Questions

[SOLVED] Sort a Map<Key, Value> by values

31 Answered Questions

[SOLVED] Convert InputStream to byte array in Java

14 Answered Questions

25 Answered Questions

[SOLVED] How to convert a byte array to a hex string in Java?

  • 2012-03-11 13:06:24
  • Andre
  • 638592 View
  • 587 Score
  • 25 Answer
  • Tags:   java bytearray hex

35 Answered Questions

17 Answered Questions

[SOLVED] How to update a value, given a key in a hashmap?

  • 2010-11-11 18:34:01
  • laertis
  • 692329 View
  • 567 Score
  • 17 Answer
  • Tags:   java key hashmap

4 Answered Questions

[SOLVED] How to convert byte array to string

Sponsored Content