在 StringBuffer append 中对单字符值使用字符而不是 String
Using character instead of String for single-character values in StringBuffer append
我正在通过 PMD 规则 AppendCharacterWithChar
。它说 Avoid concatenating characters as strings in StringBuffer.append.
StringBuffer sb = new StringBuffer();
// Avoid this
sb.append("a");
// use instead something like this
StringBuffer sb = new StringBuffer();
sb.append('a');
我真的需要这个 PMD 规则吗?下面两段代码性能差异大吗?
String text = new StringBuffer().append("some string").append('c').toString();
String text = new StringBuffer().append("some string").append("c").toString();
将字符附加为 char
总是比将其附加为 String
更快。
但是性能差异重要吗?如果你只做一次,它不会。如果它在一个循环中重复其 body 一百万次,那么是的,它可能很重要。
如果您在编译时已经有了该字符,只需将其附加为一个字符即可。如果它存储在 String
类型的变量中,请不要访问它,例如使用 String.charAt(0)
或其他一些方式,只需附加 String
.
旁注:
喜欢StringBuilder
class to StringBuffer
。 StringBuilder
更快,因为它的方法不是同步的(在大多数情况下你不需要同步)。
旁注 #2:
这不会编译:
String text = new StringBuffer().append("some string").append('c');
append()
returns StringBuffer
用于链接。你需要在上面调用 toString()
:
String text = new StringBuffer().append("some string").append('c').toString();
查看每个的实现并进行比较:
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;
}
当您可以选择同时使用两者时,您更喜欢哪一个?
如果我有 1000 行,我真的更喜欢使用 append(char c)
以获得更好的性能,但对于一行,这并不重要。
是的,它是正确的 避免在 StringBuffer.append 中将字符连接为字符串,因为每当您编写 sb.append("a")
时,都意味着您正在创建一个值为 [=11 的字符串对象=] 和 new String 意味着 Stringpool 中的新 String 对象和新 String 对象,这意味着不必要地 space 容纳在堆 space.
出于好奇,我 运行 使用 jmh(包括 GC 监控)进行了微基准测试。使用 String 稍微慢一点,但差异很小:每次调用大约 5 ns(纳秒)并且在 GC activity.
上没有显着差异
如果您调用 append("c")
而不是 append('c')
一百万次,您的程序会增加 5 毫秒。
基准测试结果,包括 gc 时间 - n
表示 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
代码:
@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);
}
}
我正在通过 PMD 规则 AppendCharacterWithChar
。它说 Avoid concatenating characters as strings in StringBuffer.append.
StringBuffer sb = new StringBuffer();
// Avoid this
sb.append("a");
// use instead something like this
StringBuffer sb = new StringBuffer();
sb.append('a');
我真的需要这个 PMD 规则吗?下面两段代码性能差异大吗?
String text = new StringBuffer().append("some string").append('c').toString();
String text = new StringBuffer().append("some string").append("c").toString();
将字符附加为 char
总是比将其附加为 String
更快。
但是性能差异重要吗?如果你只做一次,它不会。如果它在一个循环中重复其 body 一百万次,那么是的,它可能很重要。
如果您在编译时已经有了该字符,只需将其附加为一个字符即可。如果它存储在 String
类型的变量中,请不要访问它,例如使用 String.charAt(0)
或其他一些方式,只需附加 String
.
旁注:
喜欢StringBuilder
class to StringBuffer
。 StringBuilder
更快,因为它的方法不是同步的(在大多数情况下你不需要同步)。
旁注 #2:
这不会编译:
String text = new StringBuffer().append("some string").append('c');
append()
returns StringBuffer
用于链接。你需要在上面调用 toString()
:
String text = new StringBuffer().append("some string").append('c').toString();
查看每个的实现并进行比较:
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;
}
当您可以选择同时使用两者时,您更喜欢哪一个?
如果我有 1000 行,我真的更喜欢使用 append(char c)
以获得更好的性能,但对于一行,这并不重要。
是的,它是正确的 避免在 StringBuffer.append 中将字符连接为字符串,因为每当您编写 sb.append("a")
时,都意味着您正在创建一个值为 [=11 的字符串对象=] 和 new String 意味着 Stringpool 中的新 String 对象和新 String 对象,这意味着不必要地 space 容纳在堆 space.
出于好奇,我 运行 使用 jmh(包括 GC 监控)进行了微基准测试。使用 String 稍微慢一点,但差异很小:每次调用大约 5 ns(纳秒)并且在 GC activity.
上没有显着差异如果您调用 append("c")
而不是 append('c')
一百万次,您的程序会增加 5 毫秒。
基准测试结果,包括 gc 时间 - n
表示 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
代码:
@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);
}
}