答复: JAVA6可以使用字符串累加

把先前在论坛回复的一些帖打捞进来。这篇的原帖是:[url=http://www.iteye.com/topic/1040986]JAVA6可以使用字符串累加[/url]
下面是回复内容,带补充。

=================================================================

[quote="javabkb"]听过sajia老师讲的视频 记得1.4后的hotspot就有这个编译优化效果了[/quote]
<< 我肯定没有这样说过…这帖里说的东西都跟HotSpot(或者其它JVM实现)一点关系都没有,还在Java源码级编译器层次上…

[quote="javabkb"]但是一直有个问题,即使是编译优化了,用加号拼接的效率还是不如用stringbuilder的[/quote]
<< 这个也不对…要分情况说。前面也有说了,String的+本来就是用StringBuilder(JDK 5或以上)或StringBuffer(JDK 1.4或以下)实现的。
关键问题是:是不是同一个StringBuilder/StringBuffer。在循环里用+=之类的方式来拼接字符串的问题就出在每轮循环里都new了一个StringBuilder/StringBuffer来做拼接,然后toString()完就抛弃了,等下轮循环进来又再new一个。

[quote="sswh"]JDK6并没有特别优化。[/quote]
嗯,从Java字节码一层能看到的情况看,确实是如此的。从StringBuffer转换到StringBuilder也是JDK5的时候做的事情了,而不是JDK6才开始的。

让我演示一下某代码在JDK 1.0.2上编译出来的样子:
public class X {
public static void main(String[] args) {
String a = "alpha";
String b = "beta";
String c = "charlie";
String d = a + b + c;
}
}


Compiled from "X.java"
public class X extends java.lang.Object
SourceFile: "X.java"
minor version: 3
major version: 45
Constant pool:
const #1 = String #16; // alpha
const #2 = String #8; // beta
const #3 = String #22; // charlie
const #4 = class #17; // java/lang/Object
const #5 = class #10; // X
const #6 = Method #4.#7; // java/lang/Object."<init>":()V
const #7 = NameAndType #20:#23;// "<init>":()V
const #8 = Asciz beta;
const #9 = Asciz ConstantValue;
const #10 = Asciz X;
const #11 = Asciz Exceptions;
const #12 = Asciz LineNumberTable;
const #13 = Asciz SourceFile;
const #14 = Asciz LocalVariables;
const #15 = Asciz Code;
const #16 = Asciz alpha;
const #17 = Asciz java/lang/Object;
const #18 = Asciz main;
const #19 = Asciz ([Ljava/lang/String;)V;
const #20 = Asciz <init>;
const #21 = Asciz X.java;
const #22 = Asciz charlie;
const #23 = Asciz ()V;

{
public static void main(java.lang.String[]);
Code:
Stack=1, Locals=4, Args_size=1
0: ldc #1; //String alpha
2: astore_1
3: ldc #2; //String beta
5: astore_2
6: ldc #3; //String charlie
8: astore_3
9: return
LineNumberTable:
line 3: 0
line 4: 3
line 5: 6
line 2: 9


public X();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #6; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0


}

<< 留意一下Class文件的版本号,这个是用JDK 1.0.2里的javac编译的,如假包换。
看到了么,main()方法里的字节码根本就没有字符串拼接的动作发生,对吧?

然后用JDK 1.0.2的javac -O来编译:
Compiled from "X.java"
public class X extends java.lang.Object
SourceFile: "X.java"
minor version: 3
major version: 45
Constant pool:
const #1 = class #11; // java/lang/Object
const #2 = class #6; // X
const #3 = Method #1.#4; // java/lang/Object."<init>":()V
const #4 = NameAndType #14:#16;// "<init>":()V
const #5 = Asciz ConstantValue;
const #6 = Asciz X;
const #7 = Asciz Exceptions;
const #8 = Asciz SourceFile;
const #9 = Asciz LocalVariables;
const #10 = Asciz Code;
const #11 = Asciz java/lang/Object;
const #12 = Asciz main;
const #13 = Asciz ([Ljava/lang/String;)V;
const #14 = Asciz <init>;
const #15 = Asciz X.java;
const #16 = Asciz ()V;

{
public static void main(java.lang.String[]);
Code:
Stack=0, Locals=1, Args_size=1
0: return

public X();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #3; //Method java/lang/Object."<init>":()V
4: return

}


加了-O参数之后main()方法里啥也不剩了,只有个return。
这才是Java层的“优化”… :lol:

然则从Sun的JDK 1.3开始javac就忽略-O参数了。现在大家用JDK6里的javac就看不到这种效果。

================================================

换个例子,
public class Y {
public static void main(String[] args) {
String a = "alpha";
String b = "beta";
String c = "charlie";
String d = a + b + c;
System.out.println(d);
}
}


还是用JDK 1.0.2里的javac,不带-O参数来编译:
Compiled from "Y.java"
public class Y extends java.lang.Object
SourceFile: "Y.java"
minor version: 3
major version: 45
Constant pool:
const #1 = String #32; // alpha
const #2 = String #22; // beta
const #3 = String #45; // charlie
const #4 = class #35; // java/lang/StringBuffer
const #5 = class #36; // java/lang/Object
const #6 = class #25; // java/io/PrintStream
const #7 = class #24; // Y
const #8 = class #42; // java/lang/System
const #9 = Method #5.#18; // java/lang/Object."<init>":()V
const #10 = Method #4.#17; // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
const #11 = Field #8.#15; // java/lang/System.out:Ljava/io/PrintStream;
const #12 = Method #4.#18; // java/lang/StringBuffer."<init>":()V
const #13 = Method #6.#16; // java/io/PrintStream.println:(Ljava/lang/String;)V
const #14 = Method #4.#19; // java/lang/StringBuffer.toString:()Ljava/lang/String;
const #15 = NameAndType #33:#41;// out:Ljava/io/PrintStream;
const #16 = NameAndType #20:#34;// println:(Ljava/lang/String;)V
const #17 = NameAndType #44:#39;// append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
const #18 = NameAndType #40:#46;// "<init>":()V
const #19 = NameAndType #31:#21;// toString:()Ljava/lang/String;
const #20 = Asciz println;
const #21 = Asciz ()Ljava/lang/String;;
const #22 = Asciz beta;
const #23 = Asciz ConstantValue;
const #24 = Asciz Y;
const #25 = Asciz java/io/PrintStream;
const #26 = Asciz Exceptions;
const #27 = Asciz LineNumberTable;
const #28 = Asciz SourceFile;
const #29 = Asciz LocalVariables;
const #30 = Asciz Code;
const #31 = Asciz toString;
const #32 = Asciz alpha;
const #33 = Asciz out;
const #34 = Asciz (Ljava/lang/String;)V;
const #35 = Asciz java/lang/StringBuffer;
const #36 = Asciz java/lang/Object;
const #37 = Asciz main;
const #38 = Asciz ([Ljava/lang/String;)V;
const #39 = Asciz (Ljava/lang/String;)Ljava/lang/StringBuffer;;
const #40 = Asciz <init>;
const #41 = Asciz Ljava/io/PrintStream;;
const #42 = Asciz java/lang/System;
const #43 = Asciz Y.java;
const #44 = Asciz append;
const #45 = Asciz charlie;
const #46 = Asciz ()V;

{
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=5, Args_size=1
0: ldc #1; //String alpha
2: astore_1
3: ldc #2; //String beta
5: astore_2
6: ldc #3; //String charlie
8: astore_3
9: new #4; //class java/lang/StringBuffer
12: dup
13: invokespecial #12; //Method java/lang/StringBuffer."<init>":()V
16: aload_1
17: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
20: aload_2
21: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
24: aload_3
25: invokevirtual #10; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
28: invokevirtual #14; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
31: astore 4
33: getstatic #11; //Field java/lang/System.out:Ljava/io/PrintStream;
36: aload 4
38: invokevirtual #13; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
41: return
LineNumberTable:
line 3: 0
line 4: 3
line 5: 6
line 6: 9
line 7: 33
line 2: 41


public Y();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #9; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0


}


当时的Java就已经是用StringBuffer来实现字符串拼接(+)的了。
这也毫无出奇之处,在[url=http://java.sun.com/docs/books/jls/first_edition/html/15.doc.html#40226]Java语言规范[/url]里就有这么写:
[quote="Java语言规范第一版"]15.17.1.2 Optimization of String Concatenation

An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. [color=red]To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class (§20.13) or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.[/color]
For primitive objects, an implementation may also optimize away the creation of a wrapper object by converting directly from a primitive type to a string.[/quote]
顺带一提现在在JDK6里用的是Java语言规范第三版,而JDK7即将使用的是Java语言规范第四版(还没出)。

================================================

JVM的实现内部也有一些针对字符串拼接的优化的。但那些看Java字节码是看不出来的——在这个抽象层之下,屏蔽了“优化”这种细节。

例如说JRockit里有特制的jrockit.vm.StringMaker类专门用来优化字符串拼接,JRockit的JIT编译器会识别出StringBuilder的使用模式,发现符合条件的时候就把一些StringBuilder“悄悄的”转换为这种特别的StringMaker类来处理。

大家用得比较多的Oracle/Sun的HotSpot VM的比较新的版本里也有类似的优化,可以把符合条件的一些相邻的StringBuilder合并为一个,用于优化这种场景:
String a = x + y + z;
String b = a + x + y;

(假定变量a在后面就没有再被使用过了)
本来这段代码会被javac编译为等价于下面的代码:
String a = new StringBuilder().append(x).append(y).append(z).toString();
String b = new StringBuilder().append(a).append(x).append(y).toString();

而被HotSpot的JIT编译器优化后,会变成类似下面的样子:
String b = new StringBuilder().append(x).append(y).append(z).append(x).append(y).toString();

也就是把相邻的StringBuilder合并掉了。再次留意这个例子的前提是变量a在后面就没有被用过了,只有变量b在后面还有被用到。

控制这个优化的启动参数是OptimizeStringConcat,如果用Oracle JDK 6 update 21以上的版本的话可以试试对比开启它和关闭它的效果。

这才是“JVM做的优化”。字节码层面就能看出来的所谓“优化”根本还没到JVM那层。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值