Avoid resizing of fixed-size HashMap/LinkedHashMap variants

Closes gh-25349
This commit is contained in:
Juergen Hoeller
2020-08-25 19:26:18 +02:00
parent 241afeb1b7
commit ff11467a0c
58 changed files with 195 additions and 149 deletions

View File

@@ -34,9 +34,11 @@ import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.CollectionUtils;
/**
* Benchmarks for {@link GenericConversionService}.
*
* @author Brian Clozel
*/
@BenchmarkMode(Mode.Throughput)
@@ -78,11 +80,12 @@ public class GenericConversionServiceBenchmark {
@Benchmark
public void convertMapOfStringToListOfIntegerBaseline(MapBenchmarkState state, Blackhole bh) {
Map<String, Integer> target = new HashMap<>(state.source.size());
Map<String, Integer> target = CollectionUtils.newHashMap(state.source.size());
state.source.forEach((k, v) -> target.put(k, Integer.valueOf(v)));
bh.consume(target);
}
@State(Scope.Benchmark)
public static class MapBenchmarkState extends BenchmarkState {
@@ -90,7 +93,7 @@ public class GenericConversionServiceBenchmark {
@Setup(Level.Trial)
public void setup() throws Exception {
this.source = new HashMap<>(this.collectionSize);
this.source = CollectionUtils.newHashMap(this.collectionSize);
Map<String, Integer> target = new HashMap<>();
this.targetTypeDesc = TypeDescriptor.forObject(target);
this.source = IntStream.rangeClosed(1, collectionSize).mapToObj(String::valueOf)
@@ -98,6 +101,7 @@ public class GenericConversionServiceBenchmark {
}
}
@State(Scope.Benchmark)
public static class BenchmarkState {
@@ -107,6 +111,6 @@ public class GenericConversionServiceBenchmark {
int collectionSize;
TypeDescriptor targetTypeDesc;
}
}

View File

@@ -24,7 +24,6 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -35,6 +34,7 @@ import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.Mirr
import org.springframework.core.annotation.MergedAnnotation.Adapt;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@@ -905,7 +905,7 @@ public abstract class AnnotationUtils {
if (!methods.hasDefaultValueMethod()) {
return Collections.emptyMap();
}
Map<String, DefaultValueHolder> result = new LinkedHashMap<>(methods.size());
Map<String, DefaultValueHolder> result = CollectionUtils.newLinkedHashMap(methods.size());
if (!methods.hasNestedAnnotation()) {
// Use simpler method if there are no nested annotations
for (int i = 0; i < methods.size(); i++) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,12 @@
package org.springframework.core.codec;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
/**
* Constants and convenience methods for working with hints.
@@ -120,7 +120,7 @@ public abstract class Hints {
return hints2;
}
else {
Map<String, Object> result = new HashMap<>(hints1.size() + hints2.size());
Map<String, Object> result = CollectionUtils.newHashMap(hints1.size() + hints2.size());
result.putAll(hints1);
result.putAll(hints2);
return result;
@@ -141,7 +141,7 @@ public abstract class Hints {
return Collections.singletonMap(hintName, hintValue);
}
else {
Map<String, Object> result = new HashMap<>(hints.size() + 1);
Map<String, Object> result = CollectionUtils.newHashMap(hints.size() + 1);
result.putAll(hints);
result.put(hintName, hintValue);
return result;

View File

@@ -68,7 +68,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
* to ensure that the hierarchical ordering of the entries is preserved.
* @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes
*/
protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<>(4);
protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<>(3);
protected final Set<MethodMetadata> methodMetadataSet = new LinkedHashSet<>(4);

View File

@@ -66,7 +66,7 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<>(4);
protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<>(4);
protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<>(3);
public MethodMetadataReadingVisitor(String methodName, int access, String declaringClassName,

View File

@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -42,6 +43,14 @@ import org.springframework.lang.Nullable;
*/
public abstract class CollectionUtils {
/**
* Default load factor for {@link HashMap}/{@link LinkedHashMap} variants.
* @see #newHashMap(int)
* @see #newLinkedHashMap(int)
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* Return {@code true} if the supplied Collection is {@code null} or empty.
* Otherwise, return {@code false}.
@@ -62,6 +71,37 @@ public abstract class CollectionUtils {
return (map == null || map.isEmpty());
}
/**
* Instantiate a new {@link HashMap} with an initial capacity
* that can accommodate the given number of elements.
* <p>This differs from the regular {@link HashMap} constructor
* which takes an initial capacity relative to a load factor
* but is effectively aligned with the JDK's
* {@link java.util.concurrent.ConcurrentHashMap#ConcurrentHashMap(int)}.
* @param expectedSize the expected number of elements
* @since 5.3
* @see #newLinkedHashMap(int)
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static <K, V> HashMap<K, V> newHashMap(int expectedSize) {
return new HashMap((int) (expectedSize / DEFAULT_LOAD_FACTOR), DEFAULT_LOAD_FACTOR);
}
/**
* Instantiate a new {@link LinkedHashMap} with an initial capacity
* that can accommodate the given number of elements.
* <p>This differs from the regular {@link LinkedHashMap} constructor
* which takes an initial capacity relative to a load factor but is
* aligned with Spring's own {@link LinkedCaseInsensitiveMap} and
* {@link LinkedMultiValueMap} constructor semantics as of 5.3.
* @param expectedSize the expected number of elements
* @since 5.3
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static <K, V> LinkedHashMap<K, V> newLinkedHashMap(int expectedSize) {
return new LinkedHashMap((int) (expectedSize / DEFAULT_LOAD_FACTOR), DEFAULT_LOAD_FACTOR);
}
/**
* Convert the supplied array into a List. A primitive array gets converted
* into a List of the appropriate wrapper type.
@@ -74,8 +114,7 @@ public abstract class CollectionUtils {
* @see ObjectUtils#toObjectArray(Object)
* @see Arrays#asList(Object[])
*/
@SuppressWarnings("rawtypes")
public static List arrayToList(@Nullable Object source) {
public static List<?> arrayToList(@Nullable Object source) {
return Arrays.asList(ObjectUtils.toObjectArray(source));
}
@@ -430,7 +469,7 @@ public abstract class CollectionUtils {
MultiValueMap<? extends K, ? extends V> targetMap) {
Assert.notNull(targetMap, "'targetMap' must not be null");
Map<K, List<V>> result = new LinkedHashMap<>(targetMap.size());
Map<K, List<V>> result = newLinkedHashMap(targetMap.size());
targetMap.forEach((key, value) -> {
List<? extends V> values = Collections.unmodifiableList(value);
result.put(key, (List<V>) values);

View File

@@ -76,7 +76,7 @@ public class LinkedCaseInsensitiveMap<V> implements Map<String, V>, Serializable
/**
* Create a new LinkedCaseInsensitiveMap that stores case-insensitive keys
* according to the given Locale (by default in lower case).
* according to the given Locale (in lower case).
* @param locale the Locale to use for case-insensitive key conversion
* @see #convertKey(String)
*/
@@ -86,25 +86,26 @@ public class LinkedCaseInsensitiveMap<V> implements Map<String, V>, Serializable
/**
* Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap}
* with the given initial capacity and stores case-insensitive keys
* according to the default Locale (by default in lower case).
* @param initialCapacity the initial capacity
* with an initial capacity that can accommodate the given number of elements,
* storing case-insensitive keys according to the default Locale (in lower case).
* @param expectedSize the expected number of elements
* @see #convertKey(String)
*/
public LinkedCaseInsensitiveMap(int initialCapacity) {
this(initialCapacity, null);
public LinkedCaseInsensitiveMap(int expectedSize) {
this(expectedSize, null);
}
/**
* Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap}
* with the given initial capacity and stores case-insensitive keys
* according to the given Locale (by default in lower case).
* @param initialCapacity the initial capacity
* with an initial capacity that can accommodate the given number of elements,
* storing case-insensitive keys according to the given Locale (in lower case).
* @param expectedSize the expected number of elements
* @param locale the Locale to use for case-insensitive key conversion
* @see #convertKey(String)
*/
public LinkedCaseInsensitiveMap(int initialCapacity, @Nullable Locale locale) {
this.targetMap = new LinkedHashMap<String, V>(initialCapacity) {
public LinkedCaseInsensitiveMap(int expectedSize, @Nullable Locale locale) {
this.targetMap = new LinkedHashMap<String, V>(
(int) (expectedSize / CollectionUtils.DEFAULT_LOAD_FACTOR), CollectionUtils.DEFAULT_LOAD_FACTOR) {
@Override
public boolean containsKey(Object key) {
return LinkedCaseInsensitiveMap.this.containsKey(key);
@@ -118,7 +119,7 @@ public class LinkedCaseInsensitiveMap<V> implements Map<String, V>, Serializable
return doRemove;
}
};
this.caseInsensitiveKeys = new HashMap<>(initialCapacity);
this.caseInsensitiveKeys = CollectionUtils.newHashMap(expectedSize);
this.locale = (locale != null ? locale : Locale.getDefault());
}

View File

@@ -49,11 +49,11 @@ public class LinkedMultiValueMap<K, V> extends MultiValueMapAdapter<K, V> implem
/**
* Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}
* with the given initial capacity.
* @param initialCapacity the initial capacity
* with an initial capacity that can accommodate the given number of elements.
* @param expectedSize the expected number of elements
*/
public LinkedMultiValueMap(int initialCapacity) {
super(new LinkedHashMap<>(initialCapacity));
public LinkedMultiValueMap(int expectedSize) {
super(CollectionUtils.newLinkedHashMap(expectedSize));
}
/**

View File

@@ -18,7 +18,6 @@ package org.springframework.util;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -88,7 +87,7 @@ class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializable {
@Override
public Map<K, V> toSingleValueMap() {
Map<K, V> singleValueMap = new LinkedHashMap<>(this.targetMap.size());
Map<K, V> singleValueMap = CollectionUtils.newLinkedHashMap(this.targetMap.size());
this.targetMap.forEach((key, values) -> {
if (values != null && !values.isEmpty()) {
singleValueMap.put(key, values.get(0));