引言

千万不要小看代码细节的优化,有时候一个很小的优化就要你的代码执行效率数倍提升,如果这个优化点调用比较频繁,甚至有可能解决你整个系统的性能瓶颈。

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个字符。如果没有指定初始值,StringBuilderappend过程中要多次分配空间,拷贝数据。而且在接近1000的时候如果再次分配也是直接翻倍的增加空间,就造成了空间的浪费。

使用并行流

这里说的是streamparallelStream的区别。

parallelStream并行流就是一个把内容分成多个数据块,并用不不同的线程分别处理每个数据块的流。最后合并每个数据块的计算结果。处理的线程数就是机器的处理器核心数。

从原理上讲,大部分场景下并行流处理是更快,来看个例子:

@Test
    public void streamCostTest(){
        List<Integer> intList = mockData();
        useStream(intList);
        useParallelStream(intList);
    }

    /**
     * 构造数据
     *
     * @return
     */
    public List<Integer> mockData() {

        List<Integer> intList = new ArrayList<Integer>();
        for (int i = 0; i < 1000000; i++) {
            intList.add(i);
        }
        return intList;
    }



    public void useStream(List<Integer> 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<Integer> 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

注意上面我提到了大部分场景下。也就是说并行流并不是大杀器,一劳永逸的解决方案。有些地方使用并行流反而性能更差,这里只给一个建议,就是一定要自己测试,测试性能,测试多线程安全等。


参考:

https://docs.oracle.com/javas...


犀牛饲养员
277 声望271 粉丝