为什么Set.contains()对于Point要比对于大小为2的List <integer>快得多?</integer>

时间:2014-05-13 11:19:28

标签: java performance set lookup timing

我最近需要对一组整数对进行多次查找,以解决编程竞赛中的问题。我用Java编程。通常我会使用数组作为整数对,但由于Java中的集合不适用于数组,我这次决定使用列表。我后来发现我的解决方案很慢,但使用Point的另一个参赛者的解决方案要快得多。

我实施了一个小基准来比较两者。我发现Set.contains() Set<List<Integer>>(每个List<Integer>的大小为2)平均约为Set<Point>的4.5倍,无论该对是{}实际上包含在集合中。我认为原因可能源于hashCode()的不同实现,并且还实现了使用Point的自定义List.hashCode()类。这似乎是原因的一部分,但与实际List<Integer>的差异仍然很大。

这是我的基准代码:

import java.awt.Point;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;

public class IntPairTiming {

    public static final int SIZE = 1_000_000;
    public static final int TRIES = 100_000_000;

    public static void main(String[] args) {

        Random random = new Random();

        // Pairs to store
        Set<Point> pointSet = new HashSet<>();
        Set<List<Integer>> listSet = new HashSet<>();
        Set<MyPoint> myPointSet = new HashSet<>();
        for (int i = 0; i < SIZE; i++) {
            Point point = new Point(random.nextInt(), random.nextInt());
            pointSet.add(point);
            List<Integer> list = Arrays.asList(point.x, point.y);
            listSet.add(list);
            myPointSet.add(new MyPoint(point.x, point.y, list));
        }

        // Pair to lookup
        Point point = new Point(random.nextInt(), random.nextInt());
        List<Integer> list = Arrays.asList(point.x, point.y);
        MyPoint myPoint = new MyPoint(point.x, point.y, list);
        pointSet.add(point);
        listSet.add(list);
        myPointSet.add(myPoint);

        // Time set of points
        timeLookup(pointSet, point, "Set of Points");

        // Time set of lists
        timeLookup(listSet, list, "Set of Lists");

        // Time set of mypoints
        timeLookup(myPointSet, myPoint, "Set of MyPoints");
    }

    private static <T> void timeLookup(Set<T> set, T obj, String what) {
        System.out.println("-----------");
        System.out.printf("%s:%n", what);
        System.out.printf("Contains = %s%n", set.contains(obj));
        long s = System.nanoTime();
        for (int i = 0; i < TRIES; i++) set.contains(obj);
        long e = System.nanoTime();
        System.out.printf("Average lookup = %sns%n", (e - s) / TRIES);
    }

    private static class MyPoint extends Point {

        private final List<Integer> list;

        public MyPoint(int x, int y, List<Integer> list) {
            super(x, y);
            this.list = list;
        }

        @Override
        public int hashCode() {
            return this.list.hashCode();
        }
    }
}

输出:

-----------
Set of Points:
Contains = true
Average lookup = 12ns
-----------
Set of Lists:
Contains = true
Average lookup = 55ns
-----------
Set of MyPoints:
Contains = true
Average lookup = 25ns

为什么会出现如此显着的差异?根据我的理解,hashCode()equals()PointList<Integer>的大小为2时应该或多或少地等效。如果我们需要查找超过两个元素,类似于Point的自定义类总是比列表更有效吗?

2 个答案:

答案 0 :(得分:3)

这是基于expense for equals() on any sort of AbstractList大于人们想象的事实。

实现需要构造一个迭代器,迭代它,并比较所有元素(以及hashCode也需要),而专用的Point类有两个比较,非常容易通过JIT(由于字段偏移量没有变化那么多),而列表需要更多的间接。

在你的字段编号为十位的某一点(没有双关语),创建一个单独的元组类变得过早优化,除非该元组类本身有一些用作POJO本身。

答案 1 :(得分:1)

Point包含原始字段,而List<Integer>包含对象,但非常简单。

Point的哈希码计算将直接使用这些字段,而松散地说,List<Integer>的哈希码计算需要两次以上的参数操作才能获得实际值。