欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

几个小细节帮你提升java代码运行效率

程序员文章站 2022-06-07 11:52:57
...

引言

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

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/javase/8/docs/api/java/util/Optional.html#orElse-T-