From d6abac6e6f663be667b26b0cd134c1eeab5fa5db Mon Sep 17 00:00:00 2001 From: Rishabh Kumar Date: Tue, 17 Sep 2024 14:17:46 +0530 Subject: [PATCH] OAK-11073 : added ensureCapacity method to create hash based collections with expected size --- .../commons/collections/CollectionUtils.java | 51 ++++++++++++++++--- .../collections/CollectionUtilsTest.java | 22 +++++++- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/CollectionUtils.java b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/CollectionUtils.java index eebc3ac5eb1..4563f173c32 100644 --- a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/CollectionUtils.java +++ b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/CollectionUtils.java @@ -19,9 +19,11 @@ package org.apache.jackrabbit.oak.commons.collections; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @@ -34,6 +36,8 @@ */ public class CollectionUtils { + private static final int MAX_CAPACITY = 1 << 30; + private CollectionUtils() { // no instances for you } @@ -105,15 +109,28 @@ public static Set toSet(@NotNull final Iterator iterator) { public static Set toSet(@NotNull final T... elements) { Objects.requireNonNull(elements); // make sure the set does not need to be resized given the initial content - float loadFactor = (float) 0.75; // HashSet default - int initialCapacity = 1 + (int) (elements.length / loadFactor); - final Set result = new HashSet<>(initialCapacity, loadFactor); + final Set result = new HashSet<>(ensureCapacity(elements.length)); for (T element : elements) { result.add(element); } return result; } + /** + * Creates a new, empty HashMap with expected capacity. + *

+ * The returned map uses the default load factor of 0.75, and its capacity is + * large enough to add expected number of elements without resizing. + * + * @param capacity the expected number of elements + * @throws IllegalArgumentException if capacity is negative + */ + @NotNull + public static Map newHashMap(final int capacity) { + // make sure the set does not need to be resized given the initial content + return new HashMap<>(ensureCapacity(capacity)); + } + /** * Convert an {@code Iterator} to an {@code Iterable}. *

@@ -131,12 +148,12 @@ public static Set toSet(@NotNull final T... elements) { public static Iterable toIterable(@NotNull final Iterator iterator) { Objects.requireNonNull(iterator); - Iterable delegate = new Iterable() { + return new Iterable<>() { private boolean consumed = false; @Override - public Iterator iterator() { + public @NotNull Iterator iterator() { if (consumed) { throw new IllegalStateException("Iterator already returned once"); } else { @@ -145,8 +162,6 @@ public Iterator iterator() { } } }; - - return delegate; } /** @@ -170,7 +185,27 @@ public static Stream toStream(@NotNull Iterable iterable) { * iterator to convert * @return the stream (representing the remaining elements in the iterator) */ - public static Stream toStream(Iterator iterator) { + @NotNull + public static Stream toStream(@NotNull Iterator iterator) { return StreamSupport.stream(toIterable(iterator).spliterator(), false); } + + /** + * Ensure the capacity of a map or set given the expected number of elements. + * + * @param capacity the expected number of elements + * @return the capacity to use to avoid rehashing & collisions + */ + static int ensureCapacity(final int capacity) { + + if (capacity < 0) { + throw new IllegalArgumentException("Capacity must be non-negative"); + } + + if (capacity > MAX_CAPACITY) { + return MAX_CAPACITY; + } + + return 1 + (int) (capacity / 0.75f); + } } \ No newline at end of file diff --git a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/CollectionUtilsTest.java b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/CollectionUtilsTest.java index 4722d04d3e1..6b943c02bc1 100644 --- a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/CollectionUtilsTest.java +++ b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/CollectionUtilsTest.java @@ -30,6 +30,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.junit.Assert.fail; + public class CollectionUtilsTest { final List data = Arrays.asList("one", "two", "three", null); @@ -98,7 +100,7 @@ public void iteratorToIIteratable() { Assert.assertFalse(testit.hasNext()); try { testit = iterable.iterator(); - Assert.fail("should only work once"); + fail("should only work once"); } catch (IllegalStateException expected) { // that's what we want } @@ -112,4 +114,22 @@ public void iteratorToStream() { List result = stream.collect(Collectors.toList()); Assert.assertEquals(input.toString(), result.toString()); } + + @Test + public void ensureCapacity() { + int capacity = CollectionUtils.ensureCapacity(8); + Assert.assertEquals(11, capacity); + } + + @Test + public void ensureCapacityWithMaxValue() { + int capacity = CollectionUtils.ensureCapacity(1073741825); + Assert.assertEquals(1073741824, capacity); + } + + @Test(expected = IllegalArgumentException.class) + public void ensureCapacityWithNegativeValue() { + int capacity = CollectionUtils.ensureCapacity(-8); + fail("Should throw IllegalArgumentException"); + } } \ No newline at end of file