Java代码优化的金科玉律不外乎以下内容:
良好的设计将会使优化变得更加容易。
过早的优化并不能解决多有的性能问题,但是不良的设计将会导致优化难度的增加。
理论就先谈到这里。假设我们已经发现了问题出现在了右分支上,很有可能是因产品中的简单处理因耗费了大量的时间而失去响应(假设N、O和 P 的值非常大), 请注意文章中提及的左分支的时间复杂度为 O(N3)。这里所做出的努力并不能扩展,但可以为用户节省时间,将困难的性能改善推迟到后面再进行。今天蓝桥先给大家推荐5条改善Java性能的小建议:
1、使用StringBuilder
StingBuilder 应该是在我们的Java代码中默认使用的,应该避免使用 + 操作符。或许你会对 StringBuilder 的语法糖(syntax sugar)持有不同意见,比如:
将会被编译为:
但究竟发生了什么?接下来是否需要用下面的部分来对 String 类改善呢?
现在使用到了第二个 StringBuilder,而且这个 StringBuilder 不会消耗堆中额外的内存,但却给 GC 带来了压力。
2、避免使用正则表达式
正则表达式给人的印象是快捷简便。但是在 N.O.P.E 分支中使用正则表达式将是最糟糕的决定。如果万不得已非要在计算密集型代码中使用正则表达式的话,至少要将 Pattern 缓存下来,避免反复编译Pattern。
如果仅使用到了如下这样简单的正则表达式的话:
这是最好还是用普通的 char[] 数组或者是基于索引的操作。比如下面这段可读性比较差的代码其实起到了相同的作用。
上面的代码同时表明了过早的优化是没有意义的。虽然与 split() 方法相比较,这段代码的可维护性比较差。
3、不要使用iterator()方法
这条建议不适用于一般的场合,仅适用于在 N.O.P.E 分支深处的场景。尽管如此也应该有所了解。Java 5格式的循环写法非常的方便,以至于我们可以忘记内部的循环方法,比如:
当每次代码运行到这个循环时,如果 strings 变量是一个 Iterable 的话,代码将会自动创建一个Iterator 的实例。如果使用的是 ArrayList 的话,虚拟机会自动在堆上为对象分配3个整数类型大小的内存。
也可以用下面等价的循环方式来替代上面的 for 循环,仅仅是在栈上“浪费”了区区一个整形,相当划算。
如果循环中字符串的值是不怎么变化,也可用数组来实现循环。
4、不要调用高开销方法
有些方法的开销很大。以 N.O.P.E 分支为例,我们没有提到叶子的相关方法,不过这个可以有。假设我们的JDBC驱动需要排除万难去计算 ResultSet.wasNull() 方法的返回值。我们自己实现的SQL框架可能像下面这样:在上面的逻辑中,每次从结果集中取得 int 值时都要调用 ResultSet.wasNull() 方法,但是 getInt() 的方法定义为:返回类型:变量值;如果SQL查询结果为NULL,则返回0。所以一个简单有效的改善方法如下:
这是轻而易举的事情。
5、使用原始类型和栈
上面介绍了来自 jOOQ的例子中使用了大量的泛型,导致的结果是使用了 byte、 short、 int 和 long 的包装类。但至少泛型在Java 10或者Valhalla项目中被专门化之前,不应该成为代码的限制。因为可以通过下面的方法来进行替换:
……如果这样写的话:
在使用数组时情况可能会变得更加糟糕:
……如果这样写的话:
例外下面的情况对这条规则例外:因为 boolean 和 byte 类型不足以让JDK为其提供缓存方法。我们可以这样写:
其它整数基本类型也有类似情况,比如 char、short、int、long。不要在调用构造方法时将这些整型基本类型自动装箱或者调用 TheType.valueOf() 方法。也不要在包装类上调用构造方法,除非你想得到一个不在堆上创建的实例。这样做的好处是为你为同事献上一个巨坑的愚人节笑话。
非堆存储
当然了,如果你还想体验下堆外函数库的话,尽管这可能参杂着不少战略决策,而并非最乐观的本地方案。一篇由Peter Lawrey和 Ben Cotton撰写的关于非堆存储的很有意思文章请点击: OpenJDK与HashMap——让老手安全地掌握(非堆存储!)新技巧。
上一篇:还没学会用成本思维做决策?
下一篇:三十五年经验分享:程序员进阶八法