当前位置: 首页 > 知识库问答 >
问题:

串联字符文字('x')与单个字符字符串文字(“x”)

濮阳国兴
2023-03-14

当我有一个字符串需要将一个字符连接到它的结尾时,我应该更喜欢s=....+']'超过%s=....+“]”是否有任何性能原因?

我知道数组字符串连接和字符串生成器,我并不是在询问一般情况下如何连接字符串的建议。

我也知道有些人会有冲动向我解释过早的优化,而且一般来说我不应该为这些小事情费心,请不要...

我之所以问这个问题,是因为从编码风格的偏好来看,我更倾向于使用后一个,但我觉得第一个应该表现得稍微好一点,因为知道所附加的只是一个字符,所以不需要像复制一个字符串时那样对这个字符进行任何内部循环。

更新

正如@scheintod所写的,这的确是一个理论上的问题,我需要做更多的事情来更好地理解java是如何工作的,而不是实际生活中“让我们再节省一微秒”的场景。也许我应该说得更清楚些。

我喜欢理解事情在“幕后”的工作方式,我发现它有时可以帮助我创建更好的代码。

如果它确实改变了我的代码到这样的程度,那么也许我的Q应该是在这个级别,询问它是否适合JIT做这样的事情,如果使用它是否更好。

也许我该看看编译过的字节码...[我将需要学习如何在java中实现...]

作为一个附带说明和示例,我甚至会考虑查看字节码--看看我的一篇关于优化ActionScript2.0-a字节码透视图的相当古老的博客文章--第一部分,它展示了知道代码编译成什么确实可以帮助您编写更好的代码。

共有1个答案

呼延宪
2023-03-14

除了分析这一点,我们还有另一种可能获得一些见解。我想把重点放在可能的速度差异上,而不是把它们移除的东西上。

因此,让我们从这个test类开始:

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:javac 1.7.0_55)编译了这个文件

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
}

对于a+“b”append使用ljava/lang/String字符串)参数调用,而对于a+“b”使用c(字符)参数调用。

因此编译不将字符串转换为char,而是将其保持为*。

现在查看AbstractStringBuilder,它包含我们使用的方法:

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;
}

方法实际调用的。

这里最昂贵的操作当然是ensurecapacity,但仅在达到限制的情况下(它将旧的StringBuffers char[]的数组复制到一个新的数组)。所以这对双方都是正确的,并没有真正的区别。

编辑:当然,这一切都是在JIT发挥魔力之前。请参阅Stephen C的答案。

编辑2:我一直在看Eclipse的编译器生成的字节码,它几乎是一样的。所以至少这两个编译器在结果上没有什么不同。

编辑2:现在有趣的部分

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

编辑4:

考虑一下。这是用于基准测试的循环:

    start = System.currentTimeMillis();
    for( int i=0; i<RUNS; i++ ){
        a1 = a + 'B';
    }
    end = System.currentTimeMillis();
    System.out.println( "a+'B': " + (end-start) + " ms" );

因此,我们实际上不仅仅是对我们关心的一件事进行基准测试,而是尽管java循环性能、对象创建性能和变量赋值性能。所以真正的速度差可能还要大一点。

 类似资料:
  • 对性能是否有影响? 不同Java版本的行为是否有差异?

  • 问题内容: 我有以下Ecma-Script-6代码 输出如下: 和 我已经能够在此处将字符串串联起来,那么使用模板文字的情形将是什么? 问题答案: 如果像问题示例中那样仅将模板文字与占位符(例如)一起使用,则结果与串联字符串相同。从主观上讲,它看起来更好并且更易于阅读,尤其是对于多行字符串或包含这两者的字符串,因为您不必再​​转义那些字符了。 可读性是一个很棒的功能,但是关于模板最有趣的是Tagg

  • 问题内容: 这是以前有关Java中的String初始化的一些问题的后续问题。 在用Java进行了一些小测试之后,我面临以下问题: 为什么我可以执行此语句 当str2一个String对象初始化为,但我不能调用方法toString()上str2?那么Java如何将空字符串对象和字符串文字串联起来? 顺便说一句,我还尝试将一个初始化为null和字符串文字的Integer连接起来,”a_literal_s

  • 问题内容: 我正在从事Java代码优化。我不清楚或符号之间的区别: 第2行和第3行有什么区别? 问题答案: 这种方法使用StringBuilder创建结果字符串 此方法仅调用的静态方法来获取int的String版本 依次调用

  • 简介 我只介绍了表和数,因为它们在Scheme中最为常用。然而,Scheme也有像字符(Character)、字符串(String)、符号(Symbol)、向量(Vector)等的其它数据类型,我将在11到14章节中介绍它们。 字符 在某个字符前添加#\来表明该物是一个字符。例如,#\a表示字符a。字符#\Space、#\Tab、#\Linefeed和#\Return分别代表空格(Space)、制

  • 问题内容: 以下语句, 产生输出。 但是,以下内容 产生。 区别在哪里? 问题答案: 您会因为操作符优先级和字符串转换的结合而看到此行为。 JLS 15.18.1 指出: 如果只有一个操作数表达式的类型为String,则对另一操作数执行字符串转换(第5.1.11节),以在运行时生成字符串。 因此,第一个表达式中的右侧操作数将隐式转换为字符串: 但是对于第二个表达式,必须将复合赋值运算符与一起考虑。