1, substring截取超大字符串可能造成的“内存泄漏”

2,+ 操作符的优化和局限

3,StringBuilder和StringBuffer

4,split和StringTokenizer做简单字符分割效率的比较

1, substring截取超大字符串可能造成的“内存泄漏”

我们知道,String对象内保存着一个char数组。但是char数组未必和String所代表的字符集等长,而可能是一个“超集”。String有一个私有的构造函数:

// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}

这个构造函数允许你只使用value[]的一部分作为String的字符集,它并不会截取value[]的一部分来创建一个新的char数组,而是把它整个保存起来了。

接着来看substring函数的实现:

     public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :

new String(offset + beginIndex, endIndex -

 beginIndex, value);
}

substring正是用我们上面提到的构造函数来构造返回的String的,Java这么做有利有弊:

1)如果我们要从一个大字符串中截取许多小字符串,那么这些小字符串共享一个大的char[]。那么,这么做是非常高效的,避免了重新分配内存的时间空间开销。

2)但是,如果我们只从中截取一个或少数几个很小的字符串,原String将丢弃,而这些小字符串却被长期保存,这样我们就造成了某种意义上的内存泄漏 -- 我们以为原String的内存被GC释放了,然而并没有,它的主要部分 — 巨大的char数组仍被他的子String引用着,虽然只有其中很小的一部分被它们使用了。

对于这种泄漏,解决办法很简单,使用以下语法

str2 = new String(str1.substring(5,100));

构造函数String(String)会为新的String创建一个新的char[]。但是前提是,我们意识到了substring可能导致的问题。

