JAVA代码的调优思路
文中代码引用极客时间:《Java性能调优实战》-刘超 版权侵删
一、String的使用
1.1 了解一下String的原理
JDK1.6:char[]、offset、hash、count
JDK7/8:char[]、offset
JDK9:byte[]、coder、offset
1.2 字符串对象优化
1.2.1构建大对象时,尽量用StringBuilder对象
1.2.2intern()的使用,str.intern()的一次调用,将查看字符串常量区有没有str的字符串,如果有就直接获得该常量区的引用,如果没有就将字符串放到字符串常量区。
String str1= "abc";
String str2= new String("abc");
String str3= str2.intern();
#将str3的引用指向字符串数据的地址,该地址在常量区
assertSame(str1==str2);
#false
assertSame(str2==str3);
#false
assertSame(str1==str3);
#true
字符串常量区的实现是一个类似于HashTable的实现,当数据量比较大的时候,ygc耗时会越来越长。
二、正则表达式
2.1介绍
java对正则表达式公式进行语法分析,建立一个语法分析树,根据该语法树结合正则表达式引擎生成执行程序,用于匹配字符串。
正则表达式引擎实现有两种方式:DFA(Deterministic Final Automaton确定有限状态自动机)和NFA(None deterministic Final Automaton非确定性有状态自动机)。性能上DFA比NFA的时间复杂度小,但是NFA的功能性比较强,可支持group等功能。
2.2NFA的自动回溯
NFA的贪婪模式(使用了+、?、*、{min,max},正则表达式会尽可能的匹配多的字符串)会出现回溯,大量的回溯会长期占用CPU,从而带来性能开销。
举例来说
String str = "abbc";
String regx = "ab{1,3}c"
匹配过程如下:
1)、拿正则表达式的第一个匹配符“a”和str匹配,发现a和str的第一个字符串匹配,进行接下来的匹配;
2)、拿正则表达式的第二个匹配符“b{1,3}”和str匹配,发现str的第一个b匹配,第二b匹配;
3)、拿str的c和“b{1,3}”匹配时,发现不匹配;
4)、拿str的c和正则表达式的c匹配,发现匹配;结束。
以上步骤中3和4过程str的c字符两次被用来做对比,此处发生回溯。
2.3避免自动回溯
2.3.1懒惰模式在(+、?、*、{min,max})这些字符串后加?,可以减小回溯范围,举例说明:
String str = "abbc";
String regx = "ab{1,3}?c";
匹配过程如下:
1)、拿正则表达式的第一个匹配符“a”和str匹配,发现a和str的第一个字符串匹配,进行接下来的匹配;
2)、拿正则表达式的第二个匹配符“b{1,3}”和str的第一个“b”匹配,发现匹配到字符;
3)、拿正则表达式的第三个匹配符“c”和str的第二个“b”匹配,发现不匹配;
4)、(也是回溯)拿正则表达式的第二个匹配符“b{1,3}”和str的第二个“b”匹配;
5)、拿正则表达式的第三个匹配符“c”和str的“c”匹配,发现匹配,匹配结束。
2.3.2独占模式
同贪婪模式一样,独占模式一样会最大限度地匹配更多内容;不同的是,在独占模式下,匹配失败就会结束匹配,不会发生回溯问题。
String text=“abbc”
String regex=“ab{1,3}+bc”
2.3正则表达式的优化
2.3.1. 少用贪婪模式,多用独占模式
2.3.2减少分支选择
分支选择类型“(X|Y|Z)”的正则表达式会降低性能,我们在开发的时候要尽量减少使用。如果一定要用,我们可以通过以下几种方式来优化:
1.我们需要考虑选择的顺序,将比较常用的选择项放在前面,使它们可以较快地被匹配;
2.将“(abcd|abef)”替换为“ab(cd|ef)”;
3.如果是简单的分支选择类型,我们可以用三次 index 代替“(X|Y|Z)”,如果测试的话,你就会发现三次 index 的效率要比“(X|Y|Z)”高出一些
2.3.3 减少捕获嵌套
public static void main( String[] args )
{
String text = "test";
String reg="(?:)(.*?)(?:)";
#使用“(?:X)”代替“(X)”
Pattern p = Pattern.compile(reg);
Matcher m = p.matcher(text);
while(m.find()) {
System.out.println(m.group(0));
//整个匹配到的内容
System.out.println(m.group(1));
//(.*?)
}
}
三、ArrayListOrLinkedList
3.1 ArrayList
Q1:transient 关键字修饰该字段则表示该属性不会被序列化,但 ArrayList 其实是实现了序列化接口,这到底是怎么回事呢?
A1:ArrayList是基于数组,动态扩展的,不是每个被分配的空间都存储了数据,如果采用了外部序列的方式会序列化整个数组,包括没有存储数据的空间。因此需要用transient关键字修饰这个数组;
3.2性能对比(https://github.com/nickliuchao/collection)
3.2.1ArrayList 和 LinkedList 新增元素操作测试:
从集合头部位置新增元素
从集合中间位置新增元素
从集合尾部位置新增元素
测试结果 (花费时间):
ArrayList>LinkedList
ArrayList
从集合头部位置删除元素
从集合中间位置删除元素
从集合尾部位置删除元素
测试结果 (花费时间):
ArrayList>LinkedList
ArrayList
for(; ; ) 循环
迭代器迭代循环
【JAVA代码的调优思路】测试结果 (花费时间):
ArrayList
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- CVE-2020-16898|CVE-2020-16898 TCP/IP远程代码执行漏洞
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河