Ah, diese Zeilen

Dies ist eine Textversion meines Vortrags "Ah, diese Zeilen" auf der JPoint-2020-Konferenz .

Um die Zeit der Leser nicht umsonst zu verschwenden, werden wir sofort das "e" markieren.



Um was geht es in dem Artikel?



Ein Artikel über die effiziente (oder nicht effiziente) Verwendung von Zeichenfolgen.



Für wen ist der Artikel?



Ein Artikel für Produktivitätsentwickler und ihre Sympathisanten.



Woher kommt das alles?



Etwas ist im Projektcode gefangen, etwas - in Frameworks und Bibliotheken.



Was, was und woran wurde gemessen?



  • Benchmark-Code und Testergebnisse sind auf GitHub verfügbar
  • zur Messung verwendet JMH 1.23
  • Die Messungen wurden auf einer Arbeitsmaschine mit einem Intel Core i7-7700 durchgeführt (die Zahlen selbst sind nicht wichtig, die Beziehungen zwischen ihnen und den aufgedeckten Mustern sind wichtig).
  • Standardmäßig wurde JDK 11 verwendet, aber auch 8 und 14 (auf den jeweiligen Seiten ausdrücklich angegeben).
  • Benchmark-Modus: durchschnittliche Ausführungszeit + Speicherverbrauch (weniger ist besser)


Das java.lang-Paket und seine Bewohner



Diejenigen, die mit Java arbeiten, wissen, dass java.lang- dies ist der Kern der Sprache, und wenn Sie dort Änderungen vornehmen müssen, ist es sehr schwierig, sie voranzutreiben, da die Sprache konservativ ist und für jede nützliche Verbesserung eiserne Beweise erforderlich sind, dass

a) sie definitiv nichts kaputt macht

b) du brauchst es wirklich



: 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 "" "", .





java.lang.String. :



  • []
  • [ ]
  • ,




, :



  • /


, : , , . , . , : (JEP 192). : ( ).



— - ( ), — . .





JLS 15.18.1 , [] , , .



:



  • : ,
  • :




(). , :



  • equals() / hashCode()
  • -
  • java.lang.Comparable ( TreeMap)
  • Object.equals()
  • obj.toString()


, - , 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


, 1 0 , . . ( ), . , . , , :



                                       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();


: StringBuilder- + ( ).



: -



. :



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();
}




. , , . , .

. Class.getName() .



:



                                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 . / , :





:



  • ,
  • ,
  • 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;
}


: StringBuilder.toString():



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


, .




All Articles