Java的性能可选

jod*_*jod 18 java

我只是偶然发现了Java 8中的Optional类 - 我真的喜欢在我的代码中使用isPresent()方法调用替换一些空检查(字面意思是"值是否存在?")的方法.

我的问题是:这不会导致我的代码性能下降吗?我只是猜测简单的空检查可能会有点便宜而且我在字节码读取/解释方面还不是很好,所以我真的对你对这个主题的想法很感兴趣.

Jav*_*tín 13

Optional<T>它只是一个普通的泛型类,它包含一个类型为T的引用.因此,它添加了一个单向的间接层.方法调用本身也不会非常昂贵,因为类是final,因此可以避免动态调度.

你可能遇到性能问题的唯一地方就是在处理大量此类实例时,但即便如此,像a这样的东西的表现也一点Stream<Optional<String>>都不差.然而,随着大量的原始值的工作时,你会发现使用性能损失Stream<Integer>(或Integer[])与原始专业化IntStream(或int[])由于这层间接的,需要的非常频繁实例化Integer对象.然而,这是我们已经知道并且在使用类似物时付出的代价ArrayList<Integer>.

您显然会遇到与Stream<OptionalInt>/ 相同的命中OptionalInt[],因为OptionalInt基本上是一个带有int字段和存在boolean标志的类(与Optional<T>仅使用T字段的情况不同)因此非常相似,Integer尽管尺寸更大.当然,a Stream<Optional<Integer>>会增加两个间接级别,相应的双重性能损失.


Yur*_*ian 10

我使用一种大量使用空检查的算法进行了一些性能测试,并访问了一个可能为空的字段.我实现了一个简单的算法,从单个链表中删除中间元素.

首先,我实现了两类链表节点:safe - with Optional和unsafe - without.

安全节点

class Node<T> {
    private final T data;
    private Optional<Node<T>> next = Optional.empty();

    Node(T data) {

        this.data = data;
    }

    Optional<Node<T>> getNext() {
        return next;
    }

    void setNext(Node<T> next) { setNext(Optional.ofNullable(next)); }

    void setNext(Optional<Node<T>> next ) { this.next = next; }
}
Run Code Online (Sandbox Code Playgroud)

不安全的节点

class NodeUnsafe<T> {
    private final T data;
    private NodeUnsafe<T> next;

    NodeUnsafe(T data) {
        this.data = data;
    }

    NodeUnsafe<T> getNext() {
        return next;
    }

    void setNext(NodeUnsafe<T> next) {
        this.next = next;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我实现了两个相似的方法,唯一的区别 - 第一次使用Node<T>,第二次使用NodeUsafe<T> 类DeleteMiddle {

class DeleteMiddle {
    private static <T> T getLinkedList(int size, Function<Integer, T> supplier, BiConsumer<T, T> reducer) {
        T head = supplier.apply(1);
        IntStream.rangeClosed(2, size).mapToObj(supplier::apply).reduce(head,(a,b)->{
            reducer.accept(a,b);
            return b;
        });
        return head;
    }

    private static void deleteMiddle(Node<Integer> head){
        Optional<Node<Integer>> oneStep = Optional.of(head);
        Optional<Node<Integer>> doubleStep = oneStep;
        Optional<Node<Integer>> prevStep = Optional.empty();

        while (doubleStep.isPresent() && doubleStep.get().getNext().isPresent()){
            doubleStep = doubleStep.get().getNext().get().getNext();
            prevStep = oneStep;
            oneStep = oneStep.get().getNext();
        }

        final Optional<Node<Integer>> toDelete = oneStep;
        prevStep.ifPresent(s->s.setNext(toDelete.flatMap(Node::getNext)));
    }

    private static void deleteMiddleUnsafe(NodeUnsafe<Integer> head){
        NodeUnsafe<Integer> oneStep = head;
        NodeUnsafe<Integer> doubleStep = oneStep;
        NodeUnsafe<Integer> prevStep = null;

        while (doubleStep != null && doubleStep.getNext() != null){
            doubleStep = doubleStep.getNext().getNext();
            prevStep = oneStep;
            oneStep = oneStep.getNext();
        }
        if (prevStep != null) {
            prevStep.setNext(oneStep.getNext());
        }
    }

    public static void main(String[] args) {
        int size = 10000000;
        Node<Integer> head = getLinkedList(size, Node::new, Node::setNext);
        Long before = System.currentTimeMillis();
        deleteMiddle(head);
        System.out.println("Safe: " +(System.currentTimeMillis() - before));

        NodeUnsafe<Integer> headUnsafe = getLinkedList(size, NodeUnsafe::new, NodeUnsafe::setNext);
        before = System.currentTimeMillis();
        deleteMiddleUnsafe(headUnsafe);
        System.out.println("Unsafe: " +(System.currentTimeMillis() - before));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用不同大小的列表进行的两次运行的比较表明,使用Optional最佳代码的方法比使用nullables的代码慢两倍.使用小列表,它慢3倍.