String作为我们使用最频繁的一种对象类型,其性能问题是最容易被忽略的。作为Java中重要的数据类型,是内存中占据空间比较大的一个对象。如何高效地使用字符串,可以帮助我们提升系统的整体性能。

  现在,我们就从String对象的实现、特性以及实际使用中的优化这几方面来入手,深入理解以下String的性能优化。

  在这之前,首先看一个问题。通过三种方式创建三个对象,然后依次两两匹配,得出的结果是什么?答案留到最后揭晓。

 String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
System.out.println(str1 == str2);
System.out.println(str2 == str3);
System.out.println(str1 == str3);

  

  String对象是如何实现的?

  Java中对String对象做了大量的优化,以此来节约内存空间,提升String对象的性能。下图是Java6 -> Java9 String对象属性的变化:

  

  可以看到,String的属性有了以下的变化:

  • 在Java6及以前的版本中,String对象是对char数组进行了封装实现的对象,主要有:char数组、偏移量offset、字符数量count、哈希值hash。String对象通过offset和count属性来定位char数组,获取字符串。这样做可以高效快速地共享数组对象,能节省内存空间,但容易出现内存泄漏。
  • 从Java7到Java8版本,Java对String做了一些改变。String类中不再有offset和count两个属性了。这样做可以使String对象占用的内存减少,并且String.substring方法也不再共享char[],解决了可能出现的内存泄漏的问题。
  • 从Java9版本开始,将char[]改成了byte[],并增加了新属性coder,coder是一个编码格式的标识。

  为什么要这么改呢?

  我们知道,一个char字符占16位,2个字节。这种情况下存储单字节的字符就很容易浪费了。JDK1.9的String类为了节省内存空间,就使用了占8位,1个字节的byte数组来存储字符串。

  coder属性的作用是:在计算字符串长度或者使用indexOf()时,需要根据这个字段,判断如何计算字符串的长度。coder属性值默认有0和1两个值,0代表Latin-1(单字节编码),1代表UTF-16。如果String判断字符串只包含Latin-1,则coder值取0,反之为1。

  String对象的不可变性

  如果看过String的源码,就会发现,String类是被final关键字修饰的,且变量char数组也被final修饰。

  一个类被final修饰代表着该类不可继承,char[]被private和final修饰着,代表String对象不可被更改。这就叫做String对象的不可变性。即如果String对象一旦创建成功了,就不能再对它进行改变。

  这样做的好处在哪里?  

  第一、保证了String对象的安全性。假设String对象是可变的,那么String对象就会被恶意修改。

  第二.、保证hash属性值不会频繁变更,确保了唯一性。使得类似HashMap容器才能实现相应的key-value缓存功能。

  第三、可以实现字符串常量池。Java中,通常有2种创建字符串对象的方式,一种是通过字符串常量的方式创建,如String str = "abc";另一种是字符串常量通过new形式的创建,如String str = new String("abc")。

  当代码中使用第一种方式创建字符串对象时,JVM首先检查该对象是否在字符串常量池中,如果在就返回该对象的引用,否则新的字符串将在常量池中创建。这种方式可以减少同一个值的字符串对象的重复创建,节约内存。

  第二种方式,首先在编译类文件时,"abc"常量字符串将会放入到常量结构中,在类加载时,"abc"会在常量池中创建;然后调用new时,JVM命令将会调用String的构造函数,同时引用常量池的"abc"字符串,在堆内存中创建一个String对象,最后str引用String对象。

  

  String对象的优化

  1.如何构建超大字符串

  编程过程中字符串的拼接很常见。如果使用String对象相加,拼接我们想要的字符串,会不会产生多个对象呢?比如说以下代码:

String str = "ab" + "cd" + "ef";

  分析代码可知:首先会生成ab对象,再生成abcd对象,最后生成abcdef对象。理论上说,代码很低效。

  但实际上,会发现只有一个对象生成,这是为什么呢?编译时编译器会自动帮我们优化代码,使得最后只得出一个对象“abcdef”。

  再来看看,如果进行字符串常量的累计,又会出现什么结果?

String str = "abcdef";
for (int i = 0; i < 100; i++) {
str = str + i;
}

  上面的代码编译后,编译器同样对代码进行了优化,在进行字符串拼接时,偏向使用StringBuilder,这样可以提升效率。上面的代码变成了下面这样:

String str = "abcdef";
for (int i = 0; i < 100; i++) {
str = (new StringBuilder(String.valueOf(str))).append(i).toString();
}

  总结:即使使用+号作为字符串的拼接,一样可以被编译器优化成StringBuilder的方式。但如果每次循环都生成一个新的StringBuilder实例,同样会降低系统的性能。所以平时做字符串拼接的时候,建议还是显示使用StringBuilder来提升性能。在多线程编程时,String对象的拼接涉及到了线程安全,可以使用StringBuffer。但由于StringBuffer是线程安全的,涉及到锁竞争,所以就性能上来说会比StringBuilder差些。

  2.如何使用String.intern节省内存?

  对于一些数据,数据量非常大,但同时又有大部分重合的,该如何处理呢?

  具体做法是,每次赋值的时候使用String的intern方法,如果常量池中有相同值,就会重复使用该对象,返回对象的引用,这样一开始的对象就可以被回收掉了,这样的话数据量就会大幅度降低了。

  我们再来看一个例子:

String a = new String("abc").intern();
String b = new String("abc").intern();
if (a == b) {
System.out.println("a == b");
}

  输出结果是: a == b

  在字符串常量池中,默认会将对象放入常量池;在字符串变量中,对象总是创建在堆内存的,同时也会在常量池中创建一个字符串对象,复制到堆内存对象中,并返回堆内存对象引用。

  如果调用intern方法,会去查看字符串常量池中是否有等于该对象的字符串,如果没有,就会在常量池中新增该对象,并返回该对象引用;如果有则返回常量池中的字符串引用。堆内存中原有的对象由于没有引用指向它,将会通过垃圾回收器回收。

  3.如何使用字符串的分割方法?

  spilt()方法使用了正则表达式实现了强大的分割功能,而正则表达式的性能是非常不稳定的,使用不当会引起回溯问题,很可能导致CPU居高不下。

  所以要慎重使用spilt方法,我们可以用String.indexOf()方法代替spilt()方法完成字符串的分割,如果实在无法满足需求,就在使用spilt方法时,对回溯问题加以重视就可以了。

  总结

  通过上面的叙述,我们认识到了做好String字符串性能的优化,可以提升整个系统的性能。在这个理论基础上,Java版本在迭代中不断更改成员变量,节约内存空间,对String性能进行了优化。

  我们还提到了String对象的不可变性,正是这个特性实现了字符串常量池,通过减少同一个值的字符串对象的重复创建,进一步节约内存。也是因为这个特性,我们在做长字符串的拼接时,需要显示使用StringBuilder,以提升字符串的拼接性能。最后在优化方面,我们还可以使用intern方法,让变量字符串对象重复使用常量池中相同值的对象,进而节约内存。

  最后,公布上面那道题的结果:

  false、false、true。

  其中, String str1 = “abc”;通过字面量的方式创建,abc存储于字符串常量池中;

  String str2 = new String("abc");通过new对象的方式创建字符串对象,引用地址存放在堆内存中,abc则存放在字符串常量池中,所以为false;

  String str3 = str2.intern();由于调用了intern()方法,会返回常量池中的数据,str3此时就指向常量池中的abc,和str1的方式一样,所以为true;

  

性能调优 -- Java编程中的性能优化的更多相关文章

  1. 性能调优之访问日志IO性能优化

    性能调优之访问日志IO性能优化   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821 ...

  2. OCM_第十五天课程:Section6 —》数据库性能调优 _SQL 访问建议 /SQL 性能分析器/配置基线模板/SQL 执行计划管理/实例限制

    注:本文为原著(其内容来自 腾科教育培训课堂).阅读本文注意事项如下: 1:所有文章的转载请标注本文出处. 2:本文非本人不得用于商业用途.违者将承当相应法律责任. 3:该系列文章目录列表: 一:&l ...

  3. Java编程中“为了性能”尽量要做到的一些地方

    最近的机器内存又爆满了,除了新增机器内存外,还应该好好review一下我们的代码,有很多代码编写过于随意化,这些不好的习惯或对程序语言的不了解是应该好好打压打压了. 下面是参考网络资源总结的一些在Ja ...

  4. Java性能调优:利用JMC分析性能

    Java性能调优作为大型分布式系统提供高性能服务的必修课,其重要性不言而喻. 好的分析工具能起到事半功倍的效果,利用分析利器JMC.JFR,可以实现性能问题的准确定位. 本文主要阐述如何利用JMC分析 ...

  5. Java性能调优:利用JFR生成性能日志

    Java性能调优作为大型分布式系统提供高性能服务的必修课,其重要性不言而喻. 好的分析工具能起到事半功倍的效果,利用分析利器JMC.JFR,可以实现性能问题的准确定位. 本文主要阐述如何利用JFR生成 ...

  6. java编程中'为了性能'一些尽量做到的地方

    原文地址:http://blog.csdn.NET/m13666368773/article/details/7796924 最近的机器内存又爆满了,出了新增机器内存外,还应该好好review一下我们 ...

  7. 性能调优3:硬盘IO性能

    数据库系统严重依赖服务器的资源:CPU,内存和硬盘IO,通常情况下,内存是数据的读写性能最高的存储介质,但是,内存的价格昂贵,这使得系统能够配置的内存容量受到限制,不能大规模用于数据存储:并且内存是易 ...

  8. Java编程中“为了性能”需做的26件事

    1.尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: (1)控制资源的使用,通过线程同步来控制 ...

  9. 【转载】java编程中'为了性能'一些尽量做到的地方

    1.尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面 第一,控制资源的使用,通过线程同步来控制资 ...

随机推荐

  1. 【如何让代码变“高级”(二)】-这样操作值得一波666(Java Stream)(这么有趣)

    [如何让代码变“高级”(二)]-这样操作值得一波666(Java Stream)(这么有趣) 开发中的代码 在开发中的代码是不是很常见这样的代码: 这样的? for循环取元素取值 List<Us ...

  2. MySQL必知必会(Select, Order by子句)

    SELECT prod_name FROM products ORDER BY prod_name; SELECT prod_id, prod_price, prod_name FROM produc ...

  3. 入门级实操教程!从概念到部署,全方位了解K8S Ingress!

    Kubernetes Ingress用于添加规则,以将流量从外部路由到Kubernetes集群的服务中.在本文中你将了解ingress 的概念,以及用于路由外部流量到Kubernetes deploy ...

  4. Xcode 清理存储空间(转)

    一 移除 Xcode 运行安装 APP 产生的缓存文件(DerivedData) 只要重新运行Xcode就一定会重新生成,而且会随着运行程序的增多,占用空间会越来越大.删除后在重新运行程序可能会稍微慢 ...

  5. UESTC1977-图书馆(AC自动机应用)

    M - 图书馆 Time Limit: 2000 MS     Memory Limit: 256 MB Submit Status 电子科技太学图书馆创建于1956年,馆舍总面积66974平方米,各 ...

  6. java之单例设计模式

    什么是设计模式? 设计模式是在大量的实践中总结和理论化之后优选的代码结构.编程风格.以及解决问题的思考方式.设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和探索. 所谓单例 ...

  7. 【JS】310- 使用 focusout 事件,解决 iOS 键盘收起不归位问题

    点击上方"前端自习课"关注,学习起来~ ,0);            }        }, 这时,我们问题得到解决了,当从输入框输入内容,然后点击键盘的完成收起键盘,效果符合我 ...

  8. 【HTTP】267- HTTP 的15个常见知识点复习

    前言 自从入职新公司到现在,我们前端团队内部一直在做 ?每周一练 的知识复习计划,我之前整理了一个 [每周一练 之 数据结构与算法] (https://juejin.im/post/5ce2a20e6 ...

  9. Linux搭建Java环境(JDK+Tomcat+MySQL)

    目录 一.项目环境: 二.安装JDK1.8 三.安装Tomcat8.5 四.安装MySQL数据库 五.配置JAVA项目 一.项目环境: 开发环境 生产环境 测试环境 硬件环境: web服务器:cpu: ...

  10. Orleans 初接触(二) 测试用例

    [返回导航] 在简单了解了Orleans 之后我们可以通过几个例子去加深印象 一.快速入门示例 这个例子也是跟着<Microsoft Orleans 之 入门指南>(https://www ...