今天看到有网友在我的博客留言,讨论java中String在进行拼接时使用+和StringBuilder和StringBuffer中的执行速度差异很大,而且之前看的书上说java在编译的时候会自动将+替换为StringBuilder或StringBuffer,但对于这些我都没有做深入的研究,今天准备花一点时间,仔细研究一下。
首先看一下java编译器在编译的时候自动替换+为StringBuilder或StringBuffer的部分,代码如下。
测试环境为win764位系统,8G内存,CPU为 i5-3470,JDK版本为32位的JDK1.6.0_38
第一次使用的测试代码为:
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 100 execTimes=100 + exec millis=0 C:\>java StringAppendDemo 1000 execTimes=1000 + exec millis=6 C:\>java StringAppendDemo 10000 execTimes=10000 + exec millis=220 C:\>java StringAppendDemo 100000 execTimes=100000 + exec millis=44267
可以看到,输入的参数为10000和100000时,其执行时间从0.2秒到了44秒。
我们先使用javap命令看一下编译后的代码:
javap –c StringAppendDemo
这里我摘录了和循环拼接字符串有关的那部分代码,具体为:
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
可以看到,之前的+的确已经被编译为了StringBuilder对象的append方法。通过这里的字节码可以看到,对于每一个+都将被替换为一个StringBuilder而不是我所想象的只生成一个对象。也就是说,如果有10000个+号就会生成10000个StringBuilder对象。具体参看上面字节码的第84行,此处是执行完一次循环以后,再次跳转到55行去执行。
接着,我们把再写一个使用StringBuilder直接实现的方式,看看有什么不一样。
具体代码为:
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 100 execTimes=100 StringBuilder exec millis=0 C:\>java StringBuilderAppendDemo 1000 execTimes=1000 StringBuilder exec millis=1 C:\>java StringBuilderAppendDemo 10000 execTimes=10000 StringBuilder exec millis=1 C:\>java StringBuilderAppendDemo 100000 execTimes=100000 StringBuilder 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
通过字节码可以看到,整个循环拼接过程中,只在56行对StringBuilde对象进行了一次初始化,以后的拼接操作的循环都是从64行开始,然后到81行进行goto 64再次循环。
为了证明我们的推断,我们需要看看虚拟机中是否是这么实现的。
参考代码:http://www.docjar.com/html/api/com/sun/tools/javac/jvm/Gen.java.html
具体的方法,标红的地方就是在语法树处理过程中的一个用来处理字符串拼接“+”号的例子,其他部分进行的处理也类似,我们只保留需要的部分
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()); }剩余代码已删除。
而具体把+转换为StringBuilder的方法为:
void makeStringBuffer(DiagnosticPosition pos) { code.emitop2(new_, makeRef(pos, stringBufferType)); code.emitop0(dup); callMethod( pos, stringBufferType, names.init, List.<Type>nil(), false); }
看标红出的代码可以知道,此处调用了stringBufferType的init方法来进行初始化。
看到此处有同学一定会有疑问,刚刚的字节码不是显示替换成StringBuilder了吗?原因在这里:
看protected Gen(Context context)(95行)这个方法的代码,发现其中包含了stringBufferType变量的初始化:
stringBufferType = target.useStringBuilder() ? syms.stringBuilderType
: syms.stringBufferType;(108、109、110行)
通过一个三目运算符,根据当前的编译的目标JDK是否启用了StringBuilder来设置stringBufferType的真正类型。
回到处理“+”的代码,调用完makeStringBuffer方法后接着调用appendStrings方法和bufferToString方法。具体代码如下
/** 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); }
这里其实就是将字符串进行了缓存,接着通过调用stringBufferType的toString()方法把StringBuilder中的字符转换为一个字符串对象。
接着我们通过visualvm工具看看上述两个例子运行过程中的内存使用和垃圾回收情况,visualvm工具路径为JDK根目录\bin\jvisualvm.exe
执行使用+操作符进行拼接的监视情况如下
可以看到在运行过程中,虚拟机进行了52871次GC操作共耗费了49.278s,也就是说,运行时间的很大一部分是花在了垃圾回收上。
内存使用情况如下:
可以看到内存的占用大小也在忽上忽下,同样是垃圾回收的表现。
至于第二个例子,因为运行时间仅仅在4毫秒所有,vistalvm还来不及捕捉就执行完毕了,没有捕捉到相关的执行数据。
综上所述,如果在编写代码的过程中大量使用+进行字符串评价还是会对性能造成比较大的影响,但是使用的个数在1000以下还是可以接受的,大于10000的话,执行时间将可能超过1s,会对性能产生较大影响。如果有大量需要进行字符串拼接的操作,最好还是使用StringBuffer或StringBuilder进行。
相关推荐
C# StringBuilder 拼接字符串 字符串转换工具 StringBuilder比StringBuffer运行速度要快,因为StringBuilder是针对于单线程的,所这它是非线程安全的。普通情况下建议使用StringBuilder。
java8中,编辑器对“+”进行了优化,默认使用StringBuilder进行拼接,所以不用显示的使用StringBuilder了,直接用“+”就可以了。下面我们来详细了解一下
StringBuilder字符串工具,打开exe文件就可用,简单方便
刚从java转过来感觉c的字符串操作太麻烦了所以自己写了一个类似java的StringBuilder类的头文件 它支持java的StringBuilder支持的很多操作大大方便了对字符串的操作 能力所限如有bug请指出</h1>
string与StringBuilder的在字符串拼接时执行效率上有差异,这篇文章主要介绍了详解.NET中string与StringBuilder在字符串拼接功能上的比较,感兴趣的小伙伴们可以参考一下
用Stopwatch分段监控了一下,发现耗时最多的函数是SaveToExcel ...立马想起无数先烈们的警告、字符串拼接的内存操作原理等等。于是大手一挥,代码全删,改为如下形式 代码如下:string excelString = new
今天小编就为大家分享一篇关于Java中字符数组和字符串与StringBuilder和字符串转换的讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
Java的StringBuffer和StringBuilder类是用于字符串操作的可变字符序列类。本文将介绍Java StringBuffer和StringBuilder类的常见操作和用法,包括创建、追加、插入、删除、替换、反转等操作。我们还会讨论如何获取...
String类的应用 ””,null和string.Empty的区别 Stringbuilder方法 StringBuilder 和 String 的区别?
主要生成StringBuilder 字符串 类似 StringBuilder builder = new StringBuilder(); builder.AppendFormat("<span class=\"navSep\"></span>\r\n"); builder.AppendFormat("机构看盘</a>\r\n"); builder....
StringBuilder 字符串生成器 举例说明: SELECT * FROM dbo.Pub_User WHERE ID = 1001 以上这段Sql语句,利用此工具,一键生成以下StringBuilder字符串变量,支持翻转生成,内含VS2010源码,是.Net程序员必备工具 sb....
这是java学习中,总结出来的关于StringBuilder的一些内容,供学习参考。
stringbuilder用法 java中stringBuilder的用法java中stringBuilder的用法java中stringBuilder的用法java中stringBuilder的用法java中stringBuilder的用法java中stringBuilder的用法java中stringBuilder的用法java中...
为什么Java8中不再需要StringBuilder拼接字符串共4页.pdf.zip
StringBuilder类在C#中一般被用来拼接字符串,而依托.NET Framework则可以实现添加替换移除等灵活的动态字符串操作,以下就来总结一下在C#及.NET框架中使用StringBuilder类操作字符串的技巧
那么通过如下代码,看一下在for循环中,不同的字符串拼接方式,到底哪种效率最高? package com.test.stringjoin; import org.apache.commons.lang3.StringUtils; public class Test { public static void main...
java字符串三种处理方式区别String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全)
Java--Java String和StringBuilder。
Java 可变字符串 StringBuilder 的 学习 (备注: 如果解压后是乱码,请用普通文本打开,重新复制到对应文件即可)