Using a byte array as Map key
Question
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[]
.
Solution
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:
- 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
andequals
to use the contents of the byte array.
OTHER TIPS
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 ByteBuffer
s have which you don't need, but it's an option.
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
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.
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
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.
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.
I see problems since you should use Arrays.equals and Array.hashCode, in place of default array implementations
Arrays.toString(bytes)
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);
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;
}
}