引言
千万不要小看代码细节的优化,有时候一个很小的优化就要你的代码执行效率数倍提升,如果这个优化点调用比较频繁,甚至有可能解决你整个系统的性能瓶颈。
orElse和orElseGet
官方文档上是这么说的,
- orElse:Return the value if present, otherwise return other.
- orElseGet:Return the value if present, otherwise invoke other and return the result of that invocation.
描述可能没这么直观,来个例子你就明白了。
public class App {
public static void main(String[] args) {
String input = "input";
String result = Optional.ofNullable(input).orElse(defaultProcess());
System.out.println(result);
}
public static String defaultProcess() {
System.out.println("defalut process");
return "default";
}
}
运行结果:
defalut process
input
然后你猜下下面这段代码的运行结果是啥,
public class App {
public static void main(String[] args) {
String input = "input";
String result = Optional.ofNullable(input).orElseGet(() -> defaultProcess());
System.out.println(result);
}
public static String defaultProcess() {
System.out.println("defalut process");
return "default";
}
}
结果是:
input
到这里你应该已经明白了,orElse
里的逻辑在任何时候都会执行,即使optional不为空。而orElseGet
只在optional是空的时候才会执行。
可以想象,如果在实际项目中defaultProcess
里的逻辑很耗时,使用后者对性能的提示还是很明显的。
循环中减少重复计算
比如把下面这种循环,
for (int i = 0; i < list.size(); i++)
{...}
改成如下这种:
for (int i = 0, length = list.size(); i < length; i++)
{...}
简单的size计算可能对性能影响不大,但是如果循环中的方法计算是类似从数据库count等耗时类的操作,有可能就成为系统的性能瓶颈。
集合数组类的对象初始化指定初始长度。
如果我们能估计大概的内容长度,集合类的实例在创建时最好分配一个初始空间。可以明显的提升性能。这里拿StringBuilder
举个例子,如果我们使用默认的构建器,会初始分配16字符空间,如下:
/**
* Constructs a string builder with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuilder() {
super(16);
}
append操作的时候,如果发现到达了最大容量,它会将自身容量增加到当前的2倍再加2,然后从旧的空间拷贝数据到新的空间。源码如下:
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}
这是一个耗时的动作,而且有时候会浪费空间。试想一下,如果你知道业务场景是大概需要1000个字符。如果没有指定初始值,StringBuilder
在append
过程中要多次分配空间,拷贝数据。而且在接近1000的时候如果再次分配也是直接翻倍的增加空间,就造成了空间的浪费。
使用并行流
这里说的是stream
和parallelStream
的区别。
parallelStream
并行流就是一个把内容分成多个数据块,并用不不同的线程分别处理每个数据块的流。最后合并每个数据块的计算结果。处理的线程数就是机器的处理器核心数。
从原理上讲,大部分场景下并行流处理是更快,来看个例子:
@Test
public void streamCostTest(){
List intList = mockData();
useStream(intList);
useParallelStream(intList);
}
/**
* 构造数据
*
* @return
*/
public List mockData() {
List intList = new ArrayList();
for (int i = 0; i < 1000000; i++) {
intList.add(i);
}
return intList;
}
public void useStream(List integerList) {
long start = System.currentTimeMillis();
long count = integerList.stream().filter(x -> (x%2==0)).count();
System.out.println(count);
long end = System.currentTimeMillis();
System.out.println("useStream cost:" + (end - start));
}
public void useParallelStream(List integerList) {
long start = System.currentTimeMillis();
long count = integerList.parallelStream().filter(x -> (x%2==0)).count();
System.out.println(count);
long end = System.currentTimeMillis();
System.out.println("useParallelStream cost:" + (end - start));
}
测试结果:
500000
useStream cost:42
500000
useParallelStream cost:13
注意上面我提到了大部分场景下。也就是说并行流并不是大杀器,一劳永逸的解决方案。有些地方使用并行流反而性能更差,这里只给一个建议,就是一定要自己测试,测试性能,测试多线程安全等。
参考: