将String作为变量连接到Character vs与其他String连接时有什么区别?

时间:2015-03-04 13:23:34

标签: java string performance operator-keyword

当我看到这样的东西(伪单线)时:

str1 + "a" + str2

是否比以下(伪1线)更差(或更好/相等)?

str1 + 'a' + str2

更新:更好的示例(由@QPaysTaxes提供),以减少有关原始示例的混淆。

我尝试了什么: 在过去的10年中编写Java的各种东西,但我从来没有真正看到引擎盖下的内容 - 例如我会假设第二个稍微“更快/更好”,因为没有为斜杠标志创建的String-Object和/或Java的垃圾收集器必须处理更少。 我曾经为Java证书做好准备,可能本来可以在那个时候更好地争论,但是我的日常业务似乎也是关于Java的“理论”也必须保持最新...我知道没有任何更好的解释而不是我假设应该使用indexOf('c')而不是indexOf("C"),我想知道字符串连接是否相同。

我也搜索了一下,但由于我的标题可能暗示我不太好描述我正在寻找的没有例子。对此我很抱歉,这个差点的可能性只是产生了重复。

我会尝试: 基于这里接受的答案String concatenation: concat() vs "+" operator,我希望能够开始看到幕后的内容,并且有一天能够争辩/回答这些深刻的问题。

7 个答案:

答案 0 :(得分:16)

  

基于此处接受的答案,我希望能够有一个开始   看看引擎盖下的什么。

让我们看一下String与字符串联时生成的字节码:

String str1 = "a" + "test";
String str2 = 'a' + "test";

0: ldc           #2                  // String atest
2: astore_1
3: ldc           #2                  // String atest
5: astore_2

如您所见,没有区别,编译器会将其转换为相同的字节码。

现在让我们看看在将一个Character连接到一个String变量时生成的字节码。

String str1 = "a" + str3; //str3 is a String
String str2 = 'a' + str3; 

 7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
10: ldc           #5                  // String a
12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: new           #3                  // class java/lang/StringBuilder
26: dup
27: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
30: bipush        97
32: invokevirtual #8                  // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
35: aload_1
36: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

正如你所看到的,有一点不同。

10: ldc           #5                  // String a

ldc将常量池(String,int或float)中的常量#index推送到堆栈上。

因此,如果您直接与变量连接,连接一个字符将生成更少的字节码,这就是引擎

现在针对性能问题,除非您在运行程序时指定使用{{1}禁用JIT编译器,否则这不会代表任何显着的性能差异,因为JIT编译器优化了最多的临时对象。 }}

答案 1 :(得分:13)

我更喜欢使用"a"代替'a'来确保结果为String

考虑一下:

public static void main(String... args) {
    String s = "foo";
    int i = 1;
    Object arg = s + '/' + i;
    log(arg);
}

private static void log(Object... args) {
    MessageFormat format = new MessageFormat("bar {0}");
    String message = format.format(args);
    System.out.println(message); // or write to a log or something
}

假设您决定不再需要s消息,并将main方法中的第三行更改为:

Object arg = '/' + i;

然后arg将只包含一个数字,因为char + int不会连接,而是添加值。

答案 2 :(得分:5)

如果你构建一个文件名,你肯定会在之后使用它。在大多数情况下,这涉及访问物理媒体,这种情况比连接字符串的任何错误都要慢。因此,做什么是可维护的,不要担心在这种特殊情况下的性能。

构建文件名时的建议是使用File类或Path,它会自动确保路径分隔符正确。

编辑:正如您在评论中指出的那样,您的问题与一般情况有关。只要看看来源。 StringBuilder.append(String)最终在System.arraycopy()中执行String.getChars(),而StringBuilder.append(char)直接复制单个字符。所以从理论上讲,StringBuilder.append(char)会更快。

但是,你必须对此进行基准测试,看看它在实践中是否有任何不同。

答案 3 :(得分:3)

我不确定其中任何一个选项在性能方面是否更好,但我可以考虑另一个需要考虑的问题,那就是第一个代码段。

如果附加基元而不是这些基元的String表示,编译器可以更好地保护您免受打字错误。

考虑:

String plus10 = "plus" + 10;

如果您错误输入

String plus10 =  "plus" + 1O;

编译器会给你一个错误。

另一方面,如果您键入

String plus10 =  "plus" + "1O";

编译器没有问题。

附加char s

同样如此
String plus = "x" + '++' + "y";

不会编译

String plus = "x" + "++" +  "y";

将通过编译。

当然最好使用常量而不是硬编码值(并附加到StringBuilder而不是使用String连接),但即使对于常量,我也更喜欢原始类型字符串,因为它们可以为您提供更多级别的错误保护。

答案 4 :(得分:3)

实际上,性能没有任何显着差异。平均值将需要相同的时间来进行字符串连接。

但是,内部Java编译器在编译时用+替换StringBuilder运算符。

因此,当使用+运算符和char时,编译器会在内部将其转换为StringBuilder并使用.append(char)。字符串会发生同样的情况,但不同之处在于它会使用.append(String)

正如我上面提到的,平均值没有差别。简单测试将显示时间差接近于0.所以这实际上是可读性问题。从可读性的角度来看,如果你专注于字符串,最好保持类型相同,甚至对单个字符使用String,而不是char

答案 5 :(得分:3)

查看源代码通常有助于了解正在发生的事情。

String s = s1 + s2

将执行:

String s = new StringBuilder(s1).append(s2).toString();

现在查看类StringBuilder的append(char)和append(string)的源代码:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/AbstractStringBuilder.java#AbstractStringBuilder.append%28char%29

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/AbstractStringBuilder.java#AbstractStringBuilder.append%28java.lang.String%29

您将看到append(string)执行更多检查以查看字符串是null还是空。但是,您可能不会注意到任何差异。

答案 6 :(得分:2)

这就是幕后的内容:String str = s1 + "/";基本上创建了2个新的独立String对象(strnew String("/"))

这对于小型软件来说没有问题,但是如果要创建2个String对象(请记住:对象在堆栈中保留1个条目以及保留在堆中的内容),请考虑内存方式n&gt; 500.000个数据库条目。

使用单引号(如String str = s1 + '/')将完全导致另一个进程。 '/'代表在引号之间写入单个字符的数字ASCii字符表示值。此操作具有常量( O(1))运行时(想想即时数组访问),并且自然会比创建和引用对象更快。

正如很多人已经建议的那样,使用StringBuilder对象进行字符串连接比使用+运算符构建字符串要容易得多。