当将字符串作为变量与字符连接时,与将其与另一个字符串连接时有何区别?

22

当我看到像这样的东西(伪1行代码):

str1 + "a" + str2

它比以下的伪一行代码更差(或更好/相等)吗?

str1 + 'a' + str2

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

我的尝试: 在过去的10年中我尝试了各种方法来编程Java,但我从未真正看到底层是什么-例如,我会假设第二个稍微“更快/更好”,因为没有为斜杠符号创建String-Object(s),并且/或者Java的垃圾回收器处理的内容较少。我曾经为Java证书做过准备,在那个时候可能会有更好的争论,但似乎即使是我日常工作中关于Java的“理论”也必须保持最新...我知道除了我的假设之外没有更好的解释, indexOf('c') 应该被使用而不是indexOf("C"),我想知道字符串连接是否也适用于同样的情况。

我也进行了一些谷歌搜索,但是由于我的标题可能意味着我无法很好地描述我正在寻找的东西,我很抱歉这种残疾可能只产生了重复。

我将尝试: 基于接受的答案,这里String concatenation: concat() vs "+" operator,我希望能够有一个开始看到底层,并有一天能够论证/回答这样的问题。


3
更好的做法是使用File.separatorChar/File.separator,以实现跨操作系统的独立性。 - Necreaux
或者只需使用 new File(parent, filename)... - Axel
1
不要使用字符串连接运算符“+”来进行操作,因为每次操作都会创建一个新的字符串。而是应该使用 StringBuilder。 - riccardo.cardin
1
@JBA,看了一下StringBuilder的代码,我猜使用append('c')应该比使用append("C")更高效。因为StringBuilder的内部表示使用了一个char[],所以如果你使用了一个String,那么这个方法就必须进行某种类型的转换。从功能上讲,我认为这两种方法是相等的。 - riccardo.cardin
1
作为另一种示例,您可以将 str1 + 'a' + str2str1 + "a" + str2 直接进行比较;这样保持概念上的一致性,避免任何人根据隐含的用法提出答案。 - anon
显示剩余3条评论
7个回答

15
基于接受的答案,我希望能够有一个开始来看看引擎盖下面的东西。
让我们来看一下将字符串与字符连接时生成的字节码:
String str1 = "a" + "test";
String str2 = 'a' + "test";

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

正如你所看到的,没有任何区别,编译器将把它转换为相同的字节码。

现在让我们来看一下在将字符连接到字符串变量时生成的字节码。

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指令可以将常量池中的字符串、整数或浮点数值#index压入栈中。

因此,如果您直接连接一个变量,连接一个字符会生成更少的字节码,这就是底层实现。

至于性能问题,这不会产生任何显著的性能差异,因为JIT编译器优化了大多数临时对象,除非在运行程序时指定禁用JIT编译器使用-Djava.compiler=NONE


1
非常感谢您的回复。我想问一下,您是使用“javap -c”命令获取这些信息的吗?(我现在还在工作中,无法自己尝试 :/) - JBA
1
@JBA javap -c 可以工作,但我更喜欢使用“javap -v”来获取所有信息。 - Jean-François Savard
1
Java为什么在连接两个字符串时使用StringBuilder?我认为没有任何两个项目的组合比(s1 == null ? "" : s1).concat(s2)更适合StringBuilder。如果类库添加了一些静态的concat方法到String中,这些方法可以接受2、3或4个参数,那么只有当连接超过16个字符串时,StringBuilder才会“胜出”,即使连接16个或更多项,也可以通过一个接受String[]参数的String构造函数在代码大小和速度上被超越。 - supercat

12

我更喜欢使用"a"而不是'a',以确保结果是一个字符串

考虑一下这个例子:

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不是连接,而是加上它们的值。


4
如果您构建了一个文件名,那么您肯定会在之后使用它。在大多数情况下,这涉及访问物理介质,这比您错误地连接字符串要慢得多。因此,在这种特殊情况下,做可维护的事情,不要担心性能。

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

编辑:正如您在评论中指出的那样,您的问题是关于一般情况的。只需查看源代码即可。StringBuilder.append(String)最终会在String.getChars()中执行System.arraycopy(),而StringBuilder.append(char)直接复制单个字符。因此,从理论上讲,StringBuilder.append(char)将更快。

但是,您必须对此进行基准测试,以查看实际上是否有任何差异。


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。然而,您可能不会注意到任何区别。

3

我不确定这两个选项在性能方面哪个更好,但是我可以想到另一个需要考虑的问题,这会使第一个片段更优。

如果您附加原语而不是这些原语的字符串表示形式,则编译器可以更好地保护您免受拼写错误的影响。

请考虑以下内容:

String plus10 = "plus" + 10;

如果您输错了

String plus10 =  "plus" + 1O;

编译器会给您报错。
另一方面,如果您输入

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

编译器对此不会有问题。
对于附加字符也是一样。
String plus = "x" + '++' + "y";

如果代码中存在以下情况,代码将无法编译:

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

将通过编译。

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


3
实际上,在性能方面没有显著的区别。平均而言,进行字符串连接需要相同的时间。
然而,在编译时,Java编译器会使用StringBuilder内部替换“+”运算符。
因此,当使用char类型的"+"运算符时,编译器会将其在内部转换为StringBuilder并使用.append(char)。对于字符串,也会发生同样的情况,但是会使用.append(String)。
正如我之前提到的,平均而言并没有什么差异。简单的测试结果显示时间差异接近于0。所以这真正关键在于可读性。从可读性角度来看,如果要集中处理字符串,最好保持类型相同,并且即使只有一个字符,也应该使用String,而不是char。

这里有一个主题https://dev59.com/Omgu5IYBdhLWcg3wUlnt,讨论了+运算符。 - vtor

2
这是底层实现:String str = s1 + "/"; 实际上创建了两个新的独立的字符串对象(strnew String("/"))。

对于小型软件来说,这并不是问题,但是如果你要为n > 500.000个数据库条目创建2个字符串对象(请记住:对象在堆栈中保留1个条目以及保存在堆中的内容),从内存角度考虑一下。
使用单引号,例如 String str = s1 + '/',将导致另一个完全不同的过程。 '/' 代表任何单个字符在引号之间写入的数字ASCii字符表示值。此操作具有常量(O(1))运行时间(类似于即时数组访问),自然比创建和引用对象更快。
正如许多人已经建议的那样,使用StringBuilder对象进行字符串连接比使用+运算符构建字符串更加节省内存。

2
字符串字面量被池化,因此引用字面量只会创建一个对象。另外,Java中的char是UTF16编码。 - Radiodef

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接