All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.timeandspace.smoothie.SmoothieMapBuilder Maven / Gradle / Ivy

/*
 * Copyright (C) The SmoothieMap Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.timeandspace.smoothie;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.timeandspace.collect.Equivalence;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.Contract;

import java.util.function.Supplier;
import java.util.function.ToLongFunction;

import static io.timeandspace.smoothie.Utils.checkNonNegative;
import static io.timeandspace.smoothie.Utils.checkNonNull;

/**
 * SmoothieMapBuilder is used to configure and create {@link SmoothieMap}s. A new builder could be
 * created via {@link SmoothieMap#newBuilder()} method. A SmoothieMap is created using {@link
 * #build()} method.
 *
 * 

SmoothieMapBuilder is mutable: all its configuration methods change its state, and return the * receiver builder object to enable the "fluent builder" pattern: *

{@code
 * SmoothieMap.newBuilder().expectedSize(100_000).doShrink(false).build();
 * }
* *

SmoothieMapBuilder could be used to create any number of SmoothieMap objects. Created * SmoothieMaps don't retain a link to the builder and therefore don't depend on any possible * subsequent modifications of the builder's state. * * @param the type of keys in created SmoothieMaps * @param the type of values in created SmoothieMaps */ public final class SmoothieMapBuilder { static final long UNKNOWN_SIZE = Long.MAX_VALUE; private static final double MAX_EXPECTED_SIZE_ERROR_FRACTION = 0.05; @Contract(value = " -> new", pure = true) public static SmoothieMapBuilder create() { return new SmoothieMapBuilder<>(); } /** Mirror field: {@link SmoothieMap#allocateIntermediateSegments}. */ private boolean allocateIntermediateCapacitySegments; /** Mirror field: {@link SmoothieMap#splitBetweenTwoNewSegments}. */ private boolean splitBetweenTwoNewSegments; /** Mirror field: {@link SmoothieMap#doShrink}. */ private boolean doShrink; private Equivalence keyEquivalence = Equivalence.defaultEquality(); private @Nullable Supplier> keyHashFunctionFactory = null; private Equivalence valueEquivalence = Equivalence.defaultEquality(); private long expectedSize = UNKNOWN_SIZE; private long minPeakSize = UNKNOWN_SIZE; private SmoothieMapBuilder() { defaultOptimizationConfiguration(); } /** * Specifies whether SmoothieMaps created using this builder should operate in the "low-garbage" * mode (if {@link OptimizationObjective#LOW_GARBAGE} is passed into this method) or the * "footprint" mode (if {@link OptimizationObjective#FOOTPRINT} is passed into this method). See * the documentation for these enum constants for more details about the modes. * *

By default, SmoothieMaps operate in the "mixed" mode which is a compromise between the * "low-garbage" and the "footprint" modes. Calling {@link #defaultOptimizationConfiguration()} * configures this mode explicitly. * * @param optimizationObjective the primary optimization objective for created SmoothieMaps * @return this builder back * @throws NullPointerException if the provided optimization objective is null * @see #defaultOptimizationConfiguration */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder optimizeFor(OptimizationObjective optimizationObjective) { Utils.checkNonNull(optimizationObjective); switch (optimizationObjective) { case LOW_GARBAGE: this.allocateIntermediateCapacitySegments = false; this.splitBetweenTwoNewSegments = false; this.doShrink = false; break; case FOOTPRINT: this.allocateIntermediateCapacitySegments = true; this.splitBetweenTwoNewSegments = true; this.doShrink = true; break; default: throw new AssertionError("Unknown OptimizationObjective: " + optimizationObjective); } return this; } /** * Specifies that SmoothieMaps created using this builder should operate in "mixed" mode which * is a compromise between {@link OptimizationObjective#FOOTPRINT} and {@link * OptimizationObjective#LOW_GARBAGE}. * * @implSpec the "mixed" mode includes {@linkplain * #allocateIntermediateCapacitySegments(boolean) allocating intermediate-capacity segments} and * {@linkplain #doShrink(boolean) turning SmoothieMap shrinking on}, but doesn't include * {@linkplain #splitBetweenTwoNewSegments(boolean) splitting segments between two new ones}. * * @return this builder back * @see #optimizeFor */ @CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder defaultOptimizationConfiguration() { this.allocateIntermediateCapacitySegments = true; this.splitBetweenTwoNewSegments = false; this.doShrink = true; return this; } /** * Specifies whether during the growth of SmoothieMaps created with this builder they should * first allocate intermediate-capacity segments and then reallocate them as full-capacity * segments when needed, or allocate full-capacity segments right away. * *

SmoothieMap stores the entries in small segments which are mini hash tables on * their own. This hash table becomes full when the number of entries stored in it exceeds N. * At this point, a segment is split into two parts. At least one new segment should be * allocated upon a split to become the second part (or two new segments, if configured via * {@link #splitBetweenTwoNewSegments(boolean)}). The population of each of these two parts just * after the split is approximately N/2, subject to some variability (while their joint * population is N + 1). The entry storage capacity of segments is decoupled from the hash * table, so a newly allocated segment may have entry storage capacity smaller than N. A segment * of storage capacity of approximately (2/3) * N is called an intermediate-capacity * segment, and a segment of storage capacity N is called a full-capacity segment. * *

Thus, upon a segment split, a newly allocated intermediate-capacity segment is on average * 75% full in terms of the entry storage capacity, while a newly allocated full-capacity * segment is on average 50% full. When the number of entries stored in an intermediate-capacity * segment grows beyond its storage capacity (approximately, (2/3) * N), it's reallocated * in place as a full-capacity segment. * *

Thus, intermediate-capacity segments reduce the total SmoothieMap's footprint per stored * entry as well as the variability of the footprint at different SmoothieMap's sizes. The * drawbacks of intermediate-capacity segments are: *

    *
  • They are transient: being allocated and reclaimed during the growth of a SmoothieMap. * The total amount of garbage produced by a SmoothieMap instance (assuming it grows from * size 0 and neither {@link #expectedSize(long)} nor {@link #minPeakSize(long)} was * configured) becomes comparable with the SmoothieMap's footprint, though still lower than * the total amount of garbage produced by a typical open-addressing hash table * implementation such as {@link java.util.IdentityHashMap}, and comparable to the the total * amount of garbage produced by entry-based hash maps ({@link java.util.HashMap} and {@link * java.util.concurrent.ConcurrentHashMap}) without pre-configured capacity and assuming * that entries are not removed from them.
  • *
  • Reallocation of intermediate-capacity segments into full-capacity segments * contributes the amortized cost of inserting entries into a SmoothieMap.
  • *
  • The mix of full-capacity and intermediate-capacity segments precludes certain * variables to be hard-coded and generally adds some unpredictability (from the CPU * perspective) to SmoothieMap's key lookup and insertion operations which might slower them * relative to the setup where only full-capacity segments are used.
  • *
* *

By default, intermediate-capacity segments are allocated as a part of {@linkplain * #defaultOptimizationConfiguration() the "mixed" mode (the default mode)} of SmoothieMap's * operation. Intermediate-capacity segments are allocated in the "mixed" and the {@linkplain * OptimizationObjective#FOOTPRINT "footprint"} modes, but are not allocated in the {@linkplain * OptimizationObjective#LOW_GARBAGE "low-garbage"} mode. * *

Disabling allocating intermediate-capacity segments (that is, passing {@code false} into * this method) also implicitly disables {@linkplain #splitBetweenTwoNewSegments(boolean) * splitting between two new segments} because it doesn't make sense to allocate two new * full-capacity segments and move entries there while we already have one full-capacity segment * at hand (the old one). * * @param allocateIntermediateCapacitySegments whether the created SmoothieMaps should allocate * intermediate-capacity segments during the growth * @return this builder back * @see #splitBetweenTwoNewSegments(boolean) */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder allocateIntermediateCapacitySegments( boolean allocateIntermediateCapacitySegments) { if (!allocateIntermediateCapacitySegments) { splitBetweenTwoNewSegments(false); } this.allocateIntermediateCapacitySegments = allocateIntermediateCapacitySegments; return this; } /** * Specifies whether when segments are split in SmoothieMaps created with this builder, the * entries from the segment being split should be moved into two new intermediate-capacity * segments or the entries should be distributed between the old segment one newly allocated * segment (full-capacity or intermediate-capacity). * *

See the description of the model of segments and the definitions of * intermediate-capacity and full-capacity segments in the documentation for * {@link #allocateIntermediateCapacitySegments(boolean)}. Moving entries from the old segment * into two newly allocated intermediate-capacity segments means that just after the split they * are jointly 75% full in terms of the entry storage capacity. Both newly allocated segments * may then be reallocated as full-capacity segments when needed. One full-capacity segment and * one newly allocated intermediate-capacity segment are jointly 60% full. Two full-capacity * segments, the old one and a newly allocated one (if the allocation of intermediate-capacity * segments is turned off) are jointly 50% full. * *

Thus, splitting between two newly allocated segments lowers the footprint per entry and * the footprint variability at different SmoothieMap's sizes as much as possible. The downside * of this strategy is that during SmoothieMap's growth, one full-capacity and two * intermediate-capacity segments are allocated and then dropped per every 2 * N entries (given * that a segment's hash table capacity is N) every time the map doubles in size. This is a * significant rate of heap memory churn, exceeding that of typical open-addressing hash table * implementations such as {@link java.util.IdentityHashMap} and entry-based hash maps: {@link * java.util.HashMap} or {@link java.util.concurrent.ConcurrentHashMap}. * *

By default, during a split, entries are distributed between the old (full-capacity) * segment and a newly allocated intermediate-capacity segment, as per {@linkplain * #defaultOptimizationConfiguration() the "mixed" mode (the default mode)} of SmoothieMap's * operation, that is, by default, splitting is not done between two newly allocated * segments. Splitting between two new segments is not done in the {@linkplain * OptimizationObjective#LOW_GARBAGE "low-garbage"} and the "mixed" modes. It's enabled only in * the {@linkplain OptimizationObjective#FOOTPRINT "footprint"} mode. * *

Enabling splitting between two new segments also implicitly enables {@linkplain * #allocateIntermediateCapacitySegments(boolean) allocation of intermediate-capacity segments} * because the former doesn't make sense without the latter. * * @param splitBetweenTwoNewSegments whether created SmoothieMaps should move entries from * full-capacity segments into two newly allocated intermediate-capacity segments during * growth * @return this builder back * @see #allocateIntermediateCapacitySegments(boolean) * @see OptimizationObjective#FOOTPRINT */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder splitBetweenTwoNewSegments(boolean splitBetweenTwoNewSegments) { if (splitBetweenTwoNewSegments) { allocateIntermediateCapacitySegments(true); } this.splitBetweenTwoNewSegments = splitBetweenTwoNewSegments; return this; } /** * Specifies whether SmoothieMaps created with this builder should automatically shrink, i. e. * reclaim the allocated heap space when they reduce in size. * *

By default, shrinking is turned on as a part of {@linkplain * #defaultOptimizationConfiguration() the "mixed" mode (the default mode)} of SmoothieMap's * operation. The "mixed" and the {@linkplain OptimizationObjective#FOOTPRINT "footprint"} modes * turn shrinking on, but the {@linkplain OptimizationObjective#LOW_GARBAGE "low-garbage"} mode * turns shrinking off. * *

Automatic shrinking creates some low-volume stream of allocations and reclamations of * heap memory objects when entries are dynamically put into and removed from the SmoothieMap * even if the number of entries in the map remains relatively stable during this process, * though at a much lower rate than garbage is produced by entry-based Map implementations such * as {@link java.util.HashMap}, {@link java.util.TreeMap}, or {@link * java.util.concurrent.ConcurrentHashMap} during a similar process. * * @param doShrink whether the created SmoothieMaps should automatically reclaim memory when * they reduce in size * @return this builder back */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder doShrink(boolean doShrink) { this.doShrink = doShrink; return this; } /** * Sets the {@link Equivalence} {@linkplain io.timeandspace.collect.map.ObjObjMap#keyEquivalence * used for comparing keys} in SmoothieMaps created with this builder to the given equivalence. * * @param keyEquivalence the key equivalence to use in created SmoothieMaps * @return this builder back * @throws NullPointerException if the given equivalence object is null * @see #defaultKeyEquivalence() * @see io.timeandspace.collect.map.ObjObjMap#keyEquivalence * @see #valueEquivalence(Equivalence) */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder keyEquivalence(Equivalence keyEquivalence) { checkNonNull(keyEquivalence); this.keyEquivalence = keyEquivalence; return this; } /** * Sets the {@link Equivalence} {@linkplain io.timeandspace.collect.map.ObjObjMap#keyEquivalence * used for comparing keys} in SmoothieMaps created with this builder to {@link * Equivalence#defaultEquality()}. * * @return this builder back * @see #keyEquivalence(Equivalence) * @see io.timeandspace.collect.map.ObjObjMap#keyEquivalence */ @CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder defaultKeyEquivalence() { this.keyEquivalence = Equivalence.defaultEquality(); return this; } /** * Sets a key hash function to be used in {@linkplain SmoothieMap SmoothieMaps} created with * this builder. * *

The default key hash function derives 64-bit hash codes from the 32-bit result of calling * {@link Object#hashCode()} on the key objects (or {@link Equivalence#hash}, if {@link * #keyEquivalence(Equivalence)} is configured for the builder). This means that if the number * of entries in the SmoothieMap approaches or exceeds 2^32 (4 billion), a large number of * hash code collisions is inevitable. Therefore, it's recommended to always configure a custom * key hash function (using this method) for ultra-large SmoothieMaps. * *

The specified hash function must be consistent with {@link Object#equals} on the key * objects, or a custom key equivalence if specified via {@link #keyEquivalence(Equivalence)} in * the same way as {@link Object#hashCode()} must be consistent with {@code equals()}. This is * the user's responsibility to ensure this. When {@link #keyEquivalence(Equivalence)} is called * on a builder object, the key hash function, if already configured to a non-default, is * not reset. On the other hand, if {@code keyHashFunction()} (or {@link * #keyHashFunctionFactory(Supplier)}) is never called on a builder, or {@link * #defaultKeyHashFunction()} is called, it's not necessary to configure the key hash function * along with any custom {@link #keyEquivalence(Equivalence)} because by default {@code * SmoothieMapBuilder} does respect {@link Equivalence#hash}. * * @param hashFunction a key hash function for each {@code SmoothieMap} created using this * builder * @return this builder back * @throws NullPointerException if the given hash function object is null * @see #keyHashFunctionFactory(Supplier) * @see #defaultKeyHashFunction() * @see #keyEquivalence(Equivalence) */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder keyHashFunction(ToLongFunction hashFunction) { checkNonNull(hashFunction); this.keyHashFunctionFactory = () -> hashFunction; return this; } /** * Sets a factory to obtain a key hash function for each {@link SmoothieMap} created using this * builder. Compared to {@link #keyHashFunction(ToLongFunction)}, this method allows inserting * variability or randomness into the hash function: *

{@code
     * builder.keyHashFunctionFactory(() -> {
     *     LongHashFunction hashF = LongHashFunction.xx(ThreadLocalRandom.current().nextLong());
     *     return hashF::hashChars;
     * });}
* *

Hash functions returned by the specified factory must be consistent with {@link * Object#equals} on the key objects, or a custom key equivalence if specified via {@link * #keyEquivalence(Equivalence)} in the same way as {@link Object#hashCode()} must be consistent * with {@code equals()}. This is the user's responsibility to ensure the consistency. When * {@link #keyEquivalence(Equivalence)} is called on a builder object, the key hash function * factory, if already configured to a non-default, is not reset. See the Javadoc comment * for {@link #keyHashFunction(ToLongFunction)} for more information. * * @param hashFunctionFactory the factory to create a key hash function for each {@code * SmoothieMap} created using this builder * @return this builder back * @throws NullPointerException if the given hash function factory object is null * @see #keyHashFunction(ToLongFunction) * @see #defaultKeyHashFunction() */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder keyHashFunctionFactory( Supplier> hashFunctionFactory) { checkNonNull(hashFunctionFactory); this.keyHashFunctionFactory = hashFunctionFactory; return this; } /** * Specifies that SmoothieMaps created with this builder should use the default key hash * function which derives a 64-bit hash code from the 32-bit result of calling {@link * Object#hashCode()} on the key object (or {@link Equivalence#hash}, if {@link * #keyEquivalence(Equivalence)} is configured for the builder). * * @return this builder back * @see #keyHashFunction(ToLongFunction) * @see #keyHashFunctionFactory(Supplier) */ @CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder defaultKeyHashFunction() { this.keyHashFunctionFactory = null; return this; } /** * Sets the {@link Equivalence} {@linkplain * io.timeandspace.collect.map.ObjObjMap#valueEquivalence used for comparing values} in * SmoothieMaps created with this builder to the given equivalence. * * @param valueEquivalence the value equivalence to use in created SmoothieMaps * @return this builder back * @throws NullPointerException if the given equivalence object is null * @see #defaultValueEquivalence() * @see io.timeandspace.collect.map.ObjObjMap#valueEquivalence() * @see #keyEquivalence(Equivalence) */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder valueEquivalence(Equivalence valueEquivalence) { checkNonNull(valueEquivalence); this.valueEquivalence = valueEquivalence; return this; } /** * Sets the {@link Equivalence} {@linkplain * io.timeandspace.collect.map.ObjObjMap#valueEquivalence used for comparing values} in * SmoothieMaps created with this builder to {@link Equivalence#defaultEquality()}. * * @return this builder back * @see #valueEquivalence(Equivalence) * @see io.timeandspace.collect.map.ObjObjMap#valueEquivalence() */ @CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder defaultValueEquivalence() { this.valueEquivalence = Equivalence.defaultEquality(); return this; } /** * Specifies the expected steady-state size of each {@link SmoothieMap} created using * this builder. The steady-state size is the map size after it's fully populated (when the map * is used in a simple populate-then-access pattern), or if entries are dynamically inserted * into and removed from the map, the steady-state size is the map size at which it should * eventually balance. * *

The default expected size is considered unknown. Calling {@link #unknownExpectedSize()} * after this method has been once called on the builder with a specific value overrides that * values and sets the expected size to be unknown again. * *

Calling this method is a performance hint for created SmoothieMap(s) and doesn't affect * the semantics of operations. The configured value must not be the exact steady-state size of * a created SmoothieMap, but it should be within 5% from the actual steady-state size. If the * steady-state size of created SmoothieMap(s) cannot be known with this level of precision, * configuring {@code expectedSize()} might make more harm than good and therefore shouldn't be * done. {@link #minPeakSize(long)} might be known with more confidence though and it's * recommended to configure it instead. * *

In theory, the {@linkplain #minPeakSize(long) minimum peak size} might be less than the * expected size by at most 5% (higher difference is not possible if the expected size is * configured according with the configuration above). If both expected size and minimum peak * size are specified and the latter is less than the former by more than 5% an {@link * IllegalStateException} is thrown when {@link #build()} is called. * *

Most often, the minimum peak size should be equal to the expected size (e. g. in the * populate-then-access usage pattern, there is no ground for differentiating the peak and the * expected sizes). Because of this, by default, if the expected size is configured for a * builder and the minimum peak size is not configured, the expected size is used as a * substitute for the minimum peak size. * *

When entries are dynamically put into and removed from a SmoothieMap, the minimum peak * size might also be greater than the expected size, if after reaching the peak size * SmoothieMaps are consistently expected to shrink. This might also be the case * when X entries are first inserted into a SmoothieMap and then some entries are removed until * the map reduces to some specific size Y smaller than X. * * @param expectedSize the expected steady-state size of created SmoothieMaps * @return this builder back * @throws IllegalArgumentException if the provided expected size is not positive * @see #unknownExpectedSize() * @see #minPeakSize(long) */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder expectedSize(long expectedSize) { checkNonNegative(expectedSize, "expected size"); this.expectedSize = expectedSize; return this; } /** * Specifies that the expected size of each {@link SmoothieMap} created using this builder is * unknown. * * @return this builder back * @see #expectedSize(long) */ @CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder unknownExpectedSize() { this.expectedSize = UNKNOWN_SIZE; return this; } /** * Specifies the minimum bound of the peak size of each {@link SmoothieMap} created * using this builder. For example, it may be unknown what size the created SmoothieMap will * have after it is fully populated (or reaches "saturation" if entries are dynamically put into * and removed from the map): 1 million entries, or 2 million, or 3 million. But if it known * that under no circumstance the peak size of the map will be less than 1 million, then it * makes sense to configure this bound via this method. * *

The default minimum bound for the peak size of created SmoothieMaps is considered unknown, * or 0. However, to configure unknown minimum bound of the peak size (the override a specific * values which may have been once specified for the builder), {@link #unknownMinPeakSize()} * should be called and not this method. * *

Calling this method is a performance hint for created SmoothieMap(s) and doesn't affect * the semantics of operations. The configured value may be used to preallocate data structure * when a SmoothieMap is created, so it's better to err towards a value lower than the actual * peak size than towards a value higher than the actual peak size, especially when the * {@linkplain #optimizeFor optimization objective} is set to {@link * OptimizationObjective#LOW_GARBAGE}. * *

If the minimum peak size is not configured for a SmoothieMapBuilder, but {@linkplain * #expectedSize(long) expected size} is configured, the minimum peak size of created maps is * assumed to be equal to the expected size, although in some cases it may be about 5% less than * that even if the expected size is configured properly. If this is the case, configure the * minimum peak size along with the expected size explicitly. See the documentation for {@link * #expectedSize(long)} method for more information. * * @param minPeakSize the minimum bound for the peak size of created SmoothieMaps * @return this builder back * @throws IllegalArgumentException if the provided minimum peak size is not positive * @see #unknownMinPeakSize() * @see #expectedSize(long) */ @CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder minPeakSize(long minPeakSize) { checkNonNegative(minPeakSize, "minimum peak size"); this.minPeakSize = minPeakSize; return this; } /** * Specifies that the minimum bound of the peak size of each {@link SmoothieMap} created using * this builder is unknown. * * @return this builder back * @see #minPeakSize(long) */ @CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder unknownMinPeakSize() { this.minPeakSize = UNKNOWN_SIZE; return this; } /** * Creates a new {@link SmoothieMap} with the configurations from this builder. The created * {@code SmoothieMap} doesn't hold an internal reference to the builder, and all subsequent * changes to the builder doesn't affect the configurations of the map. * * @return a new {@code SmoothieMap} with the configurations from this builder */ @Contract(" -> new") public SmoothieMap build() { boolean isDefaultKeyEquivalence = keyEquivalence.equals(Equivalence.defaultEquality()); boolean isDefaultValueEquivalence = valueEquivalence.equals(Equivalence.defaultEquality()); if (keyHashFunctionFactory == null && isDefaultKeyEquivalence && isDefaultValueEquivalence) { return new SmoothieMap<>(this); } else if (keyHashFunctionFactory != null && isDefaultKeyEquivalence && isDefaultValueEquivalence) { return new SmoothieMapWithCustomKeyHashFunction<>(this); } else if (!isDefaultKeyEquivalence && isDefaultValueEquivalence) { return new SmoothieMapWithCustomKeyEquivalence<>(this); } else if (keyHashFunctionFactory == null && isDefaultKeyEquivalence) { Utils.verifyThat(!isDefaultValueEquivalence); return new SmoothieMapWithCustomValueEquivalence<>(this); } else { return new SmoothieMapWithCustomKeyAndValueEquivalences<>(this); } } boolean allocateIntermediateCapacitySegments() { return allocateIntermediateCapacitySegments; } boolean splitBetweenTwoNewSegments() { return splitBetweenTwoNewSegments; } boolean doShrink() { return doShrink; } Equivalence keyEquivalence() { return keyEquivalence; } ToLongFunction keyHashFunction() { if (keyHashFunctionFactory != null) { return keyHashFunctionFactory.get(); } if (keyEquivalence.equals(Equivalence.defaultEquality())) { return DefaultHashFunction.instance(); } return new EquivalenceBasedHashFunction<>(keyEquivalence); } Equivalence valueEquivalence() { return valueEquivalence; } private void checkSizes() { if (expectedSize == UNKNOWN_SIZE || minPeakSize == UNKNOWN_SIZE) { // Nothing to check return; } if (minPeakSize < (long) ((double) expectedSize * (1.0 - MAX_EXPECTED_SIZE_ERROR_FRACTION))) { throw new IllegalStateException("minPeakSize[" + minPeakSize + "] is less than " + "expectedSize[" + expectedSize + "] * " + (1.0 - MAX_EXPECTED_SIZE_ERROR_FRACTION)); } } long expectedSize() { checkSizes(); return expectedSize; } long minPeakSize() { checkSizes(); return minPeakSize != UNKNOWN_SIZE ? minPeakSize : expectedSize; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy