连接char文字('x')与单个char字符串文字("x") [英] Concatenate char literal ('x') vs single char string literal ("x")

查看:62
本文介绍了连接char文字('x')与单个char字符串文字("x")的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我知道数组字符串连接和字符串构建器的知识,而且我一般不要求有关如何连接字符串的建议.

我还知道有些人会急于向我解释过早的优化,并且总的来说,我不应该为这样的小问题而烦恼,请不要...

我问是因为从编码风格的偏好出发,我宁愿使用稍后的代码,但是我觉得第一个字符应该表现得更好,因为知道要追加的内容只是单个字符,就不需要像复制单个字符串时那样对这个单个字符进行任何内部循环./p>

更新

正如@Scheintod所写,这确实是一个理论上的问题,它与我希望更好地理解java的工作原理以及在现实生活中让我们再节省一微秒"的情况更少有关.也许我应该说得更清楚些.

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

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

我不希望JIT为我使用 StringBuilder 而不是 String s ...因为我(可能是错误的)认为String构建器较重",所以一方面使Strings变得重",另一方面却使构建和修改字符串的速度更快.因此,我假设在某些情况下,使用 StringBuilder s的效率要比使用stings的效率低.(如果不是这种情况,则整个String类的实现都应更改为这样.就像 StringBuilder 的代码一样,并使用一些内部表示形式来表示实际的不可变字符串...-还是JIT正在做什么?-假设在一般情况下,最好不要让开发人员选择...)

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

也许我应该开始研究编译后的字节码... [我将需要学习如何在Java中执行此操作...]

作为旁注和为何我什至考虑查看字节码的示例-看看我的一个相当老的博客帖子,内容关于解决方案

除了对此进行概要分析外,我们还有另一种可能性来获得一些见解.我想着重于可能的速度差异,而不是着眼于再次消除它们的事物.

所以让我们从这个 Test 类开始:

 公共类测试{//不要对此进行优化public static volatile String A ="A String";公共静态void main(String [] args)引发异常{字符串a1 = A +"B";字符串a2 = A +'B';a1.equals(a2);}} 

我使用javac Test.java对此进行了编译(使用javac -v:javac 1.7.0_55)

使用javap -c Test.class,我们得到:

 从"Test.java"编译公开课测试{public static volatile java.lang.String A;公共Test();代码:0:aload_01:invokespecial#1//方法java/lang/Object.< init>" :()V4:返回公共静态void main(java.lang.String [])抛出java.lang.Exception;代码:0:新#2//类java/lang/StringBuilder3:dup4:invokespecial#3//方法java/lang/StringBuilder.< init>"::()V7:getstatic#4//字段A:Ljava/lang/String;10:invokevirtual#5//方法java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;13:ldc#6//字符串B15:invokevirtual#5//方法java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;18:invokevirtual#7//方法java/lang/StringBuilder.toString :()Ljava/lang/String;21:astore_122:新#2//类java/lang/StringBuilder25:dup26:invokespecial#3//方法java/lang/StringBuilder.< init>"::()V29:getstatic#4//字段A:Ljava/lang/String;32:invokevirtual#5//方法java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;35:bipush 6637:invokevirtual#8//方法java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;40:invokevirtual#7//方法java/lang/StringBuilder.toString :()Ljava/lang/String;43:astore_244:aload_145:aload_246:invokevirtual#9//方法java/lang/String.equals:(Ljava/lang/Object;)Z49:流行50:返回静止的 {};代码:0:ldc#10//字符串A字符串2:putstatic#4//字段A:Ljava/lang/String;5:返回} 

我们可以看到,涉及到两个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){guaranteeCapacityInternal(count + 1);值[count ++] = c;返回这个;} 

  public AbstractStringBuilder append(String str){如果(str == null)str ="null";int len = str.length();guaranteeCapacityInternal(count + len);str.getChars(0,len,value,count);计数+ = len;返回这个;} 

实际调用的方法.

这里最昂贵的操作当然是 ensureCapacity ,但是只有在达到限制的情况下(它会将旧的StringBuffers char []的数组副本复制到新的数组中).因此,这两者都是正确的,并没有真正的区别.

可以看到还有许多其他操作,但是真正的区别是 value [count ++] = c; str.getChars(0,len,value,count);

如果我们查看getChars,就会发现它全都归结为一个 System.arrayCopy ,这里用于将String复制到Buffer的数组,以及一些检查和其他方法调用.单个阵列访问.

所以我想说理论上使用 A +"B" 比使用 A +'B'要慢得多/code>.

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

当然,这一切都是在JIT做魔术之前完成的.参见斯蒂芬·C的答案.

我一直在看eclipse编译器生成的字节码,它几乎是相同的.因此,至少这两个编译器的结果没有不同.

现在有趣的部分

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

  a +"B":5096毫秒a +'B':4569毫秒a +'B':4384毫秒a +"B":5502毫秒a +"B":5395毫秒a +'B':4833毫秒a +'B':4601毫秒a +"B":5090毫秒a +"B":4766毫秒a +'B':4362毫秒a +'B':4249毫秒a +"B":5142毫秒a +"B":5022毫秒a +'B':4643毫秒a +'B':5222毫秒a +"B":5322毫秒 

平均为:

  a +'B':4608msa +"B":5167ms 

因此,即使在句法知识的真实基准世界中(hehe), a +'B'也比 a +"B" 快约10%> ...

...至少(免责声明)在我的系统我的编译器我的cpu 上,并且确实没有区别/在现实世界的程序中并不明显.除了原因,您有一段代码经常运行经常,并且您所有的应用程序性能都取决于此.但是,那么您可能一开始会做不同的事情.

关于它的思考.这是用于基准测试的循环:

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

因此,我们实际上不仅在基准测试我们关心的一件事,而且尽管Java循环性能,对象创建性能和对变量性能的赋值.因此,实际速度差异可能更大.

When I have a String that I need to concatenate a single char to its end, should I prefer s = .... + ']' over s = .... + "]" for any performance reason?

I know array string joining and of String builders, and I am NOT asking for suggestions on how to concatenate strings in general.

I also know some of would have the urge to explain to me about premature optimizations and that in general I should not bother with such minor stuff, please don't...

I am asking because from a coding style preference I would prefer to use the later, but it feels to me that the first one should perform marginally better because knowing that what is being appended is just a single char there is no need for any internal looping going over this single char as there might be when copying a single character string.

Update

As @Scheintod wrote this is indeed a theoretical Q and has to do more with my desire to better understand how java works and less with any real life "lets save another microsecond" scenario... maybe I should have said that more clearly.

I like understanding the way things work "behind the scenes" And I find that it can sometime help me create better code...

The truth - I was not thinking about compiler optimizations at all...

I would not have expected the JIT to use StringBuilders instead of Strings for me... Because I (possibly wrongly) think of String builders as being "heavier" then Strings on one hand but faster at building and modifying the strings on the other hand. So I would assume that in some cases using StringBuilders would be even less efficient then using stings... (if that was not the case then the entire String class should have had its implementation changed to be such as that of a StringBuilder and use some internal representation for actual immutable strings... - or is that what the JIT is sort of doing? - assuming that for the general case it would probably be better not to let the developer choose... )

If it Does change my code to such a degree then maybe My Q should have been at that level asking if its is appropriate for the JIT to do something like this and would it be better if it used.

maybe I should start looking at compiled byte code... [I will need to learn how to do that in java ...]

As a side note and example of why I would even consider looking at bytecode - have a look at a quite old blog post of mine about Optimizing Actionscript 2.0 - a bytecode perspective - Part I it shows that knowing what your code compiles into indeed can help you write better code.

解决方案

Besides profiling this we have another possibility to get some insights. I want to focus on the possible speed differences and not on the things which remove them again.

So lets start with this Test class:

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

    }

}

