Esta es una versión de texto de mi charla "Ah, estas líneas" en la conferencia JPoint-2020 .
Para no perder el tiempo de los lectores en vano, coloquemos un punto en la "e".
¿Sobre qué es el artículo?
Un artículo sobre el uso eficiente (o no tan) de cadenas.
¿Para quién es el artículo?
Un artículo para desarrolladores de productividad y sus simpatizantes.
¿De dónde viene todo esto?
Algo está atrapado en el código del proyecto, algo, en marcos y bibliotecas.
¿Qué, qué y sobre qué se midió?
- el código de referencia y los resultados de las pruebas están disponibles en GitHub
- utilizado para la medición JMH 1.23
- Las mediciones se tomaron en una máquina en funcionamiento con un Intel Core i7-7700 (los números en sí mismos no son importantes, las relaciones entre ellos y los patrones identificados son importantes)
- JDK 11 se utilizó por defecto, pero también 8 y 14 (se indica explícitamente en las páginas respectivas)
- modo de referencia: tiempo medio de ejecución + consumo de memoria (menos es mejor)
El paquete java.lang y sus habitantes
Aquellos que trabajan con Java saben que java.lang
: este es el núcleo del lenguaje y si necesita hacer cambios allí, entonces es muy difícil impulsarlos, ya que el lenguaje es conservador y para cualquier mejora, incluso la más útil, se necesita evidencia férrea de que
a) definitivamente no romperá nada
b) realmente lo necesitas
: java.lang.String
. ( ), :
- JEP 192: String Deduplication in G1
- JEP 250: Store Interned Strings in CDS Archives
- JEP 254: Compact Strings
- JEP 280: IndifyString Concatenation
- JEP 326: Raw String Literals (Preview)
- JEP 355: Text Blocks (Preview)
- JEP 348: Compiler Intrinsics for Java SE APIs (
String.format()
)
— — java.lang.String
"" "", .
- []
- [ ]
- ,
, :
- /
, : , , . , . , : (JEP 192). : ( ).
— - ( ), — . .
:
- : ,
- :
(). , :
, - , HashMap
EnumMap
:
Map<String, ?> map = new HashMap<>();
class Constants {
static final String MarginLeft = "margl";
static final String MarginRight = "margr";
static final String MarginTop = "margt";
static final String MarginBottom = "margb";
}
Map<String, ?> map = new EnumMap<>(Constants.class);
enum Constants {
MarginLeft,
MarginRight,
MarginTop,
MarginBottom
}
:
@Benchmark
public Object hm() {
var map = new HashMap<>();
map.put(Constants.MarginLeft, 1);
map.put(Constants.MarginRight, 2);
map.put(Constants.MarginTop, 3);
map.put(Constants.MarginBottom, 4);
return map;
}
@Benchmark
public Object em() {
var map = new EnumMap<>(ConstantsEnum.class);
map.put(ConstantsEnum.MarginLeft, 1);
map.put(ConstantsEnum.MarginRight, 2);
map.put(ConstantsEnum.MarginTop, 3);
map.put(ConstantsEnum.MarginBottom, 4);
return map;
}
:
Mode Score Error Units
enumMap avgt 23.487 ± 0.694 ns/op
hashMap avgt 67.480 ± 2.395 ns/op
enumMap:·gc.alloc.rate.norm avgt 72.000 ± 0.001 B/op
hashMap:·gc.alloc.rate.norm avgt 256.000 ± 0.001 B/op
:
@Benchmark
public void hashMap(Data data, Blackhole bh) {
Map<String, Integer> map = data.hashMap;
for (String key : data.hashMapKeySet) {
bh.consume(map.get(key));
}
}
@Benchmark
public void enumMap(Data data, Blackhole bh) {
Map<ConstantsEnum, Integer> map = data.enumMap;
for (ConstantsEnum key : data.enumMapKeySet) {
bh.consume(map.get(key));
}
}
Mode Score Error Units
enumMap avgt 36.397 ± 3.080 ns/op
hashMap avgt 55.652 ± 4.375 ns/op
:
// org.springframework.aop.framework.CglibAopProxy
Map<String, Integer> map = new HashMap<>();
getCallbacks(Class<?> rootClass) {
Method[] methods = rootClass.getMethods();
for (intx = 0; x < methods.length; x++) {
map.put(methods[x].toString(), x); // <------
}
}
//
accept(Method method) {
String key = method.toString();
// key
}
: java.lang.reflect.Method.toString()
. ?
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MethodToStringBenchmark {
private Method method;
@Setup
public void setup() throws Exception {
method = getClass().getMethod("toString");
}
@Benchmark
public String methodToString() { return method.toString(); }
}
— method.toString()
:
"public java.lang.String java.lang.Object.toString()"
:
Mode Score Error Units
methodToString avgt 85,4 ± 1,3 ns/op
methodToString:·gc.alloc.rate.norm avgt 344,0 ± 0,0 B/op
, :
public class MethodToStringBenchmark {
private Method method;
@Setup
public void setup() throws Exception {
method = getClass().getMethod("getInstance");
}
@Benchmark
public String methodToString() { return method.toString(); }
MethodToStringBenchmark getInstance() throws ArrayIndexOutOfBoundsException {
return null;
}
}
:
Mode Score Error Units
methodToString avgt 199.765 ± 3.807 ns/op
methodToString:·gc.alloc.rate.norm avgt 1126.400 ± 9.817 B/op
:
"public tsypanov.strings.reflection.MethodToStringBenchmark tsypanov.strings.reflection.MethodToStringBenchmark.getInstance() throws java.lang.ArrayIndexOutOfBoundsException"
, enum
- . java.lang.reflect.Method
. , :
-
equals()
/hashCode()
- *
?
- :
public final class Method extends Executable {
@Override
@CallerSensitive
public void setAccessible(boolean flag) {
AccessibleObject.checkPermission();
if (flag) checkCanSetAccessible(Reflection.getCallerClass());
setAccessible0(flag);
}
}
, , , "!". Method.setAccessible()
equals()/hashCode()
.
:
java.lang.reflect.Method
Comparable
- -
Method
- ( )
"-" , String Method.
? , CglibAopProxy
:
@Configuration
public class AspectConfig {
@Bean
ServiceAspect serviceAspect() { return new ServiceAspect(); }
@Bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
AspectedService aspectedService() { return new AspectedServiceImpl(); }
@Bean
AbstractAutoProxyCreator proxyCreator() {
var factory = new AnnotationAwareAspectJAutoProxyCreator();
factory.setProxyTargetClass(true);
factory.setFrozen(true); // <---
return factory;
}
}
: - ( , ) 1 , 1 . , , "", "" (. ).
:
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class AspectPrototypeBenchmark {
private AnnotationConfigApplicationContext context;
@Setup
public void setUp() {
context = new AnnotationConfigApplicationContext(AspectConfig.class);
}
@Benchmark
public AspectedService getAdvisedBean() {
return context.getBean(AspectedService.class);
}
@TearDown
public void closeContext() { context.close(); }
}
:
Mode Score Error Units
before
getAdvisedBean avgt 14.024 ± 0.164 us/op
getAdvisedBean:·gc.alloc.rate.norm avgt 10983.307 ± 14.193 B/op
after
getAdvisedBean avgt 8.150 ± 0.202 us/op
getAdvisedBean:·gc.alloc.rate.norm avgt 7133.664 ± 5.594 B/op
, .
JDK ObjectStreamClass
, , — FieldReflectorKey
, .
public class ObjectStreamClass implements Serializable {
private static class Caches {
static final ConcurrentMap<FieldReflectorKey, Reference<?>> reflectors =
new ConcurrentHashMap<>();
}
private static class FieldReflectorKey extends WeakReference<Class<?>> {
private final String sigs;
private final int hash;
private final boolean nullClass;
// ...
}
: JDK-6996807 FieldReflectorKey hash code computation can be improved. : - . :
FieldReflectorKey(Class<?> cl, ObjectStreamField[] fields,
ReferenceQueue<Class<?>> queue)
{
super(cl, queue);
nullClass = (cl == null);
StringBuilder sbuf = new StringBuilder(); // <---- !!!
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
sbuf.append(f.getName()).append(f.getSignature());
}
sigs = sbuf.toString();
hash = System.identityHashCode(cl) + sigs.hashCode();
}
FieldReflectorKey(Class<?> cl, ObjectStreamField[] fields,
ReferenceQueue<Class<?>> queue)
{
super(cl, queue);
nullClass = (cl == null);
sigs = new String[2 * fields.length];
for (int i = 0, j = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
sigs[j++] = f.getName();
sigs[j++] = f.getSignature();
}
hash = System.identityHashCode(cl) + Arrays.hashCode(sigs);
}
SPECjvm2008:serial improves a little bit with this patch, and the allocation rate is down ~5%.
"" o.s.context.support.StaticMessageSource
:
public class StaticMessageSource extends AbstractMessageSource {
private final Map<String, String> messages = new HashMap<>();
@Override
protected String resolveCodeWithoutArguments(String code, Locale locale) {
return this.messages.get(code + '_' + locale.toString());
}
public void addMessage(String code, Locale locale, String msg) {
// ...
this.messages.put(code + '_' + locale.toString(), msg);
}
}
private final String code = "code1";
private final Locale locale = Locale.getDefault();
@Benchmark
public Object concatenation(Data data) {
return data.stringObjectMap.get(data.code + '_' + data.locale);
}
concatenation avgt 53.241 ± 1.494 ns/op
concatenation:·gc.alloc.rate.norm avgt 120.000 ± 0.001 B/op
— ,
@EqualsHashCode
@RequiredArgsConstructor
private static final class Key {
private final String code;
private final Locale locale;
}
:
Arrays.asList(code, locale);
// JDK
List.of(code, locale)
( Java 14)
private static record KeyRec(String code, Locale locale) {}
:
Mode Score Error Units
compositeKey avgt 6.065 ± 0.415 ns/op
concatenation avgt 53.241 ± 1.494 ns/op
list avgt 31.001 ± 1.621 ns/op
compositeKey:·gc.alloc.rate.norm avgt ≈ 10⁻⁶ B/op
concatenation:·gc.alloc.rate.norm avgt 120.000 ± 0.001 B/op
list:·gc.alloc.rate.norm avgt 80.000 ± 0.001 B/op
Mode Score Error Units
compositeKey avgt 6.065 ± 0.415 ns/op
mapInMap avgt 9.330 ± 1.010 ns/op
mapInMap:·gc.alloc.rate.norm avgt ≈ 10⁻⁵ B/op
compositeKey:·gc.alloc.rate.norm avgt ≈ 10⁻⁶ B/op
, :
JDK 14
Mode Score Error Units
compositeKey avgt 7.803 ± 0.647 ns/op
mapInMap avgt 9.330 ± 1.010 ns/op
record avgt 13.240 ± 0.691 ns/op
list avgt 37.316 ± 6.355 ns/op
concatenation avgt 69.781 ± 7.604 ns/op
compositeKey:·gc.alloc.rate.norm avgt 24.001 ± 0.001 B/op
mapInMap:·gc.alloc.rate.norm avgt ≈ 10⁻⁵ B/op
record:·gc.alloc.rate.norm avgt 24.001 ± 0.001 B/op
list:·gc.alloc.rate.norm avgt 105.602 ± 9.786 B/op
concatenation:·gc.alloc.rate.norm avgt 144.004 ± 0.001 B/op
-! - ! : — , , .
: — ,
– ( Arrays.asList()
/ List.of()
).
: ? , , : ? org.springframework.core.ResolvableType.toString()
:
StringBuilder result = new StringBuilder(this.resolved.getName());
if (hasGenerics()) {
result.append('<');
result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", "));
result.append('>');
}
return result.toString();
, 2:
1) hasGenerics()
2) hasGenerics()
this.resolved.getName()
StringBuilder
, —
, ( , . . ) , this.resolved.getName()
, , :
if (hasGenerics()) {
return this.resolved.getName()
+ '<'
+ StringUtils.arrayToDelimitedString(getGenerics(), ", ")
+ '>';
}
return this.resolved.getName();
: -
. :
private static String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
bytesToHexString
: , , StringBuilder
. ( ). ( ) ( p6spy):
public String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
int temp = (int) b & 0xFF;
sb.append(HEX_CHARS[temp / 16]);
sb.append(HEX_CHARS[temp % 16]);
}
return sb.toString();
}
StringBuilder
- , , , . :
public String toHexStringPatched(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
int temp = (int) b & 0xFF;
sb.append(HEX_CHARS[temp / 16]);
sb.append(HEX_CHARS[temp % 16]);
}
return sb.toString();
}
1 , , :
original avgt 4167,950 ± 82,704 us/op
patched avgt 3972,118 ± 34,817 us/op
original:·gc.alloc.rate.norm avgt 13631776,184 ± 0,005 B/op
patched:·gc.alloc.rate.norm avgt 8388664,173 ± 0,002 B/op
, :
@Override
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
: , , . StringBuilder
- :
public String toHexString(byte[] bytes) {
char[] result = new char[bytes.length * 2];
int idx = 0;
for (byte b : bytes) {
int temp = (int) b & 0xFF;
result[idx++] = HEX_CHARS[temp / 16];
result[idx++] = HEX_CHARS[temp % 16];
}
return new String(result);
}
:
original avgt 4167,950 ± 82,704 us/op
patched avgt 3972,118 ± 34,817 us/op
chars avgt 1377,829 ± 4,861 us/op
original:·gc.alloc.rate.norm avgt 13631776,184 ± 0,005 B/op
patched:·gc.alloc.rate.norm avgt 8388664,173 ± 0,002 B/op
chars:·gc.alloc.rate.norm avgt 6291512,057 ± 0,001 B/op
, JDK, :
original avgt 3813,358 ± 75,014 us/op
patched avgt 3733,343 ± 90,589 us/op
chars avgt 1377,829 ± 4,861 us/op
original:·gc.alloc.rate.norm avgt 6816056,159 ± 0,005 B/op
patched:·gc.alloc.rate.norm avgt 4194360,157 ± 0,006 B/op
chars:·gc.alloc.rate.norm avgt 6291512,057 ± 0,001 B/op <----
. :
abstract class AbstractStringBuilder implements Appendable, CharSequence {
byte[] value;
public AbstractStringBuilder append(char c) {
this.ensureCapacityInternal(this.count + 1);
if (this.isLatin1() && StringLatin1.canEncode(c)) {
this.value[this.count++] = (byte)c; // <-----
} else {
// ...
}
return this;
}
}
StringBuilder.append(char)
, ASCII ( ), , . , char
. JDK 9 , , char[]
byte[]
.
: :
: — .
, — , . :
//
String str = s1 + s2 + s3;
//
String str = new StringBuilder().append(str1).append(str2).append(str3).toString();
//
StringBuilder sb = new StringBuilder();
sb.append(str1);
sb.append(str2);
sb.append(str3);
String str = sb.toString();
private final String str1 = "1".repeat(10);
private final String str2 = "2".repeat(10);
private final String str3 = "3".repeat(10);
private final String str4 = "4".repeat(10);
private final String str5 = "5".repeat(10);
@Benchmark public String concatenation() { /*...*/ }
@Benchmark public String chainedAppend() { /*...*/ }
@Benchmark public String newLineAppend() { /*...*/ }
, :
Mode Score Error Units
chainedAppend avgt 33,973 ± 0,974 ns/op
concatenation avgt 36,189 ± 1,260 ns/op
newLineAppend avgt 71,083 ± 5,180 ns/op
chainedAppend:·gc.alloc.rate.norm avgt 96,000 ± 0,001 B/op
concatenation:·gc.alloc.rate.norm avgt 96,000 ± 0,001 B/op
newLineAppend:·gc.alloc.rate.norm avgt 272,000 ± 0,001 B/op
: StringBuilder
, , . : , , StringBuilder
-. .
. , / StringBuilder.append()
:
StringBuilder sb = new StringBuilder()
.append(str1)
.append(str2)
.append(str3);
if (smth) sb.append(str4);
return sb.append(str5).toString();
, ? , , :
Mode Score Error Units
chainedAppend avgt 33,973 ± 0,974 ns/op
concatenation avgt 36,189 ± 1,260 ns/op
newLineAppend avgt 71,083 ± 5,180 ns/op
tornAppend avgt 66,261 ± 2,095 ns/op
chainedAppend:·gc.alloc.rate.norm avgt 96,000 ± 0,001 B/op
concatenation:·gc.alloc.rate.norm avgt 96,000 ± 0,001 B/op
newLineAppend:·gc.alloc.rate.norm avgt 272,000 ± 0,001 B/op
tornAppend:·gc.alloc.rate.norm avgt 272,000 ± 0,001 B/op
: ( ResolvableType.toString()
). "" :
// o.s.a.interceptor.AbstractMonitoringInterceptor
String createInvocationTraceName(MethodInvocation invocation) {
StringBuilder sb = new StringBuilder(getPrefix()); // < ----
Method method = invocation.getMethod();
Class<?> clazz = method.getDeclaringClass();
if (logTargetClassInvocation && clazz.isInstance(invocation.getThis())) {
clazz = invocation.getThis().getClass();
}
sb.append(clazz.getName());
sb.append('.').append(method.getName());
sb.append(getSuffix());
return sb.toString();
}
: sb
, :
String createInvocationTraceName(MethodInvocation invocation) {
Method method = invocation.getMethod();
Class<?> clazz = method.getDeclaringClass();
if (logTargetClassInvocation && clazz.isInstance(invocation.getThis())) {
clazz = invocation.getThis().getClass();
}
StringBuilder sb = new StringBuilder(getPrefix()); // < ----
sb.append(clazz.getName());
sb.append('.').append(method.getName());
sb.append(getSuffix());
return sb.toString();
}
"" :
protected String createInvocationTraceName(MethodInvocation invocation) {
Method method = invocation.getMethod();
Class<?> clazz = method.getDeclaringClass();
if (logTargetClassInvocation && clazz.isInstance(invocation.getThis())) {
clazz = invocation.getThis().getClass();
}
return getPrefix() + clazz.getName() + '.' + method.getName() + getSuffix();
}
, . :
Mode Score Error Units
before avgt 97,273 ± 0,974 ns/op
after avgt 89,089 ± 1,260 ns/op
before:·gc.alloc.rate.norm avgt 728,000 ± 0,001 B/op
after:·gc.alloc.rate.norm avgt 728,000 ± 0,001 B/op
-, ! - , - . , :
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g", "-XX:+UseParallelGC"})
public class BrokenConcatenationBenchmark {
@Benchmark
public String slow(Data data) {
Class<? extends Data> clazz = data.clazz;
return "class " + clazz.getName();
}
@Benchmark
public String fast(Data data) {
Class<? extends Data> clazz = data.clazz;
String clazzName = clazz.getName();
return "class " + clazzName;
}
@State(Scope.Thread)
public static class Data {
Class<? extends Data> clazz = getClass();
@Setup
// explicitly load name via Class.getName0()
public void setup() { clazz.getName(); } <----
}
}
JDK-8043677. Class.getName()
:
public String getName() {
String name = this.name;
if (name == null) {
this.name = name = this.getName0();
}
return name;
}
private native String getName0();
: , . , setup()
, . , .
, , StackOverflow. apangin, . :
-. , . .
,Class.getName()
. , JIT , ,
if (name == null) { this.name = name = getName0(); }
. , , . , .
:
Mode Score Error Units
before avgt 97,273 ± 0,974 ns/op
after avgt 13,301 ± 0,411 ns/op
before:·gc.alloc.rate.norm avgt 728,000 ± 0,001 B/op
after:·gc.alloc.rate.norm avgt 280,000 ± 0,001 B/op
:
- = +
- JDK (<9)
: if-
— ASM, -. org.objectweb.asm.Type
:
void appendDescriptor(final Class<?> clazz, final StringBuilder sb) {
String name = clazz.getName();
for (int i = 0; i < name.length(); ++i) {
char car = name.charAt(i);
sb.append(car == '.' ? '/' : car);
}
sb.append(';');
}
: , , . . StringBuilder.append(char)
. , . — , , . :
void appendDescriptor(final Class<?> clazz, final StringBuilder sb) {
sb.append(clazz.getName().replace('.', '/'));
}
: . : String.replace(char, char)
, ( ).
java.lang.String
:
@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(value = Mode.AverageTime)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class CharacterReplaceBenchmark {
private final Class<?> klass = String.class;
@Benchmark
public StringBuilder manualReplace() {
return ineffective(klass, new StringBuilder());
}
@Benchmark
public StringBuilder stringReplace() {
return effective(klass, new StringBuilder());
}
}
:
Mode Score Error Units
manualReplace avgt 43,312 ± 1,767 ns/op
stringReplace avgt 30,741 ± 3,247 ns/op
manualReplace:·gc.alloc.rate.norm avgt 56,000 ± 0,001 B/op
stringReplace:·gc.alloc.rate.norm avgt 112,000 ± 0,001 B/op
, — . java.lang.String
klass
private final Class<?> klass = CharacterReplaceBenchmark.class;
:
Mode Score Error Units
manualReplace avgt 160,336 ± 2,628 ns/op
stringReplace avgt 67,258 ± 1,535 ns/op
manualReplace:·gc.alloc.rate.norm avgt 200,000 ± 0,001 B/op
stringReplace:·gc.alloc.rate.norm avgt 240,000 ± 0,001 B/op
2,5 , 20%.
private final Class<?> klass = org.springframework.objenesis.instantiator.perc.PercSerializationInstantiator.class;
String.replace(char, char)
, :
Mode Score Error Units
manualReplace avgt 212,368 ± 3,370 ns/op
stringReplace avgt 75,503 ± 1,028 ns/op
manualReplace:·gc.alloc.rate.norm avgt 360,000 ± 0,001 B/op
stringReplace:·gc.alloc.rate.norm avgt 272,000 ± 0,001 B/op
, StringBuilder
, - , :
// java.lang.AbstractStringBuilder
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0
? hugeCapacity(minCapacity)
: newCapacity;
}
:
java.lang.String 16 – 16
t.s.b.s.CharacterReplaceBenchmark 58 – 70
o.s.o.i.p.PercSerializationInstantiator 77 – 142
, — .
, , :
// com.intellij.internal.statistic.beans.ConvertUsagesUtil
char c = text.charAt(i);
switch (c) {
case GROUP_SEPARATOR:
case GROUPS_SEPARATOR:
case GROUP_VALUE_SEPARATOR:
case '\'':
case '\"':
case '=' :
escaped.append(' ');
break;
default:
escaped.append(c);
break;
}
String.replace(char, char)
, :
return text
.replace(GROUP_SEPARATOR, ' ')
.replace(GROUPS_SEPARATOR, ' ')
.replace(GROUP_VALUE_SEPARATOR, ' ')
.replace('\'', ' ')
.replace('\"', ' ')
.replace('=' , ' ');
( 1 ) 6 6 . / , :
- ConvertUsagesUtil.ensureProperKey()
- JdkUtil.quoteArg()
- EntityUtil.encode()
- SSHConfig.parseFileName()
:
- ,
- ,
- 99 100
- 1 100
StringJoiner:
lany Java 9-14: JDK-8054221, StringJoiner
-:
//
public final class StringJoiner {
private final String prefix;
private final String delimiter;
private final String suffix;
private StringBuilder value;
}
//
public final class StringJoiner {
private final String prefix;
private final String delimiter;
private final String suffix;
private String[] elts;
private int size;
private int len;
}
char[] chars = new char[len + addLen];
int k = getChars(prefix, chars, 0);
if (size > 0) {
k += getChars(elts[0], chars, k);
for (int i = 1; i < size; i++) {
k += getChars(delimiter, chars, k);
k += getChars(elts[i], chars, k);
}
}
k += getChars(suffix, chars, k);
return new String(chars);
StringJoiner
:
StringBuilder pathBuilder = new StringBuilder();
for (PathComponent pathComponent : pathComponents) {
pathBuilder.append(pathComponent.getPath());
}
return pathBuilder.toString();
StringJoiner pathBuilder = new StringJoiner("");
for (PathComponent pathComponent : pathComponents) {
pathBuilder.add(pathComponent.getPath());
}
return pathBuilder.toString();
, :
latin length Mode Score Error Units
sb true 10 avgt 122,2 ± 5,0 ns/op
sb true 100 avgt 463,5 ± 42,6 ns/op
sb true 1000 avgt 3446,6 ± 109,1 ns/op
sj true 10 avgt 141,1 ± 5,3 ns/op
sj true 100 avgt 356,0 ± 6,9 ns/op
sj true 1000 avgt 2522,1 ± 287,7 ns/op
sb false 10 avgt 229,8 ± 14,7 ns/op
sb false 100 avgt 932,4 ± 8,7 ns/op
sb false 1000 avgt 7456,4 ± 527,2 ns/op
sj false 10 avgt 192,6 ± 70,8 ns/op
sj false 100 avgt 577,7 ± 60,3 ns/op
sj false 1000 avgt 3541,9 ± 135,0 ns/op
sb:·gc.alloc.rate.norm true 10 avgt 512,0 ± 0,0 B/op
sb:·gc.alloc.rate.norm true 100 avgt 4376,0 ± 0,0 B/op
sb:·gc.alloc.rate.norm true 1000 avgt 41280,0 ± 0,0 B/op
sj:·gc.alloc.rate.norm true 10 avgt 536,0 ± 14,9 B/op
sj:·gc.alloc.rate.norm true 100 avgt 3232,0 ± 12,2 B/op
sj:·gc.alloc.rate.norm true 1000 avgt 30232,0 ± 12,2 B/op
sb:·gc.alloc.rate.norm false 10 avgt 1083,2 ± 7,3 B/op
sb:·gc.alloc.rate.norm false 100 avgt 9744,0 ± 0,0 B/op
sb:·gc.alloc.rate.norm false 1000 avgt 93448,0 ± 0,0 B/op
sj:·gc.alloc.rate.norm false 10 avgt 768,0 ± 12,2 B/op
sj:·gc.alloc.rate.norm false 100 avgt 5264,0 ± 0,0 B/op
sj:·gc.alloc.rate.norm false 1000 avgt 50264,0 ± 0,0 B/op
:
char[] chars = new char[len + addLen];
int k = getChars(prefix, chars, 0);
if (size > 0) {
k += getChars(elts[0], chars, k);
for (int i = 1; i < size; i++) {
k += getChars(delimiter, chars, k);
k += getChars(elts[i], chars, k);
}
}
k += getChars(suffix, chars, k);
return new String(chars);
char[] chars = new char[len + addLen]; // char[], byte[] ?!!
int k = getChars(prefix, chars, 0);
if (size > 0) {
k += getChars(elts[0], chars, k);
for (int i = 1; i < size; i++) {
k += getChars(delimiter, chars, k);
k += getChars(elts[i], chars, k);
}
}
k += getChars(suffix, chars, k);
return new String(chars);
. , : StringJoiner
java.util
, — java.lang
. StringBuider
- , StringJoiner
char[]
. .
:
-
map.get(/* new String */)
/map.put(/* new String */)
-
"_" + smth
- «+»,
StringBuilder
-
StringJoiner
-e
, .