`

Java字符串之性能优化

 
阅读更多

基础类型转化成String 

在程序中你可能时常会需要将别的类型转化成String,有时候可能是一些基础类型的值。在拼接字符串的时候,如果你有两个或者多个基础类型的值需要放到前面,你需要显式的将第一个值转化成String(不然的话像System.out.println(1+'a')会输出98,而不是"1a")。当然了,有一组String.valueOf方法可以完成这个(或者是基础类型对应的包装类的方法),不过如果有更好的方法能少敲点代码的话,谁还会愿意这么写呢? 

在基础类型前面拼接上一个空串(""+1)是最简单的方法了。这个表达式的结果就是一个String,在这之后你就可以随意的进行字符串拼接操作了——编译器会自动将那些基础类型全转化成String的。 

不幸的是,这是最糟糕的实现方法了。要想知道为什么,我们得先介绍下这个字符串拼接在Java里是如何处理的。如果一个字符串(不管是字面常量也好,或者是变量,方法调用的结果也好)后面跟着一个+号,再后面是任何的类型表达式: 

Java代码  收藏代码
  1. string_exp + any_exp  



Java编译器会把它变成: 

Java代码  收藏代码
  1. new StringBuilder().append( string_exp ).append( any_exp ).toString()  




如果表达式里有多个+号的话,后面相应也会多多几个StringBuilder.append的调用,最后才是toString方法。 

StringBuilder(String)这个构造方法会分配一块16个字符的内存缓冲区。因此,如果后面拼接的字符不超过16的话,StringBuilder不需要再重新分配内存,不过如果超过16个字符的话StringBuilder会扩充自己的缓冲区。最后调用toString方法的时候,会拷贝StringBuilder里面的缓冲区,新生成一个String对象返回。 

这意味着基础类型转化成String的时候,最糟糕的情况就是你得创建:一个StringBuilder对象,一个char[16]数组,一个String对象,一个能把输入值存进去的char[]数组。使用String.valueOf的话,至少StringBuilder对象省掉了。 

有的时候或许你根本就不需要转化基础类型。比如,你正在解析一个字符串,它是用单引号分隔开的。最初你可能是这么写的: 

Java代码  收藏代码
  1. final int nextComma = str.indexOf("'");  



或者是这样: 

Java代码  收藏代码
  1. final int nextComma = str.indexOf('\'');  



程序开发完了,需求变更了,需要支持任意的分隔符。当然了,你的第一反应是,得将这个分隔符存到一个String对象中,然后使用String.indexOf方法来进行拆分。我们假设有个预先配置好的分隔符就放到m_separator字段里(译注:能用这个变量名的,应该不是Java开发出身的吧。。)。那么,你解析的代码应该会是这样的: 

Java代码  收藏代码
  1. private static List<String> split( final String str )  
  2. {  
  3.     final List<String> res = new ArrayList<String>( 10 );  
  4.     int pos, prev = 0;  
  5.     while ( ( pos = str.indexOf( m_separator, prev ) ) != -1 )  
  6.     {  
  7.         res.add( str.substring( prev, pos ) );  
  8.         prev = pos + m_separator.length(); // start from next char after separator  
  9.     }  
  10.     res.add( str.substring( prev ) );  
  11.     return res;  
  12. }  



不过后面你发现这个分隔符就只有一个字符。在初始化的时候,你把String m_separator改成了char m_separator,然后把setter方法也一起改了。但你希望解析的方法不要改动太大(代码现在是好使的,我为什么要费劲去改它呢?): 

Java代码  收藏代码
  1. private static List<String> split2( final String str )  
  2. {  
  3.     final List<String> res = new ArrayList<String>( 10 );  
  4.     int pos, prev = 0;  
  5.     while ( ( pos = str.indexOf("" + m_separatorChar, prev ) ) != -1 )  
  6.     {  
  7.         res.add( str.substring( prev, pos ) );  
  8.         prev = pos + 1// start from next char after separator  
  9.     }  
  10.     res.add( str.substring( prev ) );  
  11.     return res;  
  12. }  



正如你所看到的,indexOf方法的调用被改动了,不过它还是新建出了一个字符串然后传递进去。当然,这么做是错的,因为还有一个indexOf方法是接收char类型而不是String类型的。我们用它来改写一下: 

Java代码  收藏代码
  1. private static List<String> split3( final String str )  
  2. {  
  3.     final List<String> res = new ArrayList<String>( 10 );  
  4.     int pos, prev = 0;  
  5.     while ( ( pos = str.indexOf(m_separatorChar, prev ) ) != -1 )  
  6.     {  
  7.         res.add( str.substring( prev, pos ) );  
  8.         prev = pos + 1// start from next char after separator  
  9.     }  
  10.     res.add( str.substring( prev ) );  
  11.     return res;  
  12. }  



我们来用上面的三种实现来进行测试,将"abc,def,ghi,jkl,mno,pqr,stu,vwx,yz"这个串解析1000万次。下面是Java 6_41和7_15的运行时间。Java7由于它的String.substring方法线性复杂度的所以运行时间反而增加了。关于这个你可以参考下这里的资料。 

可以看到的是,简单的一个重构,明显的缩短了分割字符串所需要的时间(split/split2->split3)。 

  split split2 split3
Java 6 4.65 sec 10.34 sec 3.8 sec
Java 7 6.72 sec 10.34 sec 3.8 sec





字符串拼接 

本文当然也不能完全不提字符串拼接另外两种方法。第一种是String.concat,这个很少会用到。它内部其实是分配了一个char[],长度就是拼接后的字符串的长度,它将字符串的数据拷贝到里面,最后使用了私有的构造方法来生成了一个新的字符串,这个构造方法不会再对char[]进行拷贝,因此这个方法调用只创建了两个对象,一个是String本身,还有一个就是它内部的char[]。不幸的是,除非你只拼接两个字符串,这个方法才会比较高效一些。 

还有一种方法就是使用StringBuilder类,以及它的一系列的append方法。如果你有很多要拼接的值的话,这个方法当然是最快的了。它在Java5中被首度引入,用来替代StringBuffer。它们的主要区别就是StringBuffer是线程安全的,而StringBuilder不是。不过你会经常并发的拼接字符串么难道? 

在测试中,我们把0到100000之间的数全部进行了拼接,分别使用了String.concat, +操作符,还有StringBuilder,代码如下: 

Java代码  收藏代码
  1. String res = "";   
  2. for ( int i = 0; i < ITERS; ++i )  
  3. {  
  4.     final String s = Integer.toString( i );  
  5.     res = res.concat( s ); //second option: res += s;  
  6. }          
  7. //third option:          
  8. StringBuilder res = new StringBuilder();   
  9. for ( int i = 0; i < ITERS; ++i )  
  10. {  
  11.     final String s = Integer.toString( i );  
  12.     res.append( s );  
  13. }  





String.concat + StringBuilder.append
10.145 sec 42.677 sec 0.012 sec




结果非常明显——O(n)的时间复杂度明显要比O(n2) 要强得多。不过在实际工作中会用到大量的+操作符——因为它们实在是非常方便。为了解决这个问题,从Java6 update 20开始,引入了一个-XX:+OtimizeStringConcat开关。在Java 7_02和Java 7_15之间的版本,它是默认打开着的(在Java 6_41中还是默认关闭着的),因此可能你得手动将它打开。跟其它-XX的选项一样,它的文档也相当的差: 

Optimize String concatenation operations where possible. (Introduced in Java 6 Update 20) 

我们假设Oracle的工程师实现这个选项的时候是尽了最大努力的吧。坊间传闻,它是把一些StringBuilder拼接的逻辑替换成了类似String.concat那样的实现——它先生成一个合适大小的char[]然后再把东西拷贝进去。最后生成一个String。那些嵌套的拼接操作它可能也支持(str1 +(str2+str3) +str4)。打开这个选项后进行测试,结果表明,+号的性能跟String.concat的十分接近: 


String.concat + StringBuilder.append
10.19 sec 10.722 sec 0.013 sec





我们做另外一个测试。正如前面提到的,默认的StringBuilder构造器分配的是16个字符的缓冲区。当需要添加第17个字符时,这个缓冲区会被扩充。我们把100到100000间的数字分别追加到"12345678901234”的后面。结果串的长度应该是在17到20之间,因此默认的+操作符的实现会需要StringBuilder重新调整大小。作为对比,我们再做另一个测试,在这里我们直接创建一个StringBuilder(21)来保证它的缓冲区足够大,而不会重新调整: 

Java代码  收藏代码
  1. final String s = BASE + i;  
  2. final String s = new StringBuilder( 21 ).append( BASE ).append( i ).toString();  




没有打开这个选项的话,+号的实现会比显式的StringBuilder的实现的时间要多出一半。打开了这个选项后,两边的结果是一样的。不过有趣的是,即使是StringBuilder的实现本身,打开了开关后速度居然也变快了! 



+, 开关关闭 +, 开关打开 new StringBuilder(21),开关关闭 new StringBuilder(21),开关打开
0.958 sec 0.494 sec 0.663 sec 0.494 sec



总结 

  • 当转化成字符串的时候,应当避免使用""串进行转化。使用合适的String.valueOf方法或者包装类的toString(value)方法。
  • 尽量使用StringBuilder进行字符串拼接。检查下老旧码,把那些能替换掉的StringBuffer也替换成它。
  • 使用Java 6 update 20引入的-XX:+OptimizeStringConcat选项来提高字符串拼接的性能。在最近的Java7的版本中已经默认打开了,不过在Java 6_41还是关闭的。





原创文章转载请注明出处:http://it.deepinmind.com 

英文原文链接 

想及时了解博客更新,可以关注我的微博Java译站

分享到:
评论

相关推荐

    大话Java性能优化

    1.2 性能优化的参考因素 1.3 性能调优分类方法 1.4 本章小结 第2章 优化前的准备知识 2.1 服务器知识 2.2 新兴技术 第3章 Java API调用优化建议 3.1 面向对象及基础类型 3.2 集合类概念 3.3 字符串概念 ...

    JavaScript 字符串连接性能优化

    每拼接一次字符串就循环重复2)~6),如果重复成百上千次则会非常消耗资源,影响性能。 解决方法: 使用 Array 对象存储字符串,之后使用 join()方法输出结果。 仿照 Java 中的 StringBuffer 类。

    Java循环与字符串代码优化

    在系统性能优化的时候循环和字符串处理一直是非常值得注意的地方。从心态上我们一定不能把自己的眼界放在十次或者是百次循环的层次上,也不能把自己要处理的字符串当做是有十个二十个字符。每次遇到循环都要假定这个...

    Java优化编程(第2版)

    4.2.2 字符串的length()方法与性能优化 4.2.3 tochararray()方法与性能优化 4.2.4 字符串转化为数字 4.3 系统i/o类 4.3.1 java语言中输入/输出流 4.3.2 通过系统缓冲流类提高i/o操作效率 4.3.3 通过自定制缓冲区提高...

    [Java算法设计]-两串旋转.java

    文档还涵盖了高级主题,如如何处理包含重复字符的字符串以及如何优化代码以提高性能。该资源包括实用练习,让读者可以练习在Java中解决两个字符串旋转问题,并提供解决方案以帮助读者检查自己的工作并深入理解所学...

    Java中关于字符串的若干问题分析.pdf

    分析,作者在多年的Java程序设计教学工作中,就字符串不同 的初始化方法对程序的影响、相等比较以及 String 类与 Strin gBuffer 类的区别三个方面做了系统分析,期望能够对 Java 歇 息提供一些参考和借鉴。

    字符数组的存储方式 字符串常量池.docx

    字符串在java程序中被大量使用,为了避免每次都创建相同的字符串对象及内存分配,JVM内部对字符串对象的创建做了一定的优化,在Permanent Generation中专门有一块区域用来存储字符串常量池(一组指针指向Heap中的...

    java笔试题重复字符串-optimizing-go-programs:关于优化Go程序的研讨会材料

    java笔试题重复字符串优化 Go 程序 这是有助于优化 Go 程序的工具和技术的整理。 它分为两部分——工具和技术。 工具查看帮助您检测代码的 go 命令行工具。 技术查看您可能用来获得性能的想法列表。 最后还有一大堆...

    Java toString的性能优化方案比较

     不过,首先让我们一起看看Javadoc回忆下Object.toString应当做什么:“返回该对象的字符串表示,该结果必须简明但表述详实易懂。建议所有子类重写该方法”。这里有趣的是“简明”和“详实”。我们所钟爱的IDE们...

    驼峰转下划线、下划线转驼峰的java工具类

    未优化复杂场景:在大量字符串处理时,如果字符串长度较大,可能有进一步优化空间,例如减少字符串复制次数。 调用方法描述: camelToUnderscore方法接收一个驼峰命名的字符串作为参数,并返回相应下划线命名的...

    Java基础知识点总结.docx

    无论是工作学习,不断的总结是必不可少的。只有不断的总结,发现问题,弥补不足,才能长久的进步!!Java学习更是如此,知识点总结目录如下: 目录 一、 Java概述 3 二、 Java语法基础 5 ...Java 性能优化 362

    javascript中的关于类型转换的性能优化

    1. 把数字转换成字符串,应用”” + 1,虽然看起来比较丑一点,但事实上这个效率是最高的,性能上来说:(“” + ) &gt; String() &gt; .toString() &gt; new String(),尽量使用编译时就能使用的内部操作要比运行时使用的用户...

    最新Java面试题视频网盘,Java面试题84集、java面试专属及面试必问课程

    实现一个拷贝文件的类使用字节流还是字符串.mp4 │ Java面试题13.线程的实现方式 怎么启动线程怎么区分线程.mp4 │ Java面试题14.线程并发库和线程池的作用?.mp4 │ Java面试题15.设计模式和常用的设计模式.mp4 │ ...

    详解C++中StringBuilder类的实现及其性能优化

    在Java和C#中,StringBuilder可以创造可变字符序列来动态地扩充字符串,那么在C++中我们同样也可以实现一个StringBuilder并且用来提升性能,下面就来详解C++中StringBuilder类的实现及其性能优化

    [Java算法设计]-回文数.java

    文档还涵盖了高级主题,如如何优化代码以提高性能以及如何处理大的回文字符串。该资源包括实用练习,让读者可以练习在Java中实现回文数,并提供解决方案以帮助读者检查自己的工作并深入理解所学内容。 无论您是Java...

    java代码优化

    2、与字符串有关的性能问题 3、与时间有关的性能问题 4、与for循环有关的性能问题 5、与集合类有关的性能问题 6、与开发有关的性能问题 7、与工具方法有关的性能问题 8、与sql动态绑定有关的性能问题 9、与反射调用...

    java源码包---java 源码 大量 实例

     设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang)并发送给李四,这里发送的是公钥的数组字节  通过网络或磁盘等方式,把公钥编码传送给李四,李四接收到...

    SQLServer2008查询性能优化 2/2

    《SQL Server 2008查询性能优化》指出的性能要点之一是数据库随着用户和数据的日益增多而进行扩展的必要性。你需要理解性能低下的起因。以及识别并修复它们的方法。《SQL Server 2008查询性能优化》将帮助你: 使用...

    java最新技术介绍.docx

    Java 11主要包含了新的语法、改进的工具和增强的性能等方面的内容,如支持原始字符串字面量、增强了对lambda表达式中局部变量的类型推断、新增了G1垃圾收集器等。 Spring 5.0:Spring 5.0是Spring框架的最新版本,它...

    JAVA上百实例源码以及开源项目

     设定字符串为“张三,你好,我是李四”  产生张三的密钥对(keyPairZhang)  张三生成公钥(publicKeyZhang)并发送给李四,这里发送的是公钥的数组字节  通过网络或磁盘等方式,把公钥编码传送给李四,李四接收到...

Global site tag (gtag.js) - Google Analytics