(后记:

谢谢@╰︶赖床专业户こ 的提醒,JDK7早期的版本还是按照我说的方式实现的,但是后期的版本已经修复了。我没有注意我参考的版本。
应该说新的实现有利有弊,现在我们需要担心的不是substring的泄露问题,而是效率问题了。
非常赞赏你的研究精神。

2,+ 操作符的优化和局限

我们知道,对于以下语法:

str1 += "abc";
str1 = str1 + "abc";

Java将创建一个新的String对象和字符串数组,把原字符串和”abc”拷贝拼接到新的字符串数组中。如果反复进行这样字符串的累加操作,自然是非常低效的,这种情况按照最佳实践,应该使用StringBuilder。

但事实上,Java已经对+操作进行了优化。看下面的代码:

String temp = "ABC" + 200 + 'D';

编译器已经把该代码优化编译成了:

String temp = new StringBuilder().append( "ABC" ).append( 200 ).append('D').toString();

(注:

另外,如果代码简单的多个字符串相加:

String temp = "Hello" + “ ” + “World”;

编译器直接优化为

String temp = "Hello World”;

所以,连续累加效率并不比使用StringBuilder效率差,因为它本来就是用一个StringBuilder对象连续的append来实现的。

但是,如果是:

for(int i=0; i<100; i++)
{
temp+="abc";
}

编译器并没有办法把以上for循环里面多次迭代的‘+’操作优化为只使用一个StringBuilder对象的连续append操作。因此,还是非常低效的。

简而言之,如果所有的字符串拼接可以在一行里面用‘+’完成,那么是没有效率问题的;否则,最好使用StringBuilder。

3,StringBuilder和StringBuffer

StringBuilder和StringBuffer用法基本没什么区别,但是StringBuilder不是线程安全的,StringBuffer是线程安全的。StringBuffer在所有用于字符操作的public方法都加了锁--使用了synchronized关键字。

我们来测试一下单线程下StringBuilder和StringBuffer的效率,以下代码:

public static void main(String[] args){
long t1 = System.nanoTime();
StringBuffer stringBuffer = new StringBuffer(); for(int i=0; i<1000000; i++)
{
stringBuffer.append("a");
}
stringBuffer.toString();
long t2 = System.nanoTime();
System.out.println("StringBuffer :"+ (t2-t1)); t1 = System.nanoTime();
StringBuilder stringBuilder = new StringBuilder(); for(int i=0; i<1000000; i++)
{
stringBuilder.append("a");
}
stringBuilder.toString();
t2 = System.nanoTime();
System.out.println("StringBuilder:"+ (t2-t1));
}

结果:

StringBuffer :33979818
StringBuilder:14061978

单线程情况下,StringBuilder要快一倍多。

那多线程情况StringBuffer效率如何呢?下面代码测试:

long t1 = System.nanoTime();
final StringBuffer stringBuffer = new StringBuffer(); ExecutorService executor = Executors.newFixedThreadPool(3);
CountDownLatch countDownLatch = new CountDownLatch(3); for (int i = 0; i < 3; i++) {
executor.execute(new Runnable() { @Override
public void run() {
for (int i = 0; i < 333333; i++) {
stringBuffer.append("a");
}
} });
countDownLatch.countDown();
}
stringBuffer.toString();
countDownLatch.await(); long t2 = System.nanoTime();
System.out.println("StringBuffer :"+ (t2-t1));

结果:

StringBuffer :2603076

虽然我们使用了3个工作线程,但是效率几乎比单线程没有什么提升,这就是使用锁在多线程的结果--锁在多线程中的协调,导致线程的频繁切换,大大降低效率。

虽然我实在不知道有什么场景需要用到多线程的字符串拼装。假设有,并且对性能有很严格的要求,我觉得可以考虑使用一些无锁的多线程编程框架,例如Disruptor--一个无锁的RingBuffer框架,使用多个生产者线程往Ring buffer中投递String对象,在消费者中用StringBuilder进行组装。(类似log4j 2的异步日志处理)

4,split和StringTokenizer做简单字符分割效率的比较。

很多文章都说split比StringTokenizer效率高很多,开始也深以为然,但是却发现它们的测试代码都存在很严重的问题。自己做了一下测试

        StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 1000000; i++) {
stringBuilder.append(i);
stringBuilder.append(",");
} String str = stringBuilder.toString();
long t1 = System.nanoTime();
String[] strArray = str.split(",");
long t2 = System.nanoTime();
System.out.println("split :" + (t2 - t1)); String str1 = stringBuilder.toString();
t1 = System.nanoTime();
StringTokenizer stringTokenizer = new StringTokenizer(str1, ",");
//List<String> strList = new ArrayList<String>(1000000); //或者 String[] strArray1 = new String[stringTokenizer.countTokens()];
for (int i = 0; i < 1000000; i++) {
String subStr = stringTokenizer.nextToken();
//strList.add(subStr); //或者strArray1[i] =subStr;
}
t2 = System.nanoTime();
System.out.println("token :" + (t2 - t1));

结果:

split :248539389
token :53191452

StringTokenizer 比split快4倍。

但是上面的比较在某些情况下并不公平,split会返回一个数组,而StringTokenizer 的next方法只能逐个浏览token。如果要求StringTokenizer 也把返回的子字符串保存在List中,那么结果如何呢?把上面代码段中的注释掉的代码打开,使StringTokenizer 也要把tokens保存在List或Array中,再进行测试。

结果:

split :254496592
token :303926083

这种情况下StringTokenizer 的效率还差一些。因此,不能一概而论split或StringTokenizer 谁的效率高,还要看如果使用。如果需要把结果放在Array或List当中,split更简单还有效率。(可见2种算法效率并没有本质差别,差就差在Array或List的使用上,具体还要从JDK的源代码去分析)

Binhua Liu原创文章,转载请注明原地址http://www.cnblogs.com/Binhua-Liu/p/5572350.html

String高效编程优化(Java)的更多相关文章

  1. 架构师速成-如何高效编程 for java

    引子 赵云大喝一声,挺枪骤马杀入重围,左冲右突,如入无人之境.那枪浑身上下,若舞梨花:遍体纷纷,如飘瑞雪. 赵云是所有历史人物中我最喜欢的一个,如果放到现代,他走了it的道路,一定可以成为一个编程高手 ...

  2. Java高效编程:总结分享

    参考资料:慕课网:Java高效编程收费实战课程.博客园.CSDN.菜鸟教程以及其他文档. 篇幅受限,不太想针对每个点都写篇博客,有的地方可能写的不是很详细,一笔带过了.如果你觉得那个点在项目中用得上可 ...

  3. 剑指Java高效编程教程

    教程介绍 所谓"武以快为尊,天下武功唯快不破".本课程剑指Java高效编程,致力于从"技术"和"工具"两大 维度提高编程效率,帮助广大程序员 ...

  4. 为什么函数式编程在Java中很危险?

    摘要:函数式编程这个不温不火的语言由来已久.有人说,这一年它会很火,尽管它很难,这也正是你需要学习的理由.那么,为什么函数式编程在Java中很危险呢?也许这个疑问普遍存在于很多程序员的脑中,作者Ell ...

  5. String字符串性能优化的探究

    一.背景 String 对象是我们使用最频繁的一个对象类型,但它的性能问题却是最容易被忽略的.String 对象作为 Java 语言中重要的数据类型,是内存中占用空间最大的一个对象,高效地使用字符串, ...

  6. 怎么优化JAVA程序的执行效率和性能?

    现在java程序已经够快的了,不过有时写出了的程序效率就不怎么样,很多细节值得我们注意,比如使用StringBuffer或者StringBuilder来拼接或者操作字符串就比直接使用String效率高 ...

  7. [.NET] 《C# 高效编程》(一) - C# 语言习惯

    C# 语言习惯 目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Con ...

  8. jvm系列(十):如何优化Java GC「译」

    本文由CrowHawk翻译,是Java GC调优的经典佳作. 本文翻译自Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三 ...

  9. 码出高效,阿里巴巴JAVA开发手册1.4.0

    码出高效,阿里巴巴JAVA开发手册1.4.0阅读笔记 一.编程规约(三) 代码格式// 关键词if与括号之间必须有一个空格,括号内的f与左括号,0与右括号不需要空格 if (flag == 0) { ...

随机推荐

  1. JMeter学习-023-JMeter 命令行(非GUI)模式详解(一)-执行、输出结果及日志、简单分布执行脚本

    前文 讲述了JMeter分布式运行脚本,以更好的达到预设的性能测试(并发)场景.同时,在前文的第一章节中也提到了 JMeter 命令行(非GUI)模式,那么此文就继续前文,针对 JMeter 的命令行 ...

  2. centos7 使用systemd 自定义关机前脚本

    systemd (centos7) 需求,关机前执行脚本 关机脚本vi /usr/bin/shutdown_cust.sh#!/bin/bashecho "zhengchangguanji& ...

  3. 让超出父视图范围的子视图响应事件,在UIView范围外响应点击

    /** *  在父视图中重写该方法,这样可使超出部分响应事件. */ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {  ...

  4. python pip安装问题

    scipy-0.18.1-cp34-cp34m-win32.whl is not a supported wheel on this platform. 遇到该问题需要更新pip版本 1.更新pip: ...

  5. Nginx location 匹配顺序整理

    Nginx location模块整理 具体的Nginx安装就不在这里描述了,这里只是为了对location的描述 Nginx环境 a. 查看当前系统cat /etc/redhat-release [r ...

  6. delphi中exit,abort,break,continue 的区别

    from:http://www.cnblogs.com/taofengli288/archive/2011/09/05/2167553.html delphi中表示跳出的有break,continue ...

  7. javaScript中定义类或对象的五种方式

    第一种方式: 工厂方法 能创建并返回特定类型的对象的工厂函数(factory function). function createCar(sColor){ var oTempCar = new Obj ...

  8. C#语法糖,让编程更具乐趣

    一.什么是语法糖 语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法 ...

  9. JAVA面试中问及HIBERNATE与 MYBATIS的对比,在这里做一下总结

    我是一名java开发人员,hibernate以及mybatis都有过学习,在java面试中也被提及问道过,在项目实践中也应用过,现在对hibernate和mybatis做一下对比,便于大家更好的理解和 ...

  10. redmine export long csv file failed: 502 proxy error

    After modified the file \apps\redmine\conf\httpd-vhosts.conf: <VirtualHost *:8080> ServerName ...