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

Posted

技术标签:

【中文标题】连接字符文字(\'x\')与单个字符字符串文字(“x”)【英文标题】:Concatenate char literal ('x') vs single char string literal ("x")连接字符文字('x')与单个字符字符串文字(“x”) 【发布时间】:2014-09-11 15:04:18 【问题描述】:

当我有一个字符串需要将一个字符连接到它的末尾时, 出于任何性能原因,我是否应该更喜欢 s = .... + ']' 而不是 s = .... + "]"

我知道数组字符串连接和字符串构建器, 而且我不是在寻求有关如何连接字符串的建议。

我也知道有些人很想向我解释过早的优化,一般来说我不应该为这些小事烦恼,请不要...

我问是因为从编码风格偏好我更愿意使用后者, 但在我看来,第一个应该稍微好一点,因为知道附加的只是一个字符,因此不需要像复制单个字符串时那样对这个单个字符进行任何内部循环。

更新

正如@Scheintod 所写,这确实是一个理论上的问题,并且必须更多地满足我的愿望,以更好地了解 java 的工作原理,而不是任何现实生活中的“让我们节省另一个微秒”场景...... 也许我应该说得更清楚。

我喜欢了解“幕后”的工作方式,而且我发现它有时可以帮助我创建更好的代码...

真相 - 我根本没有考虑编译器优化...

我没想到 JIT 会为我使用 StringBuilders 而不是 Strings... 因为我(可能错误地)一方面认为字符串构建器比字符串“更重”,但另一方面在构建和修改字符串方面更快。所以我会假设在某些情况下使用StringBuilders 会比使用 stings 效率更低......(如果不是这种情况,那么整个 String 类应该将其实现更改为 @ 987654327@ 并为实际的不可变字符串使用一些内部表示...... - 或者 JIT 正在做什么? - 假设对于一般情况,最好不要让开发人员选择......)

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

也许我应该开始查看编译后的字节码...[我需要学习如何在 java 中做到这一点...]

作为旁注和为什么我什至会考虑查看字节码的示例 - 看看我关于 Optimizing Actionscript 2.0 - a bytecode perspective - Part I 的一篇相当古老的博客文章,它表明知道你的代码编译成什么确实可以帮助你编写更好的代码.

【问题讨论】:

您为什么不尝试对此进行基准测试? 字符在加入前隐式转换为字符串,没有区别。您确实要求“过早优化”答案,但您不希望我们告诉您... @Orion 运行时解析字符串是什么? 对此表示赞同,因为我认为这是一个有趣的理论问题。我明白为什么作者不想为此获得实用建议。 我认为答案将涉及编译器(?)生成的任一替代代码,并在这很重要时深入研究情况。为我们提供有关 Java 工作原理的更深入见解。 【参考方案1】:

当我有一个需要将单个字符连接到其末尾的字符串时,出于任何性能原因,我是否应该更喜欢 s = .... + ']' 而不是 s = .... + "]"?

这里其实有两个问题:

Q1:有性能差异吗?

答案:这取决于...

在某些情况下,可能是的,这取决于 JVM 和/或字节码编译器。如果字节码编译器生成对StringBuilder.append(char) 而不是StringBuilder.append(String) 的调用,那么您会期望前者更快。但 JIT 编译器可以将这些方法视为“内在”,并使用单字符(文字)字符串优化对 append(String) 的调用。

简而言之,您需要在您的平台上对此进行基准测试以确保这一点。

在其他情况下,绝对没有区别。例如,这两个调用将编译相同的字节码序列,因为连接是一个常量表达式

    System.out.println("234" + "]");

    System.out.println("234" + ']');

这是由 JLS 保证的。

Q2:您是否更喜欢一个版本?

答案:

一般来说,这很可能是过早的优化。如果您在应用程序级别分析了您的代码并确定代码 sn-p 对性能有可衡量的影响,则您应该只选择一种形式而不是另一种形式。出于性能原因 .

如果您已对代码进行了概要分析,请使用 Q1 的答案作为指导。

如果值得尝试优化 sn-p,那么您必须在优化后重新运行基准测试/分析,看看它是否有任何不同。你对什么是最快的直觉......以及你在互联网上的一些旧文章中读到的......可能是非常错误的。

【讨论】:

【参考方案2】:

除了对此进行分析之外,我们还有另一种可能获得一些见解。我想关注可能的速度差异,而不是再次删除它们的东西。

让我们从这个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)

