Skip to content

Commit c3d5b17

Browse files
lowasserGoogle Java Core Libraries
authored and
Google Java Core Libraries
committed
Allow size hinting for ImmutableMultimap and subtypes.
RELNOTES=Add ImmutableMultimap.builderWithExpectedKeys and ImmutableMultimap.Builder.expectedValuesPerKey. PiperOrigin-RevId: 660016290
1 parent 91b6ebe commit c3d5b17

19 files changed

+664
-17
lines changed

android/guava-tests/test/com/google/common/collect/ImmutableListMultimapTest.java

+45
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,51 @@ public static Test suite() {
8686
return suite;
8787
}
8888

89+
public void testBuilderWithExpectedKeysNegative() {
90+
try {
91+
ImmutableListMultimap.builderWithExpectedKeys(-1);
92+
fail("Expected IllegalArgumentException");
93+
} catch (IllegalArgumentException expected) {
94+
}
95+
}
96+
97+
public void testBuilderWithExpectedKeysZero() {
98+
ImmutableListMultimap.Builder<String, String> builder =
99+
ImmutableListMultimap.builderWithExpectedKeys(0);
100+
builder.put("key", "value");
101+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
102+
}
103+
104+
public void testBuilderWithExpectedKeysPositive() {
105+
ImmutableListMultimap.Builder<String, String> builder =
106+
ImmutableListMultimap.builderWithExpectedKeys(1);
107+
builder.put("key", "value");
108+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
109+
}
110+
111+
public void testBuilderWithExpectedValuesPerKeyNegative() {
112+
ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
113+
try {
114+
builder.expectedValuesPerKey(-1);
115+
fail("Expected IllegalArgumentException");
116+
} catch (IllegalArgumentException expected) {
117+
}
118+
}
119+
120+
public void testBuilderWithExpectedValuesPerKeyZero() {
121+
ImmutableListMultimap.Builder<String, String> builder =
122+
ImmutableListMultimap.<String, String>builder().expectedValuesPerKey(0);
123+
builder.put("key", "value");
124+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
125+
}
126+
127+
public void testBuilderWithExpectedValuesPerKeyPositive() {
128+
ImmutableListMultimap.Builder<String, String> builder =
129+
ImmutableListMultimap.<String, String>builder().expectedValuesPerKey(1);
130+
builder.put("key", "value");
131+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
132+
}
133+
89134
public void testBuilder_withImmutableEntry() {
90135
ImmutableListMultimap<String, Integer> multimap =
91136
new Builder<String, Integer>().put(Maps.immutableEntry("one", 1)).build();

android/guava-tests/test/com/google/common/collect/ImmutableMultimapTest.java

+46
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.google.common.collect;
1818

19+
import static com.google.common.truth.Truth.assertThat;
20+
1921
import com.google.common.annotations.GwtCompatible;
2022
import com.google.common.annotations.GwtIncompatible;
2123
import com.google.common.annotations.J2ktIncompatible;
@@ -60,6 +62,50 @@ public void testBuilder_withImmutableEntryAndNullContents() {
6062
}
6163
}
6264

65+
public void testBuilderWithExpectedKeysNegative() {
66+
try {
67+
ImmutableMultimap.builderWithExpectedKeys(-1);
68+
fail("Expected IllegalArgumentException");
69+
} catch (IllegalArgumentException expected) {
70+
}
71+
}
72+
73+
public void testBuilderWithExpectedKeysZero() {
74+
ImmutableMultimap.Builder<String, String> builder =
75+
ImmutableMultimap.builderWithExpectedKeys(0);
76+
builder.put("key", "value");
77+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
78+
}
79+
80+
public void testBuilderWithExpectedKeysPositive() {
81+
ImmutableMultimap.Builder<String, String> builder =
82+
ImmutableMultimap.builderWithExpectedKeys(1);
83+
builder.put("key", "value");
84+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
85+
}
86+
87+
public void testBuilderWithExpectedValuesPerKeyNegative() {
88+
try {
89+
ImmutableMultimap.builder().expectedValuesPerKey(-1);
90+
fail("Expected IllegalArgumentException");
91+
} catch (IllegalArgumentException expected) {
92+
}
93+
}
94+
95+
public void testBuilderWithExpectedValuesPerKeyZero() {
96+
ImmutableMultimap.Builder<String, String> builder =
97+
ImmutableMultimap.<String, String>builder().expectedValuesPerKey(0);
98+
builder.put("key", "value");
99+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
100+
}
101+
102+
public void testBuilderWithExpectedValuesPerKeyPositive() {
103+
ImmutableMultimap.Builder<String, String> builder =
104+
ImmutableMultimap.<String, String>builder().expectedValuesPerKey(1);
105+
builder.put("key", "value");
106+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
107+
}
108+
63109
private static class StringHolder {
64110
@Nullable String string;
65111
}

