深入分析Java使用+和StringBuilder进行字符串拼接的差异
public static void main(String[] args) { // TODO Auto-generated method stub String demoString=""; int execTimes=10000; if(args!=null&&args.length>0) { execTimes=Integer.parseInt(args[0]); } System.out.println("execTimes="+execTimes); long starMs=System.currentTimeMillis(); for(int i=0;i<execTimes;i++) { demoString=demoString+i; } long endMs=System.currentTimeMillis(); System.out.println("+ exec millis="+(endMs-starMs)); }
C:\>java StringAppendDemo 100execTimes=100+ exec millis=0C:\>java StringAppendDemo 1000execTimes=1000+ exec millis=6C:\>java StringAppendDemo 10000execTimes=10000+ exec millis=220C:\>java StringAppendDemo 100000execTimes=100000+ exec millis=44267
51: lstore_3 52: iconst_0 53: istore 5 55: iload 5 57: iload_2 58: if_icmpge 87 61: new #5; //class java/lang/StringBuilder 64: dup 65: invokespecial #6; //Method java/lang/StringBuilder."<init>":()V 68: aload_1 69: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 72: iload 5 74: invokevirtual #9; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 77: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 80: astore_1 81: iinc 5, 1 84: goto 55
public class StringBuilderAppendDemo { public static void main(String[] args) { // TODO Auto-generated method stub String demoString=""; int execTimes=10000; if(args!=null&&args.length>0) { execTimes=Integer.parseInt(args[0]); } System.out.println("execTimes="+execTimes); long starMs=System.currentTimeMillis(); StringBuilder strBuilder=new StringBuilder(); for(int i=0;i<execTimes;i++) { strBuilder.append(i); } long endMs=System.currentTimeMillis(); System.out.println("StringBuilder exec millis="+(endMs-starMs)); }}
C:\>java StringBuilderAppendDemo 100execTimes=100StringBuilder exec millis=0C:\>java StringBuilderAppendDemo 1000execTimes=1000StringBuilder exec millis=1C:\>java StringBuilderAppendDemo 10000execTimes=10000StringBuilder exec millis=1C:\>java StringBuilderAppendDemo 100000execTimes=100000StringBuilder exec millis=5
51: lstore_3 52: new #5; //class java/lang/StringBuilder 55: dup 56: invokespecial #6; //Method java/lang/StringBuilder."<init>":()V 59: astore 5 61: iconst_0 62: istore 6 64: iload 6 66: iload_2 67: if_icmpge 84 70: aload 5 72: iload 6 74: invokevirtual #9; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 77: pop 78: iinc 6, 1 81: goto 64
public void visitAssignop(JCAssignOp tree) { OperatorSymbol operator = (OperatorSymbol) tree.operator; Item l; if (operator.opcode == string_add) { // Generate code to make a string buffer makeStringBuffer(tree.pos()); // Generate code for first string, possibly save one // copy under buffer l = genExpr(tree.lhs, tree.lhs.type); if (l.width() > 0) { code.emitop0(dup_x1 + 3 * (l.width() - 1)); } // Load first string and append to buffer. l.load(); appendString(tree.lhs); // Append all other strings to buffer. appendStrings(tree.rhs); // Convert buffer to string. bufferToString(tree.pos()); }?剩余代码已删除。
?
void makeStringBuffer(DiagnosticPosition pos) { code.emitop2(new_, makeRef(pos, stringBufferType)); code.emitop0(dup); callMethod( pos, stringBufferType, names.init, List.<Type>nil(), false); }?
?
/** Add all strings in tree to string buffer. */ void appendStrings(JCTree tree) { tree = TreeInfo.skipParens(tree); if (tree.getTag() == JCTree.PLUS && tree.type.constValue() == null) { JCBinary op = (JCBinary) tree; if (op.operator.kind == MTH && ((OperatorSymbol) op.operator).opcode == string_add) { appendStrings(op.lhs); appendStrings(op.rhs); return; } } genExpr(tree, tree.type).load(); appendString(tree); } /** Convert string buffer on tos to string. */ void bufferToString(DiagnosticPosition pos) { callMethod( pos, stringBufferType, names.toString, List.<Type>nil(), false); }?
?
?
?
将+替换为StringBuffer或StringBuilder的append操作运行时耗时耗时相差大的原因是
+ 的时候需要频繁的new StringBuilder(StringBuffer)对象和初始化,能后在调用append()方法,而直接使用append方法 不需要频繁的new 和初始化!
在使用+的时候 时间大多数都消耗在new StringBuilder(StringBuffer)对象和初始化了 9 楼 runfriends 2013-09-04 bsr1983 写道runfriends 写道连+,最后一个分号只创建一个StringBuilder
一旦连接操作由多条语句执行,就是一条语句一个StringBuilder
这个没有验证,有空验证一下
可以在连+那一行加个断点,逐行debug,就知道了 10 楼 runfriends 2013-09-04 bsr1983 写道runfriends 写道连+,最后一个分号只创建一个StringBuilder
一旦连接操作由多条语句执行,就是一条语句一个StringBuilder
这个没有验证,有空验证一下
把连+的字节码反编译一下,就是只创建一个StringBuilder 11 楼 yunhua_lee 2013-09-04 你的字符串太短了,测试起来效果差异不明显,如果你每次都使用1K的字符串去加,我估计不用到10000次,1000次差异就可能很大 12 楼 sessionsong 2013-09-04 连+ 的时候 如:
String a="a";
String b="b";
String d="d";
String c = a+b+d;
他只会new 一个stringBuilder对象!并进行2次append操作,能后在调用stringbuilder的tostring方法,返回给string对象
如果是for 循环中的+, 每循环一次new 一个Stringbuilder对象! 13 楼 须等待 2013-09-05 sessionsong 写道连+ 的时候 如:
String a="a";
String b="b";
String d="d";
String c = a+b+d;
他只会new 一个stringBuilder对象!并进行2次append操作,能后在调用stringbuilder的tostring方法,返回给string对象
如果是for 循环中的+, 每循环一次new 一个Stringbuilder对象!
那是编译级别的常量,一般在应用中都不会拿着100000个编译常量来+吧? 14 楼 sessionsong 2013-09-05 须等待 写道sessionsong 写道连+ 的时候 如:
String a="a";
String b="b";
String d="d";
String c = a+b+d;
他只会new 一个stringBuilder对象!并进行2次append操作,能后在调用stringbuilder的tostring方法,返回给string对象
如果是for 循环中的+, 每循环一次new 一个Stringbuilder对象!
那是编译级别的常量,一般在应用中都不会拿着100000个编译常量来+吧?
嗯! 那到时,这只是在讨论问题,实际中确实是不会这样 15 楼 runfriends 2013-09-05 须等待 写道sessionsong 写道连+ 的时候 如:
String a="a";
String b="b";
String d="d";
String c = a+b+d;
他只会new 一个stringBuilder对象!并进行2次append操作,能后在调用stringbuilder的tostring方法,返回给string对象
如果是for 循环中的+, 每循环一次new 一个Stringbuilder对象!
那是编译级别的常量,一般在应用中都不会拿着100000个编译常量来+吧?
对于纯常量计算,编译器会完成,不会在运行期计算
所以编译完了c的值就是"abd"
再返编译回来就是定义了四个字符串,都是字面值,没有+,也没有StringBuilder 16 楼 youbl 2013-09-09 上面字节码的第88行
我看了半天字节码,一直找不到88在哪 17 楼 bsr1983 2013-09-09 youbl 写道上面字节码的第88行
我看了半天字节码,一直找不到88在哪
此处应该是84行,之前有笔误,谢谢指出!
18 楼 cry615 2013-10-11 分析挺到位的,详细有据。