使用 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

我们可以看到,涉及到两个 StringBuilder(第 4、22 行)。所以我们发现的第一件事是,使用+ 连接Strings 实际上与使用StringBuilder 相同。

我们在这里可以看到的第二件事是 StringBuilders 都被调用了两次。第一次用于附加 volatile 变量(第 10、32 行),第二次用于附加常量部分(第 15、37 行)

A + "B" 的情况下,append 使用Ljava/lang/String(字符串)参数调用,而在A + 'B' 的情况下,使用C(字符)参数调用。

所以编译不会将String转换为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[] 的数组副本复制到新的数组中)。所以这对两者都是正确的,并没有真正的区别。

正如人们所看到的,还有许多其他操作已经完成,但真正的区别在于 value[count++] = c;str.getChars(0, len, value, count);

如果我们查看 getChars,我们会发现,这一切都归结为一个 System.arrayCopy,它在这里用于将字符串复制到 Buffer 的数组,加上一些检查和额外的方法调用,而不是一个单一的数组访问。

所以我会说理论上使用A + "B" 比使用A + 'B' 慢得多。

认为在实际执行中它也更慢。但要确定这一点,我们需要进行基准测试。

编辑: 当然,这一切都是在 JIT 发挥作用之前发生的。请参阅 Stephen C 的回答。

编辑2: 我一直在查看 eclipse 编译器生成的字节码,它几乎是相同的。所以至少这两个编译器在结果上没有区别。

编辑2: 现在是有趣的部分

基准。此结果是通过在预热后为 a+'B'a+"B" 运行循环 0..100M 生成的:

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

所以即使在综合知识的真实基准世界中(呵呵)a+'B' 也比a+"B"大约 10%...

... 至少(免责声明)在 my system 上使用 my compilermy cpu 并且 真的没有区别 /在现实世界的程序中不明显。除了因为你有一段代码你经常运行,你所有的应用程序性能都取决于它。但是一开始你可能会做一些不同的事情。

EDIT4:

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

    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 循环性能、对象创建性能和变量赋值性能进行基准测试。所以真正的速度差异可能会更大一些。

【讨论】:

谢谢。这正是我正在寻找的分析类型。但是我不确定如何阅读您关于 JIT 的最后评论...它是否指的是可以在编译之前完成连接的场景?还是别的什么?如果从控制台读取 A 的值,您的分析会发生很大变化吗? 你怎么看eclipse生成的字节码? Eclipse 有它自己的编译器。通常,它生成的类放在Project Settings -&gt; Java Build Path -&gt; Source -&gt; Default output folder中配置的一些binbuild目录中使用javapjavac生成的.class文件相同 jit:许多可能的优化不是在编译时而是在运行代码时完成的。由于 Java 现在使用热点 jit,它们只在需要的地方完成。要说出那里发生了什么确切(以及可能发生的事情),我是错误的人,我什至不知道如何找出答案。有一点关于它:wikis.oracle.com/display/HotSpotInternals/PerformanceTechniques(无论如何它是一本好书) 在 Stephen C 的回答中,“内在”的意思是,jit 可以做比那里写的其他事情 :) 看到这个问题:***.com/questions/8526907/… 如果它真的这样做,我不知道。跨度>

以上是关于连接字符文字('x')与单个字符字符串文字(“x”)的主要内容,如果未能解决你的问题,请参考以下文章

Python字符串文字连接

如何将“字节”对象转换为 Pandas Dataframe、Python3.x 中的文字字符串?

swift中的文字字符串与`String`

C/C++:文字字符串中“\xNNN”格式的固有歧义

目前是不是有将两个或多个字符串文字类型连接到 TypeScript 中的单个字符串文字类型?

当前缀字符串与非前缀字符串相邻时,字符串文字连接失败?