论坛首页 Java企业应用论坛

JAVA6可以使用字符串累加

浏览 17284 次
精华帖 (0) :: 良好帖 (3) :: 新手帖 (0) :: 隐藏帖 (1)
作者 正文
   发表时间:2011-05-12  
不要写任何依赖语法糖 依赖编译器的代码
久而久之 你就总会以为有虚拟机给你优化
这是不好的习惯
0 请登录后投票
   发表时间:2011-05-13  
RednaxelaFX 写道
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那层。

说的很详细和清楚,赞一个!
0 请登录后投票
   发表时间:2011-05-13  

	public static void main(String[] args) {

		StringBuffer sb = new StringBuffer();
		long l3 = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			sb.append("a").append(i);
		}
		long l4 = System.currentTimeMillis();
		System.out.println(l4 - l3);
		System.out.println(sb.length());

		String s = "";
		long l = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			s = s + "a" + i;
		}
		long l2 = System.currentTimeMillis();
		System.out.println(l2 - l);
		System.out.println(s.length());

	}

 

结果:

stringbuffer:

0

48890

 

String:

1281

48890

 

jdk1.6

 

根本就没优化过啊啊,或者说优化之后也是这个鸟样!坑爹呐

0 请登录后投票
   发表时间:2011-05-13  
循环10W次

stringbuffer耗时:31ms

string:等不下去了
0 请登录后投票
   发表时间:2011-05-13  
ak121077313 写道

 

	public static void main(String[] args) {

		StringBuffer sb = new StringBuffer();
		long l3 = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			sb.append("a").append(i);
		}
		long l4 = System.currentTimeMillis();
		System.out.println(l4 - l3);
		System.out.println(sb.length());

		String s = "";
		long l = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			s = s + "a" + i;
		}
		long l2 = System.currentTimeMillis();
		System.out.println(l2 - l);
		System.out.println(s.length());

	}

 

结果:

stringbuffer:

 

0

48890

 

String:

1281

48890

 

jdk1.6

 

根本就没优化过啊啊,或者说优化之后也是这个鸟样!坑爹呐

请仔细看贴。“在循环中不要使用累加操作”

 

0 请登录后投票
   发表时间:2011-05-13  
RednaxelaFX 每次都是终结者
0 请登录后投票
   发表时间:2011-05-13  
这个讨论意义不大
代码不应该依赖于jdk版本或者JVM实现
否则可移植性太差了
0 请登录后投票
   发表时间:2011-05-13   最后修改:2011-05-13
qianhd 写道
不要写任何依赖语法糖 依赖编译器的代码
久而久之 你就总会以为有虚拟机给你优化
这是不好的习惯


Crusader 写道
这个讨论意义不大
代码不应该依赖于jdk版本或者JVM实现
否则可移植性太差了



两位有点过于追求完美了,你不会从jdk1.6切换会jdk1.4以下吧。

我指导得最多的就是不要在简单的sql拼接时使用过多stringbuild,比如:
  execute("select * from " + tableName + " where id=?", 1);

有人过度强调原则,如前两位,结果写了
  execute(
    new StringBuilder("select * from ").append(tableName).append(" where id=?"), 1);

如果给人看还是直接+更加容易读

所以需要权衡,我的一般原则:
  1. 循环中操作字符串,使用StringBuilder
  2. 如果字符串特别长,同时还不能一次使用+完成所有操作 时使用StringBuilder


这个只是个人经验。

 

0 请登录后投票
   发表时间:2011-05-13  
skzr.org 写道
qianhd 写道
不要写任何依赖语法糖 依赖编译器的代码
久而久之 你就总会以为有虚拟机给你优化
这是不好的习惯

 

Crusader 写道
这个讨论意义不大
代码不应该依赖于jdk版本或者JVM实现
否则可移植性太差了



两位有点过于追求完美了,你不会从jdk1.6切换会jdk1.4以下吧。

我指导得最多的就是不要在简单的sql拼接时使用过多stringbuild,比如:
  execute("select * from " + tableName + " where id=?", 1);

有人过度强调原则,如前两位,结果写了
  execute(
    new StringBuilder("select * from ").append(tableName).append(" where id=?"), 1);

如果给人看还是直接+更加容易读

所以需要权衡,我的一般原则:
  1. 循环中操作字符串,使用StringBuilder
  2. 如果字符串特别长,同时还不能一次使用+完成所有操作 时使用StringBuilder


这个只是个人经验。

 

如果只是为了易读性,个人认为得不尝失

而且并不一定是为了向前兼容,也可能是平行,向后,谁也能保证JVM(不同厂商,不同版本)会不会优化,如何优化

0 请登录后投票
   发表时间:2011-05-13  
fantasy 写道

请仔细看贴。“在循环中不要使用累加操作”

 

没有看到这个。 而且我只关心循环中累加操作的效率。其他非循环的地方无所谓

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics