我尝试了这个示例,以发现StringBuffer、StringBuilder和String在执行时间上的不同
经过尝试,我了解到StringBuffer和StringBuilder所用的时间更少,因为它不创建新对象。
作为字符串追加空字符串,也不创建任何对象,所以速度更快。
public class StringComparation {
public static void main(String[] args) {
int N = 100000;
long time;
// String Buffer
StringBuffer sb = new StringBuffer();
time = System.currentTimeMillis();
for (int i = N; i --> 0 ;) {
sb.append("a");
}
System.out.println("String Buffer - " + (System.currentTimeMillis() - time));
// String Builder
StringBuilder sbr = new StringBuilder();
time = System.currentTimeMillis();
for (int i = N; i --> 0 ;) {
sbr.append("a");
}
System.out.println("String Builder - " + (System.currentTimeMillis() - time));
// String Without String pool value
String s2 = new String();
time = System.currentTimeMillis();
for (int i = N; i --> 0 ;) {
s2 = s2 + "";
}
System.out.println("String Without String pool value - "
+ (System.currentTimeMillis() - time));
// String With new String pool Object
String s = new String();
time = System.currentTimeMillis();
for (int i = N; i --> 0 ;) {
s = s + "a";
}
System.out.println("String With new String pool Object - "
+ (System.currentTimeMillis() - time));
// String With already available String pool Object
String s1 = new String();
time = System.currentTimeMillis();
for (int i = N; i --> 0 ;) {
s1 = s1 + "a";
}
System.out.println("String With already available String pool Object - "
+ (System.currentTimeMillis() - time));
}
}
String Buffer - 43
String Builder - 16
String Without String pool value - 64
String With new String pool Object - 12659
String With already available String pool Object - 14258
鉴于您的最后两个测试是相同的,您实际上只有四个测试在这里。为了方便起见,我将它们重构为单独的方法,并删除了基准测试代码,因为没有必要理解这里发生了什么。
public static void stringBuilderTest(int iterations) {
final StringBuilder sb = new StringBuilder();
for (int i = iterations; i-- > 0;) {
sb.append("a");
}
}
public static void stringBufferTest(int iterations) {
final StringBuffer sb = new StringBuffer();
for (int i = iterations; i-- > 0;) {
sb.append("a");
}
}
public static void emptyStringConcatTest(int iterations) {
String s = new String();
for (int i = iterations; i-- > 0;) {
s += "";
}
}
public static void nonEmptyStringConcatTest(int iterations) {
String s = new String();
for (int i = iterations; i-- > 0;) {
s += "a";
}
}
我们已经知道StringBuilder版本的代码是四个版本中最快的。StringBuffer版本的速度更慢,因为它的所有操作都是同步的,这带来了StringBuilder无法避免的开销,因为它没有同步。
因此,我们感兴趣的两个方法是emptystringconcattest
和nonemptystringconcattest
。如果我们检查EmptyStringConcattest
编译版本的字节码,我们会看到以下内容:
public static void emptyStringConcatTest(int);
flags: ACC_PUBLIC, ACC_STATIC
LineNumberTable:
line 27: 0
line 28: 8
line 29: 17
line 31: 40
Code:
stack=2, locals=3, args_size=1
0: new #14 // class java/lang/String
3: dup
4: invokespecial #15 // Method java/lang/String."<init>":()V
7: astore_1
8: iload_0
9: istore_2
10: iload_2
11: iinc 2, -1
14: ifle 40
17: new #7 // class java/lang/StringBuilder
20: dup
21: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
24: aload_1
25: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: ldc #16 // String
30: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
33: invokevirtual #17 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36: astore_1
37: goto 10
40: return
LineNumberTable:
line 27: 0
line 28: 8
line 29: 17
line 31: 40
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 10
locals = [ class java/lang/String, int ]
frame_type = 250 /* chop */
offset_delta = 29
28: ldc #9 // String
非空字符串(注意小但重要的区别!):
28: ldc #9 // String a
关于字节码,首先要注意的是for
循环主体的结构:
10: iload_2
11: iinc 2, -1
14: ifle 40
17: new #7 // class java/lang/StringBuilder
20: dup
21: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
24: aload_1
25: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: ldc #16 // String
30: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
33: invokevirtual #17 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36: astore_1
37: goto 10
我们在这里实际上得到的是一个编译器优化
for (int i = iterations; i-- > 0;) {
s += "";
}
for (int i = iterations; i-- > 0;) {
s = new StringBuilder().append(s).append("").toString();
}
public StringBuilder append(String str) {
super.append(str);
return this;
}
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;
}
非空字符串参数触发backingchar[]
的边界检查,这可能会导致expandcapacity(int)
调整其大小,从而将原始数组复制到一个新的、更大的数组中(请注意,StringBuilder中的backing数组不是final
-它可以重新分配!)。完成后,我们调用string#getchars(int,int,char[],int)
,它执行更多的数组复制。数组复制的确切实现隐藏在本机代码中,所以我不打算到处寻找它们。
更复杂的是,我们创建并扔掉的对象数量可能足以触发JVM垃圾收集器的运行,这会带来更多的开销。
总之;与nonemptystringconcattest
等价物性能的巨大下降很大程度上是由于编译器进行了糟糕的“优化”。不要在循环内直接串联,以避免这种情况。
append key value 返回新字符串值的长度。
在下面的Java代码中将创建多少对象:
问题内容: 当我使用定义变量时,该字符串不会添加到字符串池中,对吗? 现在,当我定义另一个而不是时,我将其定义为突然。(或我的大学老师说)。这是为什么?是什么使这个字符串突然变成字符串池字符串? 问题答案: 当我用new定义一个StringBuffer变量时,该字符串没有添加到字符串池中,对吗? 创建a 根本不会创建a 。 现在,当我定义另一个StringBuffer而不是new时,我突然将其定义
问题内容: 在每篇文章中,“如何向JEditorPane附加字符串?”问题的答案。就像 我已经试过了: 结果,我得到了“终止时间:1000”,而没有“进程的分布:” 为什么会发生这种情况??? 问题答案: 我怀疑这是附加文本的推荐方法。这意味着每次更改某些文本时,都需要重新解析整个文档。人们之所以这样做,是因为他们不了解如何使用JEditorPane。包括我在内。 我更喜欢使用JTextPane然
被删除,因此也是另一个匹配字符串的一部分,不确定这是由于错误的regEx还是反字符类的错误应用。
问题内容: 我正在尝试采用一个字符串,并将其附加到列表中包含的每个字符串中,然后使用完成的字符串创建一个新列表。例: 我尝试了循环,并尝试了列表理解,但这是垃圾。一如既往的任何帮助,不胜感激。 问题答案: 最简单的方法是使用列表理解: 请注意,我避免使用内置名称,因为那样会掩盖或隐藏内置名称,这非常不好。 另外,如果您实际上不需要列表,而只需要一个迭代器,则生成器表达式可能会更高效(尽管在短列表中