Skip to content

Commit 28dd952

Browse files
committed
Optimize MultiValueMap iteration operations
* use forEach and putIfAbsent to copy headers in DefaultClientRequestBuilder * use forEach in ReactorClientHttpRequest and ReactorNetty2ClientHttpRequest * circumvent ReadOnlyHttpHeaders.entrySet() * ensure the fast path to LinkedCaseInsensitiveMap for forEach and putIfAbsent exists
1 parent bd62f47 commit 28dd952

File tree

9 files changed

+82
-16
lines changed

9 files changed

+82
-16
lines changed

Diff for: spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828
import java.util.Set;
2929
import java.util.Spliterator;
30+
import java.util.function.BiConsumer;
3031
import java.util.function.Consumer;
3132
import java.util.function.Function;
3233

@@ -286,6 +287,11 @@ public Set<Entry<String, V>> entrySet() {
286287
return entrySet;
287288
}
288289

290+
@Override
291+
public void forEach(BiConsumer<? super String, ? super V> action) {
292+
this.targetMap.forEach(action);
293+
}
294+
289295
@Override
290296
public LinkedCaseInsensitiveMap<V> clone() {
291297
return new LinkedCaseInsensitiveMap<>(this);

Diff for: spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.Set;
25+
import java.util.function.BiConsumer;
2526

2627
import org.springframework.lang.Nullable;
2728

@@ -69,15 +70,13 @@ public void add(K key, @Nullable V value) {
6970

7071
@Override
7172
public void addAll(K key, List<? extends V> values) {
72-
List<V> currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(1));
73+
List<V> currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(values.size()));
7374
currentValues.addAll(values);
7475
}
7576

7677
@Override
7778
public void addAll(MultiValueMap<K, V> values) {
78-
for (Entry<K, List<V>> entry : values.entrySet()) {
79-
addAll(entry.getKey(), entry.getValue());
80-
}
79+
values.forEach(this::addAll);
8180
}
8281

8382
@Override
@@ -138,6 +137,12 @@ public List<V> put(K key, List<V> value) {
138137
return this.targetMap.put(key, value);
139138
}
140139

140+
@Override
141+
@Nullable
142+
public List<V> putIfAbsent(K key, List<V> value) {
143+
return this.targetMap.putIfAbsent(key, value);
144+
}
145+
141146
@Override
142147
@Nullable
143148
public List<V> remove(Object key) {
@@ -169,6 +174,11 @@ public Set<Entry<K, List<V>>> entrySet() {
169174
return this.targetMap.entrySet();
170175
}
171176

177+
@Override
178+
public void forEach(BiConsumer<? super K, ? super List<V>> action) {
179+
this.targetMap.forEach(action);
180+
}
181+
172182
@Override
173183
public boolean equals(@Nullable Object other) {
174184
return (this == other || this.targetMap.equals(other));

Diff for: spring-web/src/main/java/org/springframework/http/HttpHeaders.java

+11
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.util.Map;
4141
import java.util.Set;
4242
import java.util.StringJoiner;
43+
import java.util.function.BiConsumer;
4344
import java.util.regex.Matcher;
4445
import java.util.regex.Pattern;
4546
import java.util.stream.Collectors;
@@ -1821,6 +1822,16 @@ public Set<Entry<String, List<String>>> entrySet() {
18211822
return this.headers.entrySet();
18221823
}
18231824

1825+
@Override
1826+
public void forEach(BiConsumer<? super String, ? super List<String>> action) {
1827+
this.headers.forEach(action);
1828+
}
1829+
1830+
@Override
1831+
public List<String> putIfAbsent(String key, List<String> value) {
1832+
return this.headers.putIfAbsent(key, value);
1833+
}
1834+
18241835

18251836
@Override
18261837
public boolean equals(@Nullable Object obj) {

Diff for: spring-web/src/main/java/org/springframework/http/ReadOnlyHttpHeaders.java

+6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.Set;
26+
import java.util.function.BiConsumer;
2627
import java.util.stream.Collectors;
2728

2829
import org.springframework.lang.Nullable;
@@ -155,4 +156,9 @@ public Set<Entry<String, List<String>>> entrySet() {
155156
Collections::unmodifiableSet));
156157
}
157158

159+
@Override
160+
public void forEach(BiConsumer<? super String, ? super List<String>> action) {
161+
this.headers.forEach((k, vs) -> action.accept(k, Collections.unmodifiableList(vs)));
162+
}
163+
158164
}

Diff for: spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpRequest.java

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

1919
import java.net.URI;
2020
import java.nio.file.Path;
21-
import java.util.Collection;
2221

