鉴于以下 2 个 toString()
实现,首选哪一个:
public String toString(){
return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}
或者
public String toString(){
StringBuilder sb = new StringBuilder(100);
return sb.append("{a:").append(a)
.append(", b:").append(b)
.append(", c:").append(c)
.append("}")
.toString();
}
?
更重要的是,鉴于我们只有 3 个属性,它可能不会有什么不同,但是你会在什么时候从 +
concat 切换到 StringBuilder
?
StringBuilder
预先分配多少大小,否则如果空间不足,则必须将大小加倍通过创建一个新的 char[]
数组然后复制数据 - 这是昂贵的。您可以通过给出大小来作弊,然后就不需要创建此数组 - 因此,如果您认为您的字符串长度约为 100 个字符,那么您可以将 StringBuilder 设置为该大小,并且它永远不必在内部扩展。
版本 1 更可取,因为它更短且 the compiler will in fact turn it into version 2 - 没有任何性能差异。
更重要的是,考虑到我们只有 3 个属性,它可能不会有什么不同,但是你什么时候从 concat 切换到 builder 呢?
在你在循环中连接的时候——通常是编译器不能自己替换 StringBuilder
的时候。
关键是你是在一个地方写一个单一的串联还是随着时间的推移积累它。
对于您给出的示例,显式使用 StringBuilder 没有意义。 (查看第一个案例的编译代码。)
但是,如果您正在构建一个字符串,例如在循环内,请使用 StringBuilder。
澄清一下,假设 hugeArray 包含数千个字符串,代码如下:
...
String result = "";
for (String s : hugeArray) {
result = result + s;
}
与以下相比非常浪费时间和内存:
...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
sb.append(s);
}
String result = sb.toString();
result += s;
(在第一个示例中)
"{a:"+ a + ", b:" + b + ", c: " + c +"}";
在大多数情况下,您不会看到两种方法之间的实际差异,但很容易构建像这样的最坏情况:
public class Main
{
public static void main(String[] args)
{
long now = System.currentTimeMillis();
slow();
System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");
now = System.currentTimeMillis();
fast();
System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
}
private static void fast()
{
StringBuilder s = new StringBuilder();
for(int i=0;i<100000;i++)
s.append("*");
}
private static void slow()
{
String s = "";
for(int i=0;i<100000;i++)
s+="*";
}
}
输出是:
slow elapsed 11741 ms
fast elapsed 7 ms
问题是 += 附加到字符串会重建一个新字符串,因此它的成本与字符串的长度(两者的总和)成线性关系。
所以 - 对于你的问题:
第二种方法会更快,但它的可读性较差且难以维护。正如我所说,在您的具体情况下,您可能看不到差异。
+=
的看法是正确的,但原始示例是 +
序列,编译器将其转换为单个 string.concat
调用。你的结果不适用。
我更喜欢:
String.format( "{a: %s, b: %s, c: %s}", a, b, c );
...因为它简短易读。
除非您在重复计数非常高的循环中使用它并测量了性能差异,否则我不会针对速度进行优化。
我同意,如果你必须输出很多参数,这种形式可能会让人感到困惑(就像其中一条评论所说的那样)。在这种情况下,我会切换到更易读的形式(可能使用 apache-commons 的 ToStringBuilder - 取自 matt b 的答案)并再次忽略性能。
null
是安全的
从 Java 1.5 开始,用“+”和 StringBuilder.append() 进行简单的一行连接会生成完全相同的字节码。
所以为了代码的可读性,使用“+”。
2个例外:
多线程环境:StringBuffer
循环中的连接:StringBuilder/StringBuffer
我还与我的老板就是否使用 append 或 + 发生了冲突。因为他们正在使用 Append(我仍然无法弄清楚他们每次创建新对象时所说的)。所以我想做一些研发。虽然我喜欢 Michael Borgwardt 的解释,但我只是想展示一个解释,如果将来有人真的需要知道。
/**
*
* @author Perilbrain
*/
public class Appc {
public Appc() {
String x = "no name";
x += "I have Added a name" + "We May need few more names" + Appc.this;
x.concat(x);
// x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
//System.out.println(x);
}
public void Sb() {
StringBuilder sbb = new StringBuilder("no name");
sbb.append("I have Added a name");
sbb.append("We May need few more names");
sbb.append(Appc.this);
sbb.append(sbb.toString());
// System.out.println(sbb.toString());
}
}
上述类的反汇编结果为
.method public <init>()V //public Appc()
.limit stack 2
.limit locals 2
met001_begin: ; DATA XREF: met001_slot000i
.line 12
aload_0 ; met001_slot000
invokespecial java/lang/Object.<init>()V
.line 13
ldc "no name"
astore_1 ; met001_slot001
.line 14
met001_7: ; DATA XREF: met001_slot001i
new java/lang/StringBuilder //1st object of SB
dup
invokespecial java/lang/StringBuilder.<init>()V
aload_1 ; met001_slot001
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
ldc "I have Added a nameWe May need few more names"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
aload_0 ; met001_slot000
invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
astore_1 ; met001_slot001
.line 15
aload_1 ; met001_slot001
aload_1 ; met001_slot001
invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
pop
.line 18
return //no more SB created
met001_end: ; DATA XREF: met001_slot000i ...
; ===========================================================================
;met001_slot000 ; DATA XREF: <init>r ...
.var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001 ; DATA XREF: <init>+6w ...
.var 1 is x Ljava/lang/String; from met001_7 to met001_end
.end method
;44-1=44
; ---------------------------------------------------------------------------
; Segment type: Pure code
.method public Sb()V //public void Sb
.limit stack 3
.limit locals 2
met002_begin: ; DATA XREF: met002_slot000i
.line 21
new java/lang/StringBuilder
dup
ldc "no name"
invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
astore_1 ; met002_slot001
.line 22
met002_10: ; DATA XREF: met002_slot001i
aload_1 ; met002_slot001
ldc "I have Added a name"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 23
aload_1 ; met002_slot001
ldc "We May need few more names"
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 24
aload_1 ; met002_slot001
aload_0 ; met002_slot000
invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
pop
.line 25
aload_1 ; met002_slot001
aload_1 ; met002_slot001
invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
pop
.line 28
return
met002_end: ; DATA XREF: met002_slot000i ...
;met002_slot000 ; DATA XREF: Sb+25r
.var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001 ; DATA XREF: Sb+9w ...
.var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
.end method
;96-49=48
; ---------------------------------------------------------------------------
从上面的两个代码可以看出 Michael 是对的。在每种情况下,只创建了一个 SB 对象。
使用最新版本的 Java(1.8) 反汇编 (javap -c
) 显示了编译器引入的优化。 +
以及 sb.append()
将生成非常相似的代码。但是,如果我们在 for 循环中使用 +
,则值得检查其行为。
在 for 循环中使用 + 添加字符串
爪哇:
public String myCatPlus(String[] vals) {
String result = "";
for (String val : vals) {
result = result + val;
}
return result;
}
字节码:(for
循环摘录)
12: iload 5
14: iload 4
16: if_icmpge 51
19: aload_3
20: iload 5
22: aaload
23: astore 6
25: new #3 // class java/lang/StringBuilder
28: dup
29: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload 6
38: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc 5, 1
48: goto 12
使用 stringbuilder.append 添加字符串
爪哇:
public String myCatSb(String[] vals) {
StringBuilder sb = new StringBuilder();
for(String val : vals) {
sb.append(val);
}
return sb.toString();
}
ByteCdoe:(for
循环摘录)
17: iload 5
19: iload 4
21: if_icmpge 43
24: aload_3
25: iload 5
27: aaload
28: astore 6
30: aload_2
31: aload 6
33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc 5, 1
40: goto 17
43: aload_2
不过还是有一点明显的区别。在第一种情况下,使用 +
,为每个 for 循环迭代创建新的 StringBuilder
,并通过执行 toString()
调用(29 到 41)存储生成的结果。因此,在 for
循环中使用 +
运算符时,您正在生成真正不需要的中间字符串。
这取决于字符串的大小。
请参见下面的示例:
static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;
public static void main(String[] args) {
printBytecodeVersion();
printJavaVersion();
case1();//str.concat
case2();//+=
case3();//StringBuilder
}
static void case1() {
System.out.println("[str1.concat(str2)]");
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
String str = "";
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str = str.concat(UUID.randomUUID() + "---");
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}
static void case2() {
System.out.println("[str1+=str2]");
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
String str = "";
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str += UUID.randomUUID() + "---";
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}
static void case3() {
System.out.println("[str1.append(str2)]");
List<Long> savedTimes = new ArrayList();
long startTimeAll = System.currentTimeMillis();
StringBuilder str = new StringBuilder("");
for (int i = 0; i < MAX_ITERATIONS; i++) {
long startTime = System.currentTimeMillis();
str.append(UUID.randomUUID() + "---");
saveTime(savedTimes, startTime);
}
System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}
static void saveTime(List<Long> executionTimes, long startTime) {
executionTimes.add(System.currentTimeMillis() - startTime);
if (executionTimes.size() % CALC_AVG_EVERY == 0) {
out.println("average time for " + executionTimes.size() + " concatenations: "
+ NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
+ " ms avg");
executionTimes.clear();
}
}
输出:
java 字节码版本:8 java.version:1.8.0_144 [str1.concat(str2)] 10000 次连接的平均时间:0.096 毫秒 10000 次连接的平均时间:10000 次连接的平均时间:0.185 毫秒 10000 次连接的平均时间:0.327 毫秒10000 次连接:10000 次连接的平均时间为 0.501 毫秒:平均 0.656 毫秒创建长度的字符串:1950000 在 17745 毫秒内 [str1+=str2] 10000 次连接的平均时间:10000 次连接的平均时间为 0.21 毫秒:平均时间为 0.652 毫秒10000 次连接:10000 次连接的平均时间为 1.129 毫秒:10000 次连接的平均时间为 1.727 毫秒:2.302 毫秒平均创建长度的字符串:60279 毫秒内的 1950000 [str1.append(str2)] 10000 次连接的平均时间:0.002 毫秒平均10000 次连接的平均时间:0.002 毫秒 10000 次连接的平均时间:0.002 毫秒 10000 次连接的平均时间:10000 次连接的平均时间:0.002 毫秒 10000 次连接的平均时间:0.002 毫秒 平均创建长度的字符串:19500 00 在 100 毫秒内
随着字符串长度的增加,+=
和 .concat
的连接时间也会增加,后者效率更高,但仍然是非恒定的
这就是肯定需要 StringBuilder
的地方.
PS:我不认为 When to use StringBuilder in Java 真的与此重复。
这个问题讨论的是 toString()
,它在大多数情况下不会执行大字符串的连接。
2019 更新
自 java8
次以来,情况发生了一些变化。现在看来(java13),+=
的连接时间实际上与str.concat()
相同。但是 StringBuilder
连接时间仍然是恒定的。 (上面的原始帖子经过略微编辑以添加更详细的输出)
java 字节码版本:13 java.version:13.0.1 [str1.concat(str2)] 10000 次连接的平均时间:0.047 毫秒 10000 次连接的平均时间:0.1 毫秒 10000 次连接的平均时间:0.17 毫秒的平均时间10000 次连接:10000 次连接的平均时间为 0.255 毫秒:平均 0.336 毫秒创建长度的字符串:1950000 在 9147 毫秒内 [str1+=str2] 10000 次连接的平均时间:10000 次连接的平均时间:0.037 毫秒平均时间:0.097 毫秒的平均时间10000 次连接:10000 次连接的平均时间为 0.249 毫秒:10000 次连接的平均时间为 0.298 毫秒:10000 次连接的平均时间:0.326 毫秒平均创建长度的字符串:10191 毫秒内的 1950000 [str1.append(str2)] 10000 次连接的平均时间:0.001 毫秒平均10000 次连接的平均时间:0.001 毫秒 10000 次连接的平均时间:0.001 毫秒 10000 次连接的平均时间:10000 次连接的平均时间:0.001 毫秒 10000 次连接的平均时间:0.001 毫秒 平均创建长度的字符串:1950000 in 43 毫秒
值得注意的是,与 bytecode:8/java.version:8
相比,bytecode:8/java.version:13
组合具有良好的性能优势
toString()
中使用它的事实并没有消除连接一个巨大字符串的可能性(甚至一次)。我的示例中的循环只是表明,要连接的字符串越大,使用 .concat()
所需的时间就越长。
在 Java 9 中,版本 1 应该更快,因为它被转换为 invokedynamic
调用。更多详细信息可在 JEP-280 中找到:
这个想法是用对 java.lang.invoke.StringConcatFactory 的简单调用动态调用来替换整个 StringBuilder 追加舞蹈,这将接受需要连接的值。
出于性能原因,不鼓励使用 +=
(String
连接)。原因是:Java String
是不可变的,每次完成新的连接时,都会创建一个新的 String
(新的指纹与旧的指纹 in the String pool 不同)。创建新字符串会给 GC 带来压力并减慢程序速度:创建对象的成本很高。
下面的代码应该同时使它更加实用和清晰。
public static void main(String[] args)
{
// warming up
for(int i = 0; i < 100; i++)
RandomStringUtils.randomAlphanumeric(1024);
final StringBuilder appender = new StringBuilder();
for(int i = 0; i < 100; i++)
appender.append(RandomStringUtils.randomAlphanumeric(i));
// testing
for(int i = 1; i <= 10000; i*=10)
test(i);
}
public static void test(final int howMany)
{
List<String> samples = new ArrayList<>(howMany);
for(int i = 0; i < howMany; i++)
samples.add(RandomStringUtils.randomAlphabetic(128));
final StringBuilder builder = new StringBuilder();
long start = System.nanoTime();
for(String sample: samples)
builder.append(sample);
builder.toString();
long elapsed = System.nanoTime() - start;
System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);
String accumulator = "";
start = System.nanoTime();
for(String sample: samples)
accumulator += sample;
elapsed = System.nanoTime() - start;
System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);
start = System.nanoTime();
String newOne = null;
for(String sample: samples)
newOne = new String(sample);
elapsed = System.nanoTime() - start;
System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}
运行结果报告如下。
builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us
builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us
builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us
builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us
builder - 10000 - elapsed: 3364us
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us
不考虑 1 个连接的结果(JIT 还没有完成它的工作),即使是 10 个连接,性能损失也是相关的;对于数千个连接,差异是巨大的。
从这个非常快速的实验中吸取的经验教训(很容易用上面的代码重现):永远不要使用 +=
将字符串连接在一起,即使在需要一些连接的非常基本的情况下(如上所述,无论如何创建新字符串都是昂贵的,并且GC 上的压力)。
Apache Commons-Lang 有一个非常易于使用的 ToStringBuilder 类。它在处理附加逻辑以及格式化您希望 toString 的外观方面做得很好。
public void toString() {
ToStringBuilder tsb = new ToStringBuilder(this);
tsb.append("a", a);
tsb.append("b", b)
return tsb.toString();
}
将返回类似于 com.blah.YourClass@abc1321f[a=whatever, b=foo]
的输出。
或者以更简洁的形式使用链接:
public void toString() {
return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}
或者,如果您想使用反射来包含该类的每个字段:
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
如果需要,您还可以自定义 ToString 的样式。
我认为我们应该使用 StringBuilder 追加方法。原因是:
String 连接每次都会创建一个新的字符串对象(因为 String 是不可变对象),所以它会创建 3 个对象。使用 String builder 只会创建一个对象[StringBuilder 是可变的],然后再附加一个字符串。
尽可能使 toString 方法具有可读性!
在我的书中,唯一的例外是如果你能向我证明它消耗了大量资源:)(是的,这意味着分析)
另请注意,Java 5 编译器生成的代码比 Java 早期版本中使用的手写“StringBuffer”方法更快。如果您使用“+”,则此功能和未来的增强功能将免费提供。
当前的编译器是否仍然需要使用 StringBuilder 似乎存在一些争论。所以我想我会给我2美分的经验。
我有一个包含 10k 条记录的 JDBC
结果集(是的,我需要将所有这些记录放在一个批次中。)在我的机器上使用 + 运算符需要大约 5 分钟和 Java 1.8
。对于相同的查询,使用 stringBuilder.append("")
只需不到一秒的时间。
所以差别很大。在循环 StringBuilder
内要快得多。
这是我在 Java8 中检查的内容
使用字符串连接
使用 StringBuilder long time1 = System.currentTimeMillis();使用字符串连接(100000); System.out.println("使用StringConcatenation " + (System.currentTimeMillis() - time1) + " ms"); time1 = System.currentTimeMillis();使用StringBuilder(100000); System.out.println("usingStringBuilder" + (System.currentTimeMillis() - time1) + "ms");私有静态无效 usingStringBuilder(int n) { StringBuilder str = new StringBuilder(); for(int i=0;i
如果您对大量字符串使用字符串连接,那真是一场噩梦。
usingStringConcatenation 29321 ms
usingStringBuilder 2 ms
我认为这张图片对于比较所有使用 String
的类非常有用:
https://i.stack.imgur.com/Jvbvq.png
值得一提的是,作为 pointed out by @ZhekaKozlov,
+ 从 Java 9 开始更快,除非 JVM 不知道如何优化它(例如循环中的连接)。
我检查了以下代码的字节码(在 Java 17 中):
public class StringBM {
public String toStringPlus(String a) {
return "{a:" + a + ", b:" + ", c: " + "}";
}
public String toStringBuilder(String a) {
StringBuilder sb = new StringBuilder(100);
return sb.append("{a:").append(a)
.append(", b:")
.append(", c:")
.append("}")
.toString();
}
}
对于 toStringPlus
:
0: aload_1
1: invokedynamic #7, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
6: areturn
toStringBuilder
:
0: new #11 // class java/lang/StringBuilder
3: dup
4: bipush 100
6: invokespecial #13 // Method java/lang/StringBuilder."<init>":(I)V
9: astore_2
10: aload_2
11: ldc #16 // String {a:
13: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16: aload_1
17: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: ldc #22 // String , b:
22: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: ldc #24 // String , c:
27: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: ldc #26 // String }
32: invokevirtual #18 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: invokevirtual #28 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: areturn
+
版本只是调用动态函数 makeConcatWithConstants
并传入方法参数 {a:\u0001, b:, c: }(\u0001
是参数占位符)。
而 StringBuilder
版本必须以“诚实”的方式进行。
我想我们可以看到为什么 +
现在更快了。
我能否指出,如果您要遍历集合并使用 StringBuilder,您可能需要查看 Apache Commons Lang 和 StringUtils.join()(不同风格)?
无论性能如何,它都会让您不必创建 StringBuilders 和 for 循环,这似乎是第 100 万次。
使用“+”的性能明智的字符串连接成本更高,因为它必须制作一个全新的字符串副本,因为字符串在 java 中是不可变的。如果连接非常频繁,例如:在循环内,这将发挥特殊作用。当我尝试做这样的事情时,我的 IDEA 建议如下:
https://i.stack.imgur.com/Pbtde.jpg
通用规则:
在单个字符串分配中,使用字符串连接很好。
如果您要循环构建大量字符数据,请选择 StringBuffer。
在 String 上使用 += 总是比使用 StringBuffer 效率低,因此它应该敲响警钟 - 但在某些情况下,与可读性问题相比,获得的优化可以忽略不计,所以请使用你的常识。
这是围绕该主题的nice Jon Skeet blog。
对于像这样的简单字符串,我更喜欢使用
"string".concat("string").concat("string");
按顺序,我想说构造字符串的首选方法是使用 StringBuilder、String#concat(),然后是重载的 + 运算符。当处理大字符串时,StringBuilder 会显着提高性能,就像使用 + 运算符会大大降低性能一样(随着字符串大小的增加呈指数级降低)。使用 .concat() 的一个问题是它会抛出 NullPointerExceptions。
To increase the performance of repeated string concatenation, a Java compiler _may_ use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.
关键字是可能。鉴于这是官方可选的(尽管很可能已实施),我们不应该保护自己吗?