Class SmoothieMapBuilder<K,V>
- java.lang.Object
-
- io.timeandspace.smoothie.SmoothieMapBuilder<K,V>
-
- Type Parameters:
K
- the type of keys in created SmoothieMapsV
- the type of values in created SmoothieMaps
public final class SmoothieMapBuilder<K,V> extends Object
SmoothieMapBuilder is used to configure and createSmoothieMap
s. A new builder could be created viaSmoothieMap.newBuilder()
method. A SmoothieMap is created usingbuild()
method.SmoothieMapBuilder is mutable: all its configuration methods change its state, and return the receiver builder object to enable the "fluent builder" pattern:
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.
-
-
Method Summary
All Methods Instance Methods Concrete Methods Modifier and Type Method Description SmoothieMapBuilder<K,V>
allocateIntermediateCapacitySegments(boolean allocateIntermediateCapacitySegments)
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<K,V>
build()
Creates a newSmoothieMap
with the configurations from this builder.SmoothieMapBuilder<K,V>
defaultKeyEquivalence()
Sets theEquivalence
used for comparing keys in SmoothieMaps created with this builder toEquivalence.defaultEquality()
.SmoothieMapBuilder<K,V>
defaultKeyHashFunction()
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 callingObject.hashCode()
on the key object (orEquivalence.hash(T)
, ifkeyEquivalence(Equivalence)
is configured for the builder).SmoothieMapBuilder<K,V>
defaultOptimizationConfiguration()
Specifies that SmoothieMaps created using this builder should operate in "mixed" mode which is a compromise betweenOptimizationObjective.FOOTPRINT
andOptimizationObjective.LOW_GARBAGE
.SmoothieMapBuilder<K,V>
defaultValueEquivalence()
Sets theEquivalence
used for comparing values in SmoothieMaps created with this builder toEquivalence.defaultEquality()
.SmoothieMapBuilder<K,V>
doShrink(boolean doShrink)
Specifies whether SmoothieMaps created with this builder should automatically shrink, i.SmoothieMapBuilder<K,V>
expectedSize(long expectedSize)
Specifies the expected steady-state size of eachSmoothieMap
created using this builder.SmoothieMapBuilder<K,V>
keyEquivalence(Equivalence<K> keyEquivalence)
Sets theEquivalence
used for comparing keys in SmoothieMaps created with this builder to the given equivalence.SmoothieMapBuilder<K,V>
keyHashFunction(ToLongFunction<K> hashFunction)
Sets a key hash function to be used in SmoothieMaps created with this builder.SmoothieMapBuilder<K,V>
keyHashFunctionFactory(Supplier<ToLongFunction<K>> hashFunctionFactory)
Sets a factory to obtain a key hash function for eachSmoothieMap
created using this builder.SmoothieMapBuilder<K,V>
minPeakSize(long minPeakSize)
Specifies the minimum bound of the peak size of eachSmoothieMap
created using this builder.SmoothieMapBuilder<K,V>
optimizeFor(OptimizationObjective optimizationObjective)
Specifies whether SmoothieMaps created using this builder should operate in the "low-garbage" mode (ifOptimizationObjective.LOW_GARBAGE
is passed into this method) or the "footprint" mode (ifOptimizationObjective.FOOTPRINT
is passed into this method).SmoothieMapBuilder<K,V>
splitBetweenTwoNewSegments(boolean splitBetweenTwoNewSegments)
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).SmoothieMapBuilder<K,V>
unknownExpectedSize()
Specifies that the expected size of eachSmoothieMap
created using this builder is unknown.SmoothieMapBuilder<K,V>
unknownMinPeakSize()
Specifies that the minimum bound of the peak size of eachSmoothieMap
created using this builder is unknown.SmoothieMapBuilder<K,V>
valueEquivalence(Equivalence<V> valueEquivalence)
Sets theEquivalence
used for comparing values in SmoothieMaps created with this builder to the given equivalence.
-
-
-
Method Detail
-
optimizeFor
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> optimizeFor(OptimizationObjective optimizationObjective)
Specifies whether SmoothieMaps created using this builder should operate in the "low-garbage" mode (ifOptimizationObjective.LOW_GARBAGE
is passed into this method) or the "footprint" mode (ifOptimizationObjective.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
defaultOptimizationConfiguration()
configures this mode explicitly.- Parameters:
optimizationObjective
- the primary optimization objective for created SmoothieMaps- Returns:
- this builder back
- Throws:
NullPointerException
- if the provided optimization objective is null- See Also:
defaultOptimizationConfiguration()
-
defaultOptimizationConfiguration
@CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder<K,V> defaultOptimizationConfiguration()
Specifies that SmoothieMaps created using this builder should operate in "mixed" mode which is a compromise betweenOptimizationObjective.FOOTPRINT
andOptimizationObjective.LOW_GARBAGE
.- Implementation Requirements:
- the "mixed" mode includes allocating intermediate-capacity segments and turning SmoothieMap shrinking on, but doesn't include splitting segments between two new ones.
- Returns:
- this builder back
- See Also:
optimizeFor(io.timeandspace.smoothie.OptimizationObjective)
-
allocateIntermediateCapacitySegments
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> allocateIntermediateCapacitySegments(boolean allocateIntermediateCapacitySegments)
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
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
expectedSize(long)
norminPeakSize(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 asIdentityHashMap
, and comparable to the the total amount of garbage produced by entry-based hash maps (HashMap
andConcurrentHashMap
) 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 the "mixed" mode (the default mode) of SmoothieMap's operation. Intermediate-capacity segments are allocated in the "mixed" and the "footprint" modes, but are not allocated in the "low-garbage" mode.
Disabling allocating intermediate-capacity segments (that is, passing
false
into this method) also implicitly disables 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).- Parameters:
allocateIntermediateCapacitySegments
- whether the created SmoothieMaps should allocate intermediate-capacity segments during the growth- Returns:
- this builder back
- See Also:
splitBetweenTwoNewSegments(boolean)
- 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
-
splitBetweenTwoNewSegments
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> splitBetweenTwoNewSegments(boolean splitBetweenTwoNewSegments)
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
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
IdentityHashMap
and entry-based hash maps:HashMap
orConcurrentHashMap
.By default, during a split, entries are distributed between the old (full-capacity) segment and a newly allocated intermediate-capacity segment, as per 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 "low-garbage" and the "mixed" modes. It's enabled only in the "footprint" mode.
Enabling splitting between two new segments also implicitly enables allocation of intermediate-capacity segments because the former doesn't make sense without the latter.
- Parameters:
splitBetweenTwoNewSegments
- whether created SmoothieMaps should move entries from full-capacity segments into two newly allocated intermediate-capacity segments during growth- Returns:
- this builder back
- See Also:
allocateIntermediateCapacitySegments(boolean)
,OptimizationObjective.FOOTPRINT
-
doShrink
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> doShrink(boolean doShrink)
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 the "mixed" mode (the default mode) of SmoothieMap's operation. The "mixed" and the "footprint" modes turn shrinking on, but the "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
HashMap
,TreeMap
, orConcurrentHashMap
during a similar process.- Parameters:
doShrink
- whether the created SmoothieMaps should automatically reclaim memory when they reduce in size- Returns:
- this builder back
-
keyEquivalence
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> keyEquivalence(Equivalence<K> keyEquivalence)
Sets theEquivalence
used for comparing keys in SmoothieMaps created with this builder to the given equivalence.- Parameters:
keyEquivalence
- the key equivalence to use in created SmoothieMaps- Returns:
- this builder back
- Throws:
NullPointerException
- if the given equivalence object is null- See Also:
defaultKeyEquivalence()
,ObjObjMap.keyEquivalence()
,valueEquivalence(Equivalence)
-
defaultKeyEquivalence
@CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder<K,V> defaultKeyEquivalence()
Sets theEquivalence
used for comparing keys in SmoothieMaps created with this builder toEquivalence.defaultEquality()
.- Returns:
- this builder back
- See Also:
keyEquivalence(Equivalence)
,ObjObjMap.keyEquivalence()
-
keyHashFunction
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> keyHashFunction(ToLongFunction<K> hashFunction)
Sets a key hash function to be used in SmoothieMaps created with this builder.The default key hash function derives 64-bit hash codes from the 32-bit result of calling
Object.hashCode()
on the key objects (orEquivalence.hash(T)
, ifkeyEquivalence(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
Object.equals(java.lang.Object)
on the key objects, or a custom key equivalence if specified viakeyEquivalence(Equivalence)
in the same way asObject.hashCode()
must be consistent withequals()
. This is the user's responsibility to ensure this. WhenkeyEquivalence(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, ifkeyHashFunction()
(orkeyHashFunctionFactory(Supplier)
) is never called on a builder, ordefaultKeyHashFunction()
is called, it's not necessary to configure the key hash function along with any customkeyEquivalence(Equivalence)
because by defaultSmoothieMapBuilder
does respectEquivalence.hash(T)
.- Parameters:
hashFunction
- a key hash function for eachSmoothieMap
created using this builder- Returns:
- this builder back
- Throws:
NullPointerException
- if the given hash function object is null- See Also:
keyHashFunctionFactory(Supplier)
,defaultKeyHashFunction()
,keyEquivalence(Equivalence)
-
keyHashFunctionFactory
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> keyHashFunctionFactory(Supplier<ToLongFunction<K>> hashFunctionFactory)
Sets a factory to obtain a key hash function for eachSmoothieMap
created using this builder. Compared tokeyHashFunction(ToLongFunction)
, this method allows inserting variability or randomness into the hash function:builder.keyHashFunctionFactory(() -> { LongHashFunction hashF = LongHashFunction.xx(ThreadLocalRandom.current().nextLong()); return hashF::hashChars; });
Hash functions returned by the specified factory must be consistent with
Object.equals(java.lang.Object)
on the key objects, or a custom key equivalence if specified viakeyEquivalence(Equivalence)
in the same way asObject.hashCode()
must be consistent withequals()
. This is the user's responsibility to ensure the consistency. WhenkeyEquivalence(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 forkeyHashFunction(ToLongFunction)
for more information.- Parameters:
hashFunctionFactory
- the factory to create a key hash function for eachSmoothieMap
created using this builder- Returns:
- this builder back
- Throws:
NullPointerException
- if the given hash function factory object is null- See Also:
keyHashFunction(ToLongFunction)
,defaultKeyHashFunction()
-
defaultKeyHashFunction
@CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder<K,V> defaultKeyHashFunction()
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 callingObject.hashCode()
on the key object (orEquivalence.hash(T)
, ifkeyEquivalence(Equivalence)
is configured for the builder).- Returns:
- this builder back
- See Also:
keyHashFunction(ToLongFunction)
,keyHashFunctionFactory(Supplier)
-
valueEquivalence
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> valueEquivalence(Equivalence<V> valueEquivalence)
Sets theEquivalence
used for comparing values in SmoothieMaps created with this builder to the given equivalence.- Parameters:
valueEquivalence
- the value equivalence to use in created SmoothieMaps- Returns:
- this builder back
- Throws:
NullPointerException
- if the given equivalence object is null- See Also:
defaultValueEquivalence()
,ObjObjMap.valueEquivalence()
,keyEquivalence(Equivalence)
-
defaultValueEquivalence
@CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder<K,V> defaultValueEquivalence()
Sets theEquivalence
used for comparing values in SmoothieMaps created with this builder toEquivalence.defaultEquality()
.- Returns:
- this builder back
- See Also:
valueEquivalence(Equivalence)
,ObjObjMap.valueEquivalence()
-
expectedSize
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> expectedSize(long expectedSize)
Specifies the expected steady-state size of eachSmoothieMap
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
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
expectedSize()
might make more harm than good and therefore shouldn't be done.minPeakSize(long)
might be known with more confidence though and it's recommended to configure it instead.In theory, the 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
IllegalStateException
is thrown whenbuild()
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.
- Parameters:
expectedSize
- the expected steady-state size of created SmoothieMaps- Returns:
- this builder back
- Throws:
IllegalArgumentException
- if the provided expected size is not positive- See Also:
unknownExpectedSize()
,minPeakSize(long)
-
unknownExpectedSize
@CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder<K,V> unknownExpectedSize()
Specifies that the expected size of eachSmoothieMap
created using this builder is unknown.- Returns:
- this builder back
- See Also:
expectedSize(long)
-
minPeakSize
@CanIgnoreReturnValue @Contract("_ -> this") public SmoothieMapBuilder<K,V> minPeakSize(long minPeakSize)
Specifies the minimum bound of the peak size of eachSmoothieMap
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),
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 optimization objective is set to
OptimizationObjective.LOW_GARBAGE
.If the minimum peak size is not configured for a SmoothieMapBuilder, but 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
expectedSize(long)
method for more information.- Parameters:
minPeakSize
- the minimum bound for the peak size of created SmoothieMaps- Returns:
- this builder back
- Throws:
IllegalArgumentException
- if the provided minimum peak size is not positive- See Also:
unknownMinPeakSize()
,expectedSize(long)
-
unknownMinPeakSize
@CanIgnoreReturnValue @Contract(" -> this") public SmoothieMapBuilder<K,V> unknownMinPeakSize()
Specifies that the minimum bound of the peak size of eachSmoothieMap
created using this builder is unknown.- Returns:
- this builder back
- See Also:
minPeakSize(long)
-
build
@Contract(" -> new") public SmoothieMap<K,V> build()
Creates a newSmoothieMap
with the configurations from this builder. The createdSmoothieMap
doesn't hold an internal reference to the builder, and all subsequent changes to the builder doesn't affect the configurations of the map.- Returns:
- a new
SmoothieMap
with the configurations from this builder
-
-