2322
import io.netty.buffer.ByteBuf;
2423
import io.netty.handler.codec.http.cookie.DefaultCookie;
@@ -129,9 +128,10 @@ protected void applyHeaders() {
129128

130129
@Override
131130
protected void applyCookies() {
132-
getCookies().values().stream().flatMap(Collection::stream)
133-
.map(cookie -> new DefaultCookie(cookie.getName(), cookie.getValue()))
134-
.forEach(this.request::addCookie);
131+
getCookies().values().forEach(values -> values.forEach(value -> {
132+
DefaultCookie cookie = new DefaultCookie(value.getName(), value.getValue());
133+
this.request.addCookie(cookie);
134+
}));
135135
}
136136

137137
@Override

Diff for: spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ClientHttpRequest.java

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

1919
import java.net.URI;
2020
import java.nio.file.Path;
21-
import java.util.Collection;
2221

2322
import io.netty5.buffer.Buffer;
2423
import io.netty5.handler.codec.http.headers.DefaultHttpCookiePair;
@@ -130,9 +129,10 @@ protected void applyHeaders() {
130129

131130
@Override
132131
protected void applyCookies() {
133-
getCookies().values().stream().flatMap(Collection::stream)
134-
.map(cookie -> new DefaultHttpCookiePair(cookie.getName(), cookie.getValue()))
135-
.forEach(this.request::addCookie);
132+
getCookies().values().forEach(values -> values.forEach(value -> {
133+
DefaultHttpCookiePair cookie = new DefaultHttpCookiePair(value.getName(), value.getValue());
134+
this.request.addCookie(cookie);
135+
}));
136136
}
137137

138138
@Override

Diff for: spring-web/src/test/java/org/springframework/http/HttpHeadersTests.java

+25
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,31 @@ void readOnlyHttpHeadersRetainEntrySetOrder() {
704704
assertThat(readOnlyHttpHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys);
705705
}
706706

707+
@Test
708+
void readOnlyHttpHeadersCopyOrderTest() {
709+
headers.add("aardvark", "enigma");
710+
headers.add("beaver", "enigma");
711+
headers.add("cat", "enigma");
712+
headers.add("dog", "enigma");
713+
headers.add("elephant", "enigma");
714+
715+
String[] expectedKeys = new String[] { "aardvark", "beaver", "cat", "dog", "elephant" };
716+
717+
HttpHeaders readOnlyHttpHeaders = HttpHeaders.readOnlyHttpHeaders(headers);
718+
719+
HttpHeaders forEachHeaders = new HttpHeaders();
720+
readOnlyHttpHeaders.forEach(forEachHeaders::putIfAbsent);
721+
assertThat(forEachHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys);
722+
723+
HttpHeaders putAllHeaders = new HttpHeaders();
724+
putAllHeaders.putAll(readOnlyHttpHeaders);
725+
assertThat(putAllHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys);
726+
727+
HttpHeaders addAllHeaders = new HttpHeaders();
728+
addAllHeaders.addAll(readOnlyHttpHeaders);
729+
assertThat(addAllHeaders.entrySet()).extracting(Entry::getKey).containsExactly(expectedKeys);
730+
}
731+
707732
@Test // gh-25034
708733
void equalsUnwrapsHttpHeaders() {
709734
HttpHeaders headers1 = new HttpHeaders();

Diff for: spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultClientRequestBuilder.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,7 @@ public String logPrefix() {
255255
public Mono<Void> writeTo(ClientHttpRequest request, ExchangeStrategies strategies) {
256256
HttpHeaders requestHeaders = request.getHeaders();
257257
if (!this.headers.isEmpty()) {
258-
this.headers.entrySet().stream()
259-
.filter(entry -> !requestHeaders.containsKey(entry.getKey()))
260-
.forEach(entry -> requestHeaders
261-
.put(entry.getKey(), entry.getValue()));
258+
this.headers.forEach(requestHeaders::putIfAbsent);
262259
}
263260

264261
MultiValueMap<String, HttpCookie> requestCookies = request.getCookies();

Diff for: spring-websocket/src/main/java/org/springframework/web/socket/WebSocketHttpHeaders.java

+11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.Set;
25+
import java.util.function.BiConsumer;
2526

2627
import org.springframework.http.HttpHeaders;
2728
import org.springframework.lang.Nullable;
@@ -295,6 +296,16 @@ public Set<Entry<String, List<String>>> entrySet() {
295296
return this.headers.entrySet();
296297
}
297298

299+
@Override
300+
public void forEach(BiConsumer<? super String, ? super List<String>> action) {
301+
this.headers.forEach(action);
302+
}
303+
304+
@Override
305+
public List<String> putIfAbsent(String key, List<String> value) {
306+
return this.headers.putIfAbsent(key, value);
307+
}
308+
298309

299310
@Override
300311
public boolean equals(@Nullable Object other) {

0 commit comments

Comments
 (0)