[JAVA] How to use an array for HashMap keys

Looking at Map keys are not good if they are arranged in an array, there are various ways to make it work while thinking that it will not work. Notes on using byte array as key in Java Map is linked, and [Using a byte array as Map key](http: / /stackoverflow.com/questions/1058149/using-a-byte-array-as-map-key) has a link, and the last Stack Overflow has a lot of it.

HashMap key assumptions

Here, it is HashMap. It should change with TreeMap. → How to use arrays for TreeMap keys The key premise is --Implementing equals --Implementing hashCode --Immutable instance

The first and second are used perfectly with containsKey, get, put of HashMap. The third is not imuutable and can be changed later, but if you change the key value after putting it, it may not be found. If you change this to DEF after putting it in ABC, it is natural that you can not find it in ABC, but there is a possibility that it will not be found in DEF. The reason is that the hashCode changes, but what originally goes into the place that corresponds to the hashCode of DEF has entered the place that corresponds to the hashCode of ABC.

Results of array equals and hashCode

Equals and hashCode are incorrect for arrays.

Main.java


    public static void main(String[] args) {
        byte[] b01 = { 1, 2, 3 };
        byte[] b02 = { 1, 2, 3 };
        int h01 = b01.hashCode();
        int h02 = b02.hashCode();
        System.out.println(b01 == b02);
        System.out.println(b01.equals(b02));
        System.out.println(h01 == h02);
        int h11 = Arrays.hashCode(b01);
        int h12 = Arrays.hashCode(b02);
        System.out.println(Arrays.equals(b01, b02));
        System.out.println(h11 == h12);
    }

Result is···

false
false
false
true
true

The reason why Arrays.equals and Arrays.hashCode are bothered is that the equals and hashCode of the array are incorrect.

Part 1: Make a ByteArrayWrapper

How to implement equals and hashCode by wrapping byte [].

ByteArrayWrapper.java


import java.util.Arrays;

public class ByteArrayWrapper {
    private byte[] data;

    public ByteArrayWrapper(byte[] data) {
        this.data = data;
    }

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

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

Let's test it.

Main.java


    public static void main(String[] args) {
        byte[] b01 = { 1, 2, 3 };
        byte[] b02 = { 1, 2, 3 };
        ByteArrayWrapper w01 = new ByteArrayWrapper(b01);
        ByteArrayWrapper w02 = new ByteArrayWrapper(b02);
        Map<ByteArrayWrapper, String> map = new HashMap<>();
        map.put(w01, "OK");
        int h01 = w01.hashCode();
        int h02 = w02.hashCode();
        System.out.println(w01 == w02);
        System.out.println(w01.equals(w02));
        System.out.println(h01 == h02);
        System.out.println(map.get(w01));
        System.out.println(map.get(w02));
    }

Result is···

false
true
true
OK
OK

It's natural because we call Arrays.equals and Arrays.hashCode in it.

修正版ByteArrayWrapper.java

(1/17 postscript) If you bring it from Stack Overflow, it's useless. Since it only holds the array as it is, modifying the array passed to the constructor will cause a malfunction. It's a big deal, so let's try it.

Main.java


    public static void main(String[] args) {
        byte[] b01 = { 1, 2, 3 };
        byte[] b02 = { 1, 2, 3 };
        byte[] b03 = { 9, 2, 3 };
        ByteArrayWrapper w01 = new ByteArrayWrapper(b01);
        ByteArrayWrapper w02 = new ByteArrayWrapper(b02);
        ByteArrayWrapper w03 = new ByteArrayWrapper(b03);
        Map<ByteArrayWrapper, String> map = new HashMap<>();
        map.put(w01, "OK");
        System.out.println(map.get(w01));
        b01[0] = 9;
        System.out.println(map.get(w01));
        System.out.println(map.get(w02));
        System.out.println(map.get(w03));
        System.out.println(map);
    }

When you run ...

OK
null
null
null
{ByteArrayWrapper@9669=OK}

The data is in the map, but I can't get it with the key. To prevent this, make a copy of the array in the constructor.

ByteArrayWrapper.java


import java.util.Arrays;

public class ByteArrayWrapper {
    private byte[] data;

    public ByteArrayWrapper(byte[] data) {
        this.data = data.clone();
    }

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

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

If you run the same test program ...

OK
OK
OK
null
{ByteArrayWrapper@7861=OK}

The arrangement inside is protected.

Part 2: Use ByteBuffer

There is a comment that you can go with java.nio.ByteBuffer without creating a wrapper class. Let's test it.

Main.java


    public static void main(String[] args) {
        byte[] b01 = { 1, 2, 3 };
        byte[] b02 = { 1, 2, 3 };
        ByteBuffer w01 = ByteBuffer.wrap(b01);
        ByteBuffer w02 = ByteBuffer.wrap(b02);
        Map<ByteBuffer, String> map = new HashMap<>();
        map.put(w01, "OK");
        int h01 = w01.hashCode();
        int h02 = w02.hashCode();
        System.out.println(w01 == w02);
        System.out.println(w01.equals(w02));
        System.out.println(h01 == h02);
        System.out.println(map.get(w01));
        System.out.println(map.get(w02));
    }

Result is···

false
true
true
OK
OK

OK at all. This is the best feeling. I like the standard library added from JDK 1.4.

Part 3: Use BigInteger → No

There is a comment that even java.math.BigInteger cannot go because the constructor receives byte []. However, when it is {0, 100}, it will be with {100}, right? There is a comment return. Let's test it.

Main.java


