String性能提升10倍的几个方法,看完受益匪浅!(源码+原理分析)(1)

}

}

public static String doAdd() {

String result = “”;

for (int i = 0; i < 10000; i++) {

result += (“Java中文社群:” + i);

}

return result;

}

public static String doAppend() {

StringBuilder sb = new StringBuilder();

for (int i = 0; i < 10000; i++) {

sb.append(“Java中文社群:” + i);

}

return sb.toString();

}

}

以上程序的执行结果如下:

String 拼加,执行时间:429StringBuilder 拼加,执行时间:1

String 拼加,执行时间:325StringBuilder 拼加,执行时间:1

String 拼加,执行时间:287StringBuilder 拼加,执行时间:1

String 拼加,执行时间:265StringBuilder 拼加,执行时间:1

String 拼加,执行时间:249StringBuilder 拼加,执行时间:1

从结果可以看出,优化前后的性能相差很大。

注意:此性能测试的结果与循环的次数有关,也就是说循环的次数越多,他们性能相除的结果也越大。

接下来,我们要思考一个问题:为什么 StringBuilder.append() 方法比 += 的性能高?而且拼接的次数越多性能的差距也越大?

当我们打开 StringBuilder 的源码,就可以发现其中的“小秘密”了,StringBuilder 父类 AbstractStringBuilder 的实现源码如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence {

char[] value;

int count;

@Override

public AbstractStringBuilder append(CharSequence s, int start, int end) {

if (s == null)

s = “null”;

if ((start < 0) || (start > end) || (end > s.length()))

throw new IndexOutOfBoundsException(

"start " + start + ", end " + end + ", s.length() "

+ s.length());

int len = end - start;

ensureCapacityInternal(count + len);

for (int i = start, j = count; i < end; i++, j++)

value[j] = s.charAt(i);

count += len;

return this;

}

// 忽略其他信息…

}

而 StringBuilder 使用了父类提供的 char[] 作为自己值的实际存储单元,每次在拼加时会修改 char[] 数组,StringBuilder toString() 源码如下:

@Override

public String toString() {

// Create a copy, don’t share the array

return new String(value, 0, count);

}

综合以上源码可以看出:StringBuilder 使用了 char[] 作为实际存储单元,每次在拼加时只需要修改 char[] 数组即可,只是在 toString() 时创建了一个字符串;而 String 一旦创建之后就不能被修改,因此在每次拼加时,都需要重新创建新的字符串,所以 StringBuilder.append() 的性能就会比字符串的 += 性能高很多

2.善用 intern 方法

=================================================================================

善用 String.intern() 方法可以有效的节约内存并提升字符串的运行效率,先来看 intern() 方法的定义与源码:

/**

* Returns a canonical representation for the string object.

* A pool of strings, initially empty, is maintained privately by the

* class {@code String}.

* When the intern method is invoked, if the pool already contains a

* string equal to this {@code String} object as determined by

* the {@link #equals(Object)} method, then the string from the pool is

* returned. Otherwise, this {@code String} object is added to the

* pool and a reference to this {@code String} object is returned.

* It follows that for any two strings {@code s} and {@code t},

* {@code s.intern() == t.intern()} is {@code true}

* if and only if {@code s.equals(t)} is {@code true}.

* All literal strings and string-valued constant expressions are

* interned. String literals are defined in section 3.10.5 of the

The Java™ Language Specification.

* @return  a string that has the same contents as this string, but is

*          guaranteed to be from a pool of unique strings.

*/

public native String intern();

可以看出 intern() 是一个高效的本地方法,它的定义中说的是,当调用 intern 方法时,如果字符串常量池中已经包含此字符串,则直接返回此字符串的引用,如果不包含此字符串,先将字符串添加到常量池中,再返回此对象的引用。

那什么情况下适合使用 intern() 方法?

Twitter 工程师曾分享过一个 String.intern() 的使用示例,Twitter 每次发布消息状态的时候,都会产生一个地址信息,以当时 Twitter 用户的规模预估,服务器需要 32G 的内存来存储地址信息。

public class Location {

private String city;

private String region;

private String countryCode;

private double longitude;

private double latitude;

}

考虑到其中有很多用户在地址信息上是有重合的,比如,国家、省份、城市等,这时就可以将这部分信息单独列出一个类,以减少重复,代码如下:

public class SharedLocation {

private String city;

private String region;

private String countryCode;

}

public class Location {

private SharedLocation sharedLocation;

double longitude;

double latitude;

}

通过优化,数据存储大小减到了 20G 左右。但对于内存存储这个数据来说,依然很大,怎么办呢?

Twitter 工程师使用 String.intern() 使重复性非常高的地址信息存储大小从 20G 降到几百兆,从而优化了 String 对象的存储。

实现的核心代码如下:

SharedLocation sharedLocation = new SharedLocation();

sharedLocation.setCity(messageInfo.getCity().intern());

sharedLocation.setCountryCode(messageInfo.getRegion().intern());

sharedLocation.setRegion(messageInfo.getCountryCode().intern());

从 JDK1.7 版本以后,常量池已经合并到了堆中,所以不会复制字符串副本,只是会把首次遇到的字符串的引用添加到常量池中。此时只会判断常量池中是否已经有此字符串,如果有就返回常量池中的字符串引用。

这就相当于以下代码:

String s1 = new String(“Java中文社群”).intern();

String s2 = new String(“Java中文社群”).intern();

System.out.println(s1 == s2);

执行的结果为:true

此处如果有人问为什么不直接赋值(使用 String s1 = “Java中文社群”),是因为这段代码是简化了上面 Twitter 业务代码的语义而创建的,他使用的是对象的方式,而非直接赋值的方式。更多关于 intern() 的内容可以查看《别再问我new字符串创建了几个对象了!我来证明给你看!》这篇文章。

3.慎重使用 Split 方法

==================================================================================

之所以要劝各位慎用 Split 方法,是因为 Split 方法大多数情况下使用的是正则表达式,这种分割方式本身没有什么问题,但是由于正则表达式的性能是非常不稳定的,使用不恰当会引起回溯问题,很可能导致 CPU 居高不下。

例如以下正则表达式:

String badRegex = “^([hH][tT]{2}[pP]😕/|[hH][tT]{2}[pP][sS]😕/)(([A-Za-z0-9-]+).)+([A-Za-z0-9-\\/])+$”;

String bugUrl = “http://www.apigo.com/dddp-web/pdf/download?request=6e7JGxxxxx4ILd-kExxxxxxxqJ4-CHLmqVnenXC692m74H38sdfdsazxcUmfcOH2fAfY1Vw__%5EDadIfJgiEf”;

if (bugUrl.matches(badRegex)) {

System.out.println(“match!!”);

} else {

System.out.println(“no match!!”);

}

执行效果如下图所示:

image

可以看出,此代码导致了 CPU 使用过高。

Java 正则表达式使用的引擎实现是 NFA(Non deterministic Finite Automaton,不确定型有穷自动机)自动机,这种正则表达式引擎在进行字符匹配时会发生回溯(backtracking),而一旦发生回溯,那其消耗的时间就会变得很长,有可能是几分钟,也有可能是几个小时,时间长短取决于回溯的次数和复杂度。

为了更好地解释什么是回溯,我们使用以下面例子进行解释:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料


《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料

[外链图片转存中…(img-z21h8B6X-1712115715430)]
[外链图片转存中…(img-6kUzbvMW-1712115715430)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值