2015-02-05 29 views
11

Przechodziłem przez regułę PMD AppendCharacterWithChar. Podano Unikaj łączenia znaków w łańcuchy w StringBuffer.append.Używanie znaku zamiast łańcucha dla wartości pojedynczego znaku w łańcuchu znaków StringBuffer

StringBuffer sb = new StringBuffer(); 
    // Avoid this 
    sb.append("a"); 

    // use instead something like this 
    StringBuffer sb = new StringBuffer(); 
    sb.append('a'); 

Czy naprawdę muszę to PMD regułę? Czy istnieje duża różnica w wydajności między dwoma następującymi kodami?

String text = new StringBuffer().append("some string").append('c').toString(); 

String text = new StringBuffer().append("some string").append("c").toString(); 
+0

istnieje dobry powód, aby nie Concat znaków jako znaków czy to tylko ciekawość? – xmoex

+0

Wdrażam test PMD dla mojego projektu. Istnieje wiele miejsc, w których ludzie użyli pojedynczego 'string' zamiast' char' w 'StringBuffer' /' StringBuilder' append. Chciałem tylko wiedzieć, czy warto zadać mu ból, aby poprawić 295 naruszenie, czy powinienem po prostu zignorować tę zasadę. – Zeeshan

+0

, więc powodem jest leniwy? Moim zdaniem nie ma wymówki, aby nie robić tego we właściwy sposób :-) możesz użyć skryptu z wyrażeń regularnych, aby go łatwo naprawić, może twój IDE może to zrobić dla ciebie? https://regex101.com/r/yN3dE2/1 – xmoex

Odpowiedz

12

Dołączanie charakter jako char zawsze będzie szybsze niż dołączenie go jako String.

Ale czy różnica w wydajności ma znaczenie? Jeśli zrobisz to tylko raz, nie będzie. Jeśli jest w cyklu powtarzającym swoje ciało milion razy, to tak, może to mieć znaczenie.

Jeśli masz już znak podczas kompilacji, po prostu dołącz go jako znak. Jeśli jest przechowywany w zmiennej o numerze String, nie przejmuj się dostępem do niego, np. z String.charAt(0) lub na kilka innych sposobów, wystarczy po prostu dołączyć String.

przy bocznej Uwaga:

Favor klasa do StringBufferStringBuilder. StringBuilder jest szybszy, ponieważ jego metody nie są zsynchronizowane (w większości przypadków nie są potrzebne).

Na marginesie # 2:

Nie będzie to skompilować:

String text = new StringBuffer().append("some string").append('c'); 

append() powraca StringBuffer łańcuchowym. Trzeba zadzwonić toString() na nim:

String text = new StringBuffer().append("some string").append('c').toString(); 
+0

dziękuje, opuścił toString, poprawi to :) – Zeeshan

+0

Jak zwykle z tymi pytaniami: tak, może, może być wzrost wydajności. Zróbcie benchmark na swoim systemie, zdobądźcie dowód, a potem zdecydujcie, ile wysiłku warto włożyć do swojej sytuacji. – avalancha

2

Zobacz realizację każdego i porównać je:

public AbstractStringBuilder append(char c):

public AbstractStringBuilder append(char c) { 
    int newCount = count + 1; 
    if (newCount > value.length) 
     expandCapacity(newCount); 
    value[count++] = c; 
    return this; 
} 

public AbstractStringBuilder append(String str):

public AbstractStringBuilder append(String str) { 
    if (str == null) str = "null"; 
    int len = str.length(); 
    if (len == 0) return this; 
    int newCount = count + len; 
    if (newCount > value.length) 
     expandCapacity(newCount); 
    str.getChars(0, len, value, count); 
    count = newCount; 
    return this; 
} 

Który z nich jest preferujesz, kiedy masz możliwość korzystania z obu?

Jeśli mam 1000 linii, naprawdę wolę używać append(char c) dla lepszych osiągów, ale dla jednej linii to naprawdę nie ma znaczenia.

0

Tak jej prawo Unikać łączenie ciągów znaków, jak w StringBuffer.append bo gdy piszesz sb.append("a") oznacza tworzysz obiekt String o wartości a i nowy łańcuch oznacza nowy obiekt String i nowy obiekt String w Stringpool i oznacza, że ​​niepotrzebnie zakwaterowanie w przestrzeni sterty.

+1

'sb.append (" a ")' nie tworzy nowych ciągów. Literały łańcuchowe są interened. – icza

+0

, ale co, jeśli nie ma obiektu typu string z wartością "a"? czy utworzą nowy obiekt w puli ciągów? –

+0

Tak, jeśli nie jest jeszcze w puli, zostanie do niego dodany. Ale ponieważ literał 'String' jest częścią definicji klasy, może być dodany do puli' String', gdy klasa jest ładowana (zależnie od implementacji). – icza

3

Z ciekawości uruchomiłem mikro test porównawczy z jmh (w tym monitoring GC).Użycie ciągu jest minimalnie wolniejsze, ale różnica jest minimalna: około 5 ns (nanosekund) na inwokację i brak znaczących różnic w aktywności GC.

Jeśli nazywa append("c") zamiast append('c') milion razy, to byłoby dodać 5 ms do swojego programu.

wyniki Benchmark, w tym czasie GC - n oznacza początkową długość StringBuilder:

Benchmark        (n) Mode Cnt  Score  Error Units 
SO28344.appendChar      0 avgt 30 16.476 ± 0.331 ns/op 
SO28343294.appendChar:·gc.time   0 avgt 30 256.000    ms 
SO28343294.appendString     0 avgt 30 22.048 ± 0.345 ns/op 
SO28343294.appendString:·gc.time  0 avgt 30 220.000    ms 

SO28343294.appendChar     50 avgt 30 17.323 ± 0.967 ns/op 
SO28343294.appendChar:·gc.time   50 avgt 30 67.000    ms 
SO28343294.appendString    50 avgt 30 20.944 ± 1.466 ns/op 
SO28343294.appendString:·gc.time  50 avgt 30 74.000    ms 

SO28343294.appendChar    1000 avgt 30 58.396 ± 0.811 ns/op 
SO28343294.appendChar:·gc.time  1000 avgt 30 25.000    ms 
SO28343294.appendString    1000 avgt 30 64.572 ± 4.779 ns/op 
SO28343294.appendString:·gc.time  1000 avgt 30 24.000    ms 

Kod:

@State(Scope.Thread) 
@BenchmarkMode(Mode.AverageTime) 
public class SO28343294 { 

    @Param({"0", "50", "1000"}) int n; 
    Random r = new Random(); 
    StringBuilder sb; 
    String s; 
    char c; 

    @Setup(Level.Invocation) public void populate() { 
    sb = new StringBuilder(n + 5); 
    for (int i = 0; i < n; i++) { 
     sb.append((char) (r.nextInt(26) + 'a')); 
    } 
    c = (char) (r.nextInt(26) + 'a'); 
    s = new String(new char[] { c }); 
    } 

    @Benchmark public StringBuilder appendString() { 
    return sb.append(s); 
    } 

    @Benchmark public StringBuilder appendChar() { 
    return sb.append(c); 
    } 
}