From ba83b760d370fafb428e96b2346eb116cbacd028 Mon Sep 17 00:00:00 2001 From: mdabrowski-eu <57441874+mdabrowski-eu@users.noreply.github.com> Date: Sun, 14 Feb 2021 16:51:51 +0100 Subject: [PATCH] BAEL-4716 HashMap optimization (#10479) --- .../java/com/baeldung/map/hashing/Member.java | 6 ++ .../map/hashing/MemberWithBadHashing.java | 8 ++ .../map/hashing/MemberWithGuavaHashing.java | 18 +++++ .../baeldung/map/hashing/MemberWithId.java | 18 +++++ .../map/hashing/MemberWithIdAndName.java | 23 ++++++ .../map/hashing/MemberWithObjects.java | 19 +++++ .../baeldung/map/hashing/HashingUnitTest.java | 76 +++++++++++++++++++ 7 files changed, 168 insertions(+) create mode 100644 java-collections-maps-3/src/main/java/com/baeldung/map/hashing/Member.java create mode 100644 java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithBadHashing.java create mode 100644 java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithGuavaHashing.java create mode 100644 java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithId.java create mode 100644 java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithIdAndName.java create mode 100644 java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithObjects.java create mode 100644 java-collections-maps-3/src/test/java/com/baeldung/map/hashing/HashingUnitTest.java diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/Member.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/Member.java new file mode 100644 index 0000000000..22b6a61d4d --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/Member.java @@ -0,0 +1,6 @@ +package com.baeldung.map.hashing; + +class Member { + Integer id; + String name; +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithBadHashing.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithBadHashing.java new file mode 100644 index 0000000000..2a69291742 --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithBadHashing.java @@ -0,0 +1,8 @@ +package com.baeldung.map.hashing; + +public class MemberWithBadHashing extends Member { + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithGuavaHashing.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithGuavaHashing.java new file mode 100644 index 0000000000..bb33ace0b7 --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithGuavaHashing.java @@ -0,0 +1,18 @@ +package com.baeldung.map.hashing; + +import com.google.common.base.Charsets; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + +public class MemberWithGuavaHashing extends Member { + @Override + public int hashCode() { + HashFunction hashFunction = Hashing.murmur3_32(); + return hashFunction.newHasher() + .putInt(id) + .putString(name, Charsets.UTF_8) + .hash().hashCode(); + } + + +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithId.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithId.java new file mode 100644 index 0000000000..5d82eb0cb7 --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithId.java @@ -0,0 +1,18 @@ +package com.baeldung.map.hashing; + +public class MemberWithId extends Member { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MemberWithId that = (MemberWithId) o; + + return id.equals(that.id); + } + + @Override + public int hashCode() { + return id; + } +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithIdAndName.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithIdAndName.java new file mode 100644 index 0000000000..81ae48cf55 --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithIdAndName.java @@ -0,0 +1,23 @@ +package com.baeldung.map.hashing; + +import java.util.Objects; + +public class MemberWithIdAndName extends Member { + public static final int PRIME = 31; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MemberWithObjects that = (MemberWithObjects) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + int result = id.hashCode(); + result = PRIME * result + (name == null ? 0 : name.hashCode()); + return result; + } +} diff --git a/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithObjects.java b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithObjects.java new file mode 100644 index 0000000000..b641035e4f --- /dev/null +++ b/java-collections-maps-3/src/main/java/com/baeldung/map/hashing/MemberWithObjects.java @@ -0,0 +1,19 @@ +package com.baeldung.map.hashing; + +import java.util.Objects; + +public class MemberWithObjects extends Member { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MemberWithObjects that = (MemberWithObjects) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } +} diff --git a/java-collections-maps-3/src/test/java/com/baeldung/map/hashing/HashingUnitTest.java b/java-collections-maps-3/src/test/java/com/baeldung/map/hashing/HashingUnitTest.java new file mode 100644 index 0000000000..d28a3b50b5 --- /dev/null +++ b/java-collections-maps-3/src/test/java/com/baeldung/map/hashing/HashingUnitTest.java @@ -0,0 +1,76 @@ +package com.baeldung.map.hashing; + +import com.google.common.base.Stopwatch; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.HashMap; +import java.util.SplittableRandom; +import java.util.function.Supplier; + +public class HashingUnitTest { + + public static final int SAMPLES = 1000000; + private SplittableRandom random = new SplittableRandom(); + + private String[] names = {"John", "Adam", "Suzie"}; + + @Test + void givenPrimitiveByteArrayKey_whenRetrievingFromMap_shouldRetrieveDifferentObjects() { + // bad hashing example is prohibitively slow for bigger samples +// Duration[] badHashing = testDuration(MemberWithBadHashing::new); + Duration[] withId = testDuration(MemberWithId::new); + Duration[] withObjects = testDuration(MemberWithObjects::new); + Duration[] withIdAndName = testDuration(MemberWithIdAndName::new); + +// System.out.println("Inserting with bad hashing:"); +// System.out.println(badHashing[0]); +// System.out.println("Getting with bad hashing:"); +// System.out.println(badHashing[1]); + + System.out.println("Inserting with id hashing:"); + System.out.println(withId[0]); + System.out.println("Getting with id hashing:"); + System.out.println(withId[1]); + + System.out.println("Inserting with id and name hashing:"); + System.out.println(withIdAndName[0]); + System.out.println("Getting with id and name hashing:"); + System.out.println(withIdAndName[1]); + + System.out.println("Inserting with Objects hashing:"); + System.out.println(withObjects[0]); + System.out.println("Getting with Objects hashing:"); + System.out.println(withObjects[1]); + } + + private String randomName() { + return names[random.nextInt(2)]; + } + + private Duration[] testDuration(Supplier factory) { + HashMap map = new HashMap<>(); + Stopwatch stopwatch = Stopwatch.createUnstarted(); + + stopwatch.start(); + for(int i = 0; i < SAMPLES; i++) { + T member = factory.get(); + member.id = i; + member.name = randomName(); + map.put(member, member.name); + } + stopwatch.stop(); + Duration elapsedInserting = stopwatch.elapsed(); + stopwatch.reset(); + + stopwatch.start(); + for (T key : map.keySet()) { + map.get(key); + } + stopwatch.stop(); + Duration elapsedGetting = stopwatch.elapsed(); + stopwatch.reset(); + + return new Duration[]{elapsedInserting, elapsedGetting}; + } +}