I compiled this with javac Test.java (using javac -v: javac 1.7.0_55)

Using javap -c Test.class we get:

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
}

We can see, that there are two StringBuilders involved (Lines 4, 22 ). So the first thing we discover is, that using + to concat Strings is effectively the same as using StringBuilder.

The second thing we can see here is that the StringBuilders both are called twice. First for appending the volatile variable (Lines 10, 32) and the second time for appending the constant part (Lines 15, 37)

In case of A + "B" append is called with a Ljava/lang/String (a String) argument while in case of A + 'B' it is called with an C (a char) argument.

So the compile does not convert String to char but leaves it as it is*.

Now looking in AbstractStringBuilder which contains the methods used we have:

public AbstractStringBuilder append(char c) {
    ensureCapacityInternal(count + 1);
    value[count++] = c;
    return this;
}

and

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

as the methods actually called.

The most expensive operations here is certainly ensureCapacity but only in case the limit is reached (it does an array copy of the old StringBuffers char[] into a new one). So this is true for both and makes no real difference.

As one can see there are numerous other operations which are done but the real distinction is between value[count++] = c; and str.getChars(0, len, value, count);

If we look in to getChars we see, that it all boils down to one System.arrayCopy which is used here to copy the String to the Buffer's array plus some checks and additional method calls vs. one single array access.

So I would say in theory using A + "B" is much slower than using A + 'B'.

I think in real execution it is slower, too. But to determine this we need to benchmark.

EDIT: Of cause this is all before the JIT does it's magic. See Stephen C's answer for that.

EDIT2: I've been looking at the bytecode which eclipse's compiler generated and it's nearly identical. So at least these two compilers don't differ in the outcome.

EDIT2: AND NOW THE FUN PART

The Benchmarks. This result is generated by running Loops 0..100M for a+'B' and a+"B" few times after a warmup:

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

averageing to:

a+'B': 4608ms
a+"B": 5167ms

So even in the real benchmark world of syntetic knowlege (hehe) a+'B' is about 10% faster than a+"B"...

... at least (disclaimer) on my system with my compiler and my cpu and it's really no difference / not noticeable in real world programms. Except of cause you have a piece of code you run realy often and all your application perfomance depends on that. But then you would probably do things different in the first place.

EDIT4:

Thinking about it. This is the loop used to benchmark:

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

so we're really not only benchmarking the one thing we care about but although java loop performance, object creation perfomance and assignment to variables performance. So the real speed difference may be even a little bigger.

这篇关于连接char文字('x')与单个char字符串文字("x")的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