锁定老帖子 主题:JAVA6可以使用字符串累加
精华帖 (0) :: 良好帖 (3) :: 新手帖 (0) :: 隐藏帖 (1)
|
|||||||
---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||
发表时间:2011-05-12
最后修改:2011-08-29
在JAVA6中,编译器会始终对字符串的累加操作做优化编译。 编译器将字符串累加编译成StringBuilder。
String aa = "aa"; String bb = "bb"; String cc = "cc"; String result = aa + bb + cc;
NEW StringBuilder DUP ALOAD 1: aa INVOKESTATIC String.valueOf(Object) : String INVOKESPECIAL StringBuilder.<init>(String) : void ALOAD 2: bb INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder ALOAD 3: cc INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder INVOKEVIRTUAL StringBuilder.toString() : String ASTORE 4: result 编译器将字符串累加编译成一个字符串
String s="aa"+"bb"+"cc";
LDC "aabbcc" 最佳实践:先拼接字符串再拼接变量从上可知第二种字符串累加的效率更高。 aa + "bb"+"cc" 效率小于 "bb"+"cc"+aa。 因为"bb"+"cc"会先编译为"bbcc"。而aa + "bb" + "cc"中出现变量,在变量后的字符串就会编译成StringBuilder。
String aa = "aa"; String result3 = aa + "bb"+"cc"; String result4 = "bb"+"cc"+aa;
L0 (0) LDC "aa" ASTORE 1: aa L1 (3) NEW StringBuilder DUP ALOAD 1: aa INVOKESTATIC String.valueOf(Object) : String INVOKESPECIAL StringBuilder.<init>(String) : void LDC "bb" INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder LDC "cc" INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder INVOKEVIRTUAL StringBuilder.toString() : String ASTORE 2: result3 L2 (15) NEW StringBuilder DUP LDC "bbcc" INVOKESPECIAL StringBuilder.<init>(String) : void ALOAD 1: aa INVOKEVIRTUAL StringBuilder.append(String) : StringBuilder INVOKEVIRTUAL StringBuilder.toString() : String ASTORE 3: result4 L3 (24) RETURN L4 (26) 最佳实践:在循环中不要使用累加操作因为StringBuilder是在循环内创建的。建议手动在外面创建StringBuilder。 StringBuilder eqPhone = new StringBuilder(); // 将#转换为- for (String phoneNum : wwPhone.split("#")) { if (StringUtil.isNotBlank(phoneNum)) { eqPhone.append(phoneNum).append("-"); } } 解释如下:上面是源代码,下面是字节码分析
public static void a() { String s1="1"; for (int i = 0; i <10; i++) { s1+="2"; } } 0 ldc <String "1"> [56]//装载常量“1”到操作栈 2 astore_0 [s1] //存储常量到第一个位置 3 iconst_0 //装载常量0 4 istore_1 [i]//从操作栈存储常量到局部变量的第二个位置 5 goto 31//无条件跳转到31行 8 new java.lang.StringBuilder [54]//在循环内创建对象 11 dup//复制引用到栈 12 aload_0 [s1]//装载变量 13 invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [77]//调用valueOf静态方法 16 invokespecial java.lang.StringBuilder(java.lang.String) [58]调用StringBuilder构造方法 19 ldc <String "2"> [69]//装载常量2到操作栈 21 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [61] 24 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [65] 27 astore_0 [s1]//保存变量 28 iinc 1 1 [i]//第二个局部变量(也就是i)自增1 31 iload_1 [i]//从局部变量取第2个数,装载到操作栈 32 bipush 10 // 装载常量到操作栈 34 if_icmplt 8// if不满足就跳转到第8行 37 return重点看这两行,就能发现在循环里每次都会new StringBuilder 8 new java.lang.StringBuilder [54]//在循环内创建对象 34 if_icmplt 8// if不满足就跳转到第8行 分析工具java asm
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
可以直接用javap命令查看分析把,不需要使用java asm编码分析
|
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
fantasy 写道
在JAVA6中,编译器会始终对字符串的累加操作做优化编译。 编译器将字符串累加编译成StringBuilder。
你确认这是Java6的新功能?
我机子上没有装更早期的JDK了,但至少从JDK1.4字符串累加(就是类似String result = aa + bb + cc; )编译后的字节码就是这种形式
也就是new一个StringBuilder(JDK 6下面)或者StringBuffer(JDK 1.4下面),再做buffer.append()
同样的代码,在JDK1.4下面编译的效果:
编译选项: 就字符串累加方面,从JDK1.4~JDK6 没有什么本质的区别,只是StringBuffer和StringBuilder的区别。
JDK6并没有特别优化。
|
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
是楼主火星了?
|
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
听过sajia老师讲的视频 记得1.4后的hotspot就有这个编译优化效果了
|
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
但是一直有个问题,即使是编译优化了,用加号拼接的效率还是不如用stringbuilder的
|
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
javabkb 写道 但是一直有个问题,即使是编译优化了,用加号拼接的效率还是不如用stringbuilder的
一旦次数多了起来差距非常非常大。。。 |
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
aa87963014 写道 javabkb 写道 但是一直有个问题,即使是编译优化了,用加号拼接的效率还是不如用stringbuilder的
一旦次数多了起来差距非常非常大。。。 1.6优化了如果多个字符串做+ 直接创建stringbuilder来做了 |
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
javabkb 写道 听过sajia老师讲的视频 记得1.4后的hotspot就有这个编译优化效果了
<< 我肯定没有这样说过…这帖里说的东西都跟HotSpot(或者其它JVM实现)一点关系都没有,还在Java源码级编译器层次上… javabkb 写道 但是一直有个问题,即使是编译优化了,用加号拼接的效率还是不如用stringbuilder的
<< 这个也不对…要分情况说。前面也有说了,String的+本来就是用StringBuilder或StringBuffer实现的。 关键问题是:是不是同一个StringBuilder/StringBuffer。在循环里用+=之类的方式来拼接字符串的问题就出在每轮循环里都new了一个StringBuilder/StringBuffer来做拼接,然后toString()完就抛弃了,等下轮循环进来又再new一个。 sswh 写道 JDK6并没有特别优化。
嗯,从Java字节码一层能看到的情况看,确实是如此的。 让我演示一下某代码在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层的“优化”… 然则从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来实现字符串拼接(+)的了。 这也毫无出奇之处,在Java语言规范里就有这么写: 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. 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. 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. 顺带一提现在在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在后面还有被用到。 这才是“JVM做的优化”。字节码层面就能看出来的所谓“优化”根本还没到JVM那层。 |
|||||||
返回顶楼 | |||||||
发表时间:2011-05-12
看来鼓励大家研究javac的源代码还是很有必要啊,
|
|||||||
返回顶楼 | |||||||