Ah essas falas

Esta é uma versão em texto da minha palestra "Ah, essas linhas" na conferência JPoint-2020 .

Para não perder o tempo dos leitores em vão, pontuaremos imediatamente o "e".



Sobre o que é o artigo?



Um artigo sobre eficiência (ou não) no uso de strings.



Para quem é o artigo?



Um artigo para desenvolvedores de produtividade e seus simpatizantes.



De onde vem tudo isso?



Algo está preso no código do projeto, algo - em estruturas e bibliotecas.



O quê, o quê e em que foi medido?



  • código de benchmark e resultados de teste estão disponíveis no GitHub
  • usado para medição JMH 1,23
  • as medições foram realizadas em uma máquina funcional com Intel Core i7-7700 (os números em si não são importantes, as relações entre eles e os padrões identificados são importantes)
  • JDK 11 foi usado por padrão, mas também 8 e 14 (explicitamente declarado nas respectivas páginas)
  • modo de benchmark: tempo médio de execução + consumo de memória (menos é melhor)


O pacote java.lang e seus habitantes



Aqueles que trabalham com Java sabem que java.lang- este é o núcleo da linguagem e se você precisar fazer mudanças lá, então é muito difícil empurrá-los, já que a linguagem é conservadora e para qualquer melhoria até mesmo a mais útil, é necessária

uma prova de ferro de que a) definitivamente não quebrará nada

b) você realmente precisa disso



: 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