    public static void main(String[] args) {
        byte[] b01 = { 0, 100 };
        byte[] b02 = { 0, 100 };
        byte[] b03 = { 100 };
        BigInteger w01 = new BigInteger(b01);
        BigInteger w02 = new BigInteger(b02);
        BigInteger w03 = new BigInteger(b03);
        Map<BigInteger, String> map = new HashMap<>();
        map.put(w01, "OK");
        int h01 = w01.hashCode();
        int h02 = w02.hashCode();
        int h03 = w03.hashCode();
        System.out.println(w01 == w02);
        System.out.println(w01 == w03);
        System.out.println(w01.equals(w02));
        System.out.println(w01.equals(w03));
        System.out.println(h01 == h02);
        System.out.println(h01 == h03);
        System.out.println(map.get(w01));
        System.out.println(map.get(w02));
        System.out.println(map.get(w03));
    }

Result is···

false
false
true
true
true
true
OK
OK
OK

That's too bad. {0, 100} and {100} are together. It's dangerous if you use it without knowing it.

Part 4: Convert to a character string → Not efficient

String s01 = new String (b01, "UTF-8"); but it looks like garbled characters. String s02 = Arrays.toString (b01); would be legitimate, but the output string would be "[1, 2, 3]". I don't need parentheses or blanks. Therefore, create a utility method that outputs numerical values, numerical values, .... Convert the number to hexadecimal. Convert the number to 36 base. (Character.MAX_RADIX == 36) Base64 encode.

However, it is inefficient because the size is definitely larger than byte [].

Finally

I think ByteBuffer is fine, but if you want to use int [] instead of byte [], there is IntBuffer. Or rather, all primitive types are available. Various somehow Buffer. I did not know that.

Strictly not imuutable, but you wouldn't add it with put after specifying it as a key. (1/17 postscript) Looking at the javadoc and source of ByteBuffer, get () is also useless. This is because the current position moves and equals and hashCode target the current position and beyond. equals and hashCode are accessed by get (int). The following notes are written in ByteBuffer / hashCode.

The buffer hash code is content dependent. Avoid using the buffer as a key for hash maps or other data structures unless it is clear that the contents of the buffer will not change in the future.

But if you don't know how to use it outside, you can rest assured that you can't change it by creating ByteArrayWrapper in the usual way. (1/17 postscript) I'm sorry, the first ByteArray Wrapper I put out was not imuutable. Fixed.

ByteArrayWrapper that can be used with both HashMap and TreeMap

(1/18 postscript) I put it at the end of TreeMap, but it is a wrapper that can be used for both. Modified from the source in HashMap: Changed constructor argument from byte [] to byte ..., added equals (byte [] other), added compareTo ()

ByteArrayWrapper.java


import java.util.Arrays;

public class ByteArrayWrapper implements Comparable<ByteArrayWrapper> {
    private byte[] data;

    public ByteArrayWrapper(byte... data) {
        this.data = data.clone();
    }

    public boolean equals(Object other) {
        if (other instanceof ByteArrayWrapper) {
            return equals(((ByteArrayWrapper) other).data);
        }
        return false;
    }
    
    public boolean equals(byte[] other) {
        return Arrays.equals(data, other);
    }
    
    public int hashCode() {
        return Arrays.hashCode(data);
    }

    public int compareTo(ByteArrayWrapper that) {
        int n = Math.min(this.data.length, that.data.length);
        for (int i = 0; i < n; i++) {
            int cmp = Byte.compare(this.data[i], that.data[i]);
            if (cmp != 0)
                return cmp;
        }
        return this.data.length - that.data.length;
    }
}

Recommended Posts

How to use an array for HashMap keys
How to use an array for a TreeMap key
How to use binding.pry for view files
[Java] How to use the HashMap class
[Ruby] How to use slice for beginners
How to use Map
How to use rbenv
How to use letter_opener_web
How to use with_option
How to use fields_for
How to use map
[Java] How to turn a two-dimensional array with an extended for statement
How to use collection_select
How to create pagination for a "kaminari" array
How to use Twitter4J
How to use active_hash! !!
How to use MapStruct
How to use hidden_field_tag
How to use TreeSet
[How to use label]
How to use identity
How to use hashes
How to use JUnit 5
[Rails] How to use Gem'rails-i18n' for Japanese support
How to use nginx-ingress-controller with Docker for Mac
[For Rails beginners] Summary of how to use RSpec (get an overview)
How to use Dozer.mapper
How to use Gradle
[For super beginners] How to use autofocus: true
How to use org.immutables
How to use java.util.stream.Collector
How to use VisualVM
How to use Map
How to make a judgment method to search for an arbitrary character in an array
How to write to apply gem Pagy (pagination) to an array
How to output standard from an array with forEach
How to use Truth (assertion library for Java / Android)
[For those who create portfolios] How to use font-awesome-rails
How to use GitHub for super beginners (team development)
How to create an application
[Java] How to use Map
How to use Chain API
[Java] How to use Map
How to use Priority Queuing
[Rails] How to use enum
How to use java Optional
How to use JUnit (beginner)
[Rails] How to use enum
How to use @Builder (Lombok)
[Swift] How to use UserDefaults
How to use java class
How to use Swift UIScrollView
How to use Big Decimal
[Java] How to use Optional ②
[Java] How to use removeAll ()
How to use String [] args
[Java] How to use string.format
How to use rails join
How to use Java Map
Ruby: How to use cookies
How to use dependent :: destroy