Java 串联字符文字(';x';)与单个字符字符串文字(";x";)
当我有一个字符串,我需要连接一个字符到它的结尾, 我应该更喜欢Java 串联字符文字(';x';)与单个字符字符串文字(";x";),java,string,char,string-concatenation,Java,String,Char,String Concatenation,当我有一个字符串,我需要连接一个字符到它的结尾, 我应该更喜欢s=..+']超过s=..+“]”是否出于性能原因 我知道数组字符串连接和字符串生成器, 我并不是在征求关于如何连接字符串的建议 我也知道有些人会迫不及待地向我解释过早的优化,一般来说,我不应该为这些小事情操心,请不要 我这样问是因为从编码风格的偏好来看,我更喜欢使用后者, 但是我觉得第一个应该稍微好一点,因为知道所附加的只是一个字符,所以不需要像复制单个字符串时那样在这个字符上进行任何内部循环 更新 正如@Scheintod所写的,
s=..+']代码>超过s=..+“]”
是否出于性能原因
我知道数组字符串连接和字符串生成器,
我并不是在征求关于如何连接字符串的建议
我也知道有些人会迫不及待地向我解释过早的优化,一般来说,我不应该为这些小事情操心,请不要
我这样问是因为从编码风格的偏好来看,我更喜欢使用后者,
但是我觉得第一个应该稍微好一点,因为知道所附加的只是一个字符,所以不需要像复制单个字符串时那样在这个字符上进行任何内部循环
更新
正如@Scheintod所写的,这确实是一个理论问题,我希望更好地理解java是如何工作的,而不是与任何现实生活中的“让我们再节省一微秒”场景有关。。。
也许我应该说得更清楚些
我喜欢理解“幕后”的工作方式,我发现它有时可以帮助我创建更好的代码
事实上-我根本没有考虑编译器优化
我没有想到JIT会为我使用StringBuilder
s而不是String
s。。。
因为我(可能是错误地)认为字符串生成器一方面比字符串“重”,但另一方面在构建和修改字符串方面更快。所以我假设在某些情况下,使用StringBuilder
s比使用stings效率更低。。。(如果不是这样,那么整个String类应该将其实现更改为StringBuilder
的实现,并对实际的不可变字符串使用一些内部表示…-或者JIT就是这样做的?-假设在一般情况下,最好不要让呃选择…)
如果它确实将我的代码更改到了这样的程度,那么我的Q可能应该在这个级别上询问JIT是否适合这样做,如果使用它会更好
也许我应该开始看编译后的字节码。。。[我需要学习如何在java中实现这一点…]
作为一个附带的注释和例子,我甚至会考虑查看字节码——看看我的一个相当老的博客文章,它表明,知道你的代码编译成什么可以帮助你写出更好的代码。
当我有一个字符串需要将一个字符连接到它的末尾时,我应该更喜欢s=....+']'超过s=…+“]”是否出于任何性能原因
这里实际上有两个问题:
问题1:是否存在性能差异
回答:这取决于
- 在某些情况下,可能是,这取决于JVM和/或字节码编译器。如果字节码编译器生成对
StringBuilder.append(char)
的调用,而不是StringBuilder.append(String)
的调用,那么您会期望前者更快。但是JIT编译器可以将这些方法视为“intrinics”,并使用一个字符(文字)字符串优化对append(String)
的调用
简言之,您需要在您的平台上对此进行基准测试以确保
- 在其他情况下,肯定没有区别。例如,这两个调用将被编译为相同的字节码序列,因为串联是一个常量表达式
这是由JLS保证的
问题2:您是否更喜欢一个版本
答复:
- 在一般意义上,这可能是一个过早的优化。如果您已经在应用程序级别分析了代码,并且确定代码片段对性能有可测量的影响,那么出于性能原因,您应该只选择一种形式而不是另一种形式
- 如果您已经分析了代码,那么使用Q1的答案作为指导
如果值得尝试优化代码段,那么在优化后重新运行基准测试/评测是至关重要的,以查看它是否有任何不同。你对什么是最快的直觉。。。你在网上读到的一些旧文章。。。这可能是非常错误的
除了分析这一点,我们还有另一种可能获得一些见解。我想把重点放在可能的速度差异上,而不是那些再次消除它们的东西上
因此,让我们从这个测试类开始:
public class Test {
// Do not optimize this
public static volatile String A = "A String";
public static void main( String [] args ) throws Exception {
String a1 = A + "B";
String a2 = A + 'B';
a1.equals( a2 );
}
}
我用javac Test.java编译了这个
(使用javac-v:javac1.7.0_55)
使用javap-c Test.class,我们得到:
Compiled from "Test.java"
public class Test {
public static volatile java.lang.String A;
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: getstatic #4 // Field A:Ljava/lang/String;
10: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
13: ldc #6 // String B
15: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: astore_1
22: new #2 // class java/lang/StringBuilder
25: dup
26: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
29: getstatic #4 // Field A:Ljava/lang/String;
32: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: bipush 66
37: invokevirtual #8 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
40: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
43: astore_2
44: aload_1
45: aload_2
46: invokevirtual #9 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
49: pop
50: return
static {};
Code:
0: ldc #10 // String A String
2: putstatic #4 // Field A:Ljava/lang/String;
5: return
}
及
正如实际调用的方法一样
这里最昂贵的操作当然是ensureCapacity
,但只有在达到限制的情况下(它将旧的StringBuffers char[]数组复制到新的StringBuffers中)。因此,这对双方都是正确的,没有真正的区别
正如我们所看到的,还有许多其他操作已经完成,但真正的区别在于value[count++]=c
和str.getChars(0,len,value,count)代码>
如果我们查看getChars,我们会发现,这一切归结为一个System.arrayCopy
,它在这里用于将字符串复制到缓冲区的数组,再加上一些检查和附加方法调用,而不是一个数组访问
所以我想说,理论上使用A+“B”
比使用A+“B”
慢得多
我认为在实际执行中,它也会慢一些。但要确定这一点,我们需要进行基准测试
编辑:
当然,这一切都是在JIT之前发生的,这很神奇。见Stephen C的答案
编辑2:
我一直在研究eclipse编译器生成的字节码,我
Compiled from "Test.java"
public class Test {
public static volatile java.lang.String A;
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: getstatic #4 // Field A:Ljava/lang/String;
10: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
13: ldc #6 // String B
15: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: astore_1
22: new #2 // class java/lang/StringBuilder
25: dup
26: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
29: getstatic #4 // Field A:Ljava/lang/String;
32: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: bipush 66
37: invokevirtual #8 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
40: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
43: astore_2
44: aload_1
45: aload_2
46: invokevirtual #9 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
49: pop
50: return
static {};
Code:
0: ldc #10 // String A String
2: putstatic #4 // Field A:Ljava/lang/String;
5: return
}
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
a+"B": 5096 ms
a+'B': 4569 ms
a+'B': 4384 ms
a+"B": 5502 ms
a+"B": 5395 ms
a+'B': 4833 ms
a+'B': 4601 ms
a+"B": 5090 ms
a+"B": 4766 ms
a+'B': 4362 ms
a+'B': 4249 ms
a+"B": 5142 ms
a+"B": 5022 ms
a+'B': 4643 ms
a+'B': 5222 ms
a+"B": 5322 ms
a+'B': 4608ms
a+"B": 5167ms
start = System.currentTimeMillis();
for( int i=0; i<RUNS; i++ ){
a1 = a + 'B';
}
end = System.currentTimeMillis();
System.out.println( "a+'B': " + (end-start) + " ms" );