これは、JPoint-2020カンファレンスでの私の講演「ああ、これらの行」のテキストバージョンです。
読者の時間を無駄にしないために、すぐに「e」を点在させましょう。
記事の内容はなんですか?
文字列の効率的な(またはそうでない)使用に関する記事。
誰のための記事ですか?
生産性開発者とその共感者のための記事。
これはどこから来るのですか?
何かがプロジェクトコードに、何かがフレームワークとライブラリに含まれています。
何を、何を、何を測定したのですか?
- ベンチマークコードとテスト結果はGitHubで入手できます
- 測定に使用JMH1.23
- 測定は、Intel Core i7-7700を搭載した稼働中のマシンで実行されました(数値自体は重要ではなく、それらと明らかにされたパターンとの関係は重要です)
- デフォルトではJDK11が使用されていましたが、8と14も使用されていました(それぞれのページに明示的に記載されています)
- ベンチマークモード:平均実行時間+メモリ消費量(少ないほど良い)
java.langパッケージとその住民
Javaを使用している人java.lang
は、これが言語のコアであり、そこで変更を加える必要がある場合、言語が保守的であり、最も有用な改善のためにさえ、
a)絶対に何も壊さないという鉄の証拠が必要であるため、それらをプッシュすることは非常に困難です。
b)本当に必要です
: 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
, .