android/guava-tests/test/com/google/common/collect/ImmutableSetMultimapTest.java

+73
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,79 @@ public static Test suite() {
8686
return suite;
8787
}
8888

89+
public void testBuilderWithExpectedKeysNegative() {
90+
try {
91+
ImmutableSetMultimap.builderWithExpectedKeys(-1);
92+
fail("Expected IllegalArgumentException");
93+
} catch (IllegalArgumentException expected) {
94+
}
95+
}
96+
97+
public void testBuilderWithExpectedKeysZero() {
98+
ImmutableSetMultimap.Builder<String, String> builder =
99+
ImmutableSetMultimap.builderWithExpectedKeys(0);
100+
builder.put("key", "value");
101+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
102+
}
103+
104+
public void testBuilderWithExpectedKeysPositive() {
105+
ImmutableSetMultimap.Builder<String, String> builder =
106+
ImmutableSetMultimap.builderWithExpectedKeys(1);
107+
builder.put("key", "value");
108+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
109+
}
110+
111+
public void testBuilderWithExpectedValuesPerKeyNegative() {
112+
ImmutableSetMultimap.Builder<String, String> builder = ImmutableSetMultimap.builder();
113+
try {
114+
builder.expectedValuesPerKey(-1);
115+
fail("Expected IllegalArgumentException");
116+
} catch (IllegalArgumentException expected) {
117+
}
118+
}
119+
120+
public void testBuilderWithExpectedValuesPerKeyZero() {
121+
ImmutableSetMultimap.Builder<String, String> builder =
122+
ImmutableSetMultimap.<String, String>builder().expectedValuesPerKey(0);
123+
builder.put("key", "value");
124+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
125+
}
126+
127+
public void testBuilderWithExpectedValuesPerKeyPositive() {
128+
ImmutableSetMultimap.Builder<String, String> builder =
129+
ImmutableSetMultimap.<String, String>builder().expectedValuesPerKey(1);
130+
builder.put("key", "value");
131+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
132+
}
133+
134+
public void testBuilderWithExpectedValuesPerKeyNegativeOrderValuesBy() {
135+
ImmutableSetMultimap.Builder<String, String> builder =
136+
ImmutableSetMultimap.<String, String>builder().orderValuesBy(Ordering.natural());
137+
try {
138+
builder.expectedValuesPerKey(-1);
139+
fail("Expected IllegalArgumentException");
140+
} catch (IllegalArgumentException expected) {
141+
}
142+
}
143+
144+
public void testBuilderWithExpectedValuesPerKeyZeroOrderValuesBy() {
145+
ImmutableSetMultimap.Builder<String, String> builder =
146+
ImmutableSetMultimap.<String, String>builder()
147+
.orderValuesBy(Ordering.natural())
148+
.expectedValuesPerKey(0);
149+
builder.put("key", "value");
150+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
151+
}
152+
153+
public void testBuilderWithExpectedValuesPerKeyPositiveOrderValuesBy() {
154+
ImmutableSetMultimap.Builder<String, String> builder =
155+
ImmutableSetMultimap.<String, String>builder()
156+
.orderValuesBy(Ordering.natural())
157+
.expectedValuesPerKey(1);
158+
builder.put("key", "value");
159+
assertThat(builder.build().entries()).containsExactly(Maps.immutableEntry("key", "value"));
160+
}
161+
89162
public void testBuilder_withImmutableEntry() {
90163
ImmutableSetMultimap<String, Integer> multimap =
91164
new Builder<String, Integer>().put(Maps.immutableEntry("one", 1)).build();

android/guava/src/com/google/common/collect/ImmutableListMultimap.java

+31
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.common.collect;
1818

19+
import static com.google.common.collect.CollectPreconditions.checkNonnegative;
1920
import static java.util.Objects.requireNonNull;
2021

2122
import com.google.common.annotations.GwtCompatible;
@@ -198,6 +199,19 @@ public static <K, V> Builder<K, V> builder() {
198199
return new Builder<>();
199200
}
200201

202+
/**
203+
* Returns a new builder with a hint for how many distinct keys are expected to be added. The
204+
* generated builder is equivalent to that returned by {@link #builder}, but may perform better if
205+
* {@code expectedKeys} is a good estimate.
206+
*
207+
* @throws IllegalArgumentException if {@code expectedKeys} is negative
208+
* @since NEXT
209+
*/
210+
public static <K, V> Builder<K, V> builderWithExpectedKeys(int expectedKeys) {
211+
checkNonnegative(expectedKeys, "expectedKeys");
212+
return new Builder<>(expectedKeys);
213+
}
214+
201215
/**
202216
* A builder for creating immutable {@code ListMultimap} instances, especially {@code public
203217
* static final} multimaps ("constant multimaps"). Example:
@@ -224,6 +238,23 @@ public static final class Builder<K, V> extends ImmutableMultimap.Builder<K, V>
224238
*/
225239
public Builder() {}
226240

241+
/** Creates a new builder with a hint for the number of distinct keys. */
242+
Builder(int expectedKeys) {
243+
super(expectedKeys);
244+
}
245+
246+
/**
247+
* {@inheritDoc}
248+
*
249+
* @since NEXT
250+
*/
251+
@CanIgnoreReturnValue
252+
@Override
253+
public Builder<K, V> expectedValuesPerKey(int expectedValuesPerKey) {
254+
super.expectedValuesPerKey(expectedValuesPerKey);
255+
return this;
256+
}
257+
227258
@CanIgnoreReturnValue
228259
@Override
229260
public Builder<K, V> put(K key, V value) {

android/guava/src/com/google/common/collect/ImmutableMultimap.java

+67-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
2020
import static com.google.common.collect.CollectPreconditions.checkEntryNotNull;
21+
import static com.google.common.collect.CollectPreconditions.checkNonnegative;
2122
import static com.google.common.collect.Maps.immutableEntry;
2223
import static java.util.Objects.requireNonNull;
2324

@@ -127,6 +128,19 @@ public static <K, V> Builder<K, V> builder() {
127128
return new Builder<>();
128129
}
129130

131+
/**
132+
* Returns a new builder with a hint for how many distinct keys are expected to be added. The
133+
* generated builder is equivalent to that returned by {@link #builder}, but may perform better if
134+
* {@code expectedKeys} is a good estimate.
135+
*
136+
* @throws IllegalArgumentException if {@code expectedKeys} is negative
137+
* @since NEXT
138+
*/
139+
public static <K, V> Builder<K, V> builderWithExpectedKeys(int expectedKeys) {
140+
checkNonnegative(expectedKeys, "expectedKeys");
141+
return new Builder<>(expectedKeys);
142+
}
143+
130144
/**
131145
* A builder for creating immutable multimap instances, especially {@code public static final}
132146
* multimaps ("constant multimaps"). Example:
@@ -151,13 +165,22 @@ public static class Builder<K, V> {
151165
@CheckForNull Map<K, ImmutableCollection.Builder<V>> builderMap;
152166
@CheckForNull Comparator<? super K> keyComparator;
153167
@CheckForNull Comparator<? super V> valueComparator;
168+
int expectedValuesPerKey = ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY;
154169

155170
/**
156171
* Creates a new builder. The returned builder is equivalent to the builder generated by {@link
157172
* ImmutableMultimap#builder}.
158173
*/
159174
public Builder() {}
160175

176+
/** Creates a new builder with a hint for the number of distinct keys. */
177+
Builder(int expectedKeys) {
178+
if (expectedKeys > 0) {
179+
builderMap = Platform.preservesInsertionOrderOnPutsMapWithExpectedSize(expectedKeys);
180+
}
181+
// otherwise, leave it null to be constructed lazily
182+
}
183+
161184
Map<K, ImmutableCollection.Builder<V>> ensureBuilderMapNonNull() {
162185
Map<K, ImmutableCollection.Builder<V>> result = builderMap;
163186
if (result == null) {
@@ -167,8 +190,46 @@ Map<K, ImmutableCollection.Builder<V>> ensureBuilderMapNonNull() {
167190
return result;
168191
}
169192

170-
ImmutableCollection.Builder<V> newValueCollectionBuilder() {
171-
return ImmutableList.builder();
193+
ImmutableCollection.Builder<V> newValueCollectionBuilderWithExpectedSize(int expectedSize) {
194+
return ImmutableList.builderWithExpectedSize(expectedSize);
195+
}
196+
197+
/**
198+
* Provides a hint for how many values will be associated with each key newly added to the
199+
* builder after this call. This does not change semantics, but may improve performance if
200+
* {@code expectedValuesPerKey} is a good estimate.
201+
*
202+
* <p>This may be called more than once; each newly added key will use the most recent call to
203+
* {@link #expectedValuesPerKey} as its hint.
204+
*
205+
* @throws IllegalArgumentException if {@code expectedValuesPerKey} is negative
206+
* @since NEXT
207+
*/
208+
@CanIgnoreReturnValue
209+
public Builder<K, V> expectedValuesPerKey(int expectedValuesPerKey) {
210+
checkNonnegative(expectedValuesPerKey, "expectedValuesPerKey");
211+
212+
// Always presize to at least 1, since we only bother creating a value collection if there's
213+
// at least one element.
214+
this.expectedValuesPerKey = Math.max(expectedValuesPerKey, 1);
215+
216+
return this;
217+
}
218+
219+
/**
220+
* By default, if we are handed a value collection bigger than expectedValuesPerKey, presize to
221+
* accept that many elements.
222+
*
223+
* <p>This gets overridden in ImmutableSetMultimap.Builder to only trust the size of {@code
224+
* values} if it is a Set and therefore probably already deduplicated.
225+
*/
226+
int expectedValueCollectionSize(int defaultExpectedValues, Iterable<?> values) {
227+
if (values instanceof Collection<?>) {
228+
Collection<?> collection = (Collection<?>) values;
229+
return Math.max(defaultExpectedValues, collection.size());
230+
} else {
231+
return defaultExpectedValues;
232+
}
172233
}
173234

174235
/** Adds a key-value mapping to the built multimap. */
@@ -177,7 +238,7 @@ public Builder<K, V> put(K key, V value) {
177238
checkEntryNotNull(key, value);
178239
ImmutableCollection.Builder<V> valuesBuilder = ensureBuilderMapNonNull().get(key);
179240
if (valuesBuilder == null) {
180-
valuesBuilder = newValueCollectionBuilder();
241+
valuesBuilder = newValueCollectionBuilderWithExpectedSize(expectedValuesPerKey);
181242
ensureBuilderMapNonNull().put(key, valuesBuilder);
182243
}
183244
valuesBuilder.add(value);
@@ -224,7 +285,9 @@ public Builder<K, V> putAll(K key, Iterable<? extends V> values) {
224285
}
225286
ImmutableCollection.Builder<V> valuesBuilder = ensureBuilderMapNonNull().get(key);
226287
if (valuesBuilder == null) {
227-
valuesBuilder = newValueCollectionBuilder();
288+
valuesBuilder =
289+
newValueCollectionBuilderWithExpectedSize(
290+
expectedValueCollectionSize(expectedValuesPerKey, values));
228291
ensureBuilderMapNonNull().put(key, valuesBuilder);
229292
}
230293
while (valuesItr.hasNext()) {

0 commit comments

Comments
 (0)