有效处理包含操作的Java类

时间:2013-09-06 10:45:47

标签: java performance contains

我在迭代算法中使用HashSet,通过添加新对象(通过方法add)在每次算法迭代时动态放大。我经常使用HashSet方法检查生成的对象是否已经放在contains内。 观察HashSet可能包含数千个对象

以下是有关课程HashSet的文章的引文: “此类为基本操作(添加,删除,包含和大小)提供恒定的时间性能,假设散列函数在桶之间正确地分散元素。

除了doc中提供的其他注意事项(为简单起见未报告),我发现addcontains是在恒定时间内执行的。 请您能否在Java中建议另一种数据结构,以便针对我的问题为“包含”操作提供更好的性能?

也接受来自Apache Commons或Guava的类。

3 个答案:

答案 0 :(得分:3)

如果对象具有正确实现的hashCode()方法,HashSet.contains()的性能将尽可能好。这将确保在桶之间正确分配。

请参阅Best implementation for hashCode method

答案 1 :(得分:1)

正如其他答案已经说明的那样,“恒定时间”是您可以获得的最佳运行时行为。 如果你得到它取决于你的hashcode实现,但是因为你使用NetBeans建议你不应该太糟糕。

关于如何保持“恒定时间”尽可能小:

  • 尝试从一开始就将HashSet分配得足够大,以避免代价高昂的rehash-operations
  • 您可以在第一次调用hashCode()时缓存计算的哈希码,并在以后返回缓存值。应该没有必要添加一些触发机制来清除对象更新的缓存,因为你的相关字段应该是不可变的 - 如果它们不是你,你肯定会使用HashSet遇到麻烦。

答案 2 :(得分:-2)

如果已将对象放入该hashset中,则可以让对象记住。如果它被添加到哈希集中,只需要存储一个布尔字段。然后,您不需要在HashSet上调用contains,而只需读取对象的字段值。只有在将对象放入一个将检查布尔字段的哈希集时,此方法才有效。

在散列集中包含的对象中,可以使用java.util.BitSet扩展为常量数量的散列集,其中当算法开始之前已知散列集的数量时,每个散列集都可以由唯一整数标识。

因为你说你经常调用contains,所以用相同的现有对象(对象池)替换新生成的对象是有意义的,因为它的开销将通过包含只是一个字段来分摊读取。

这里要求的是一些示例代码。特殊集实现的速度比我机器上的普通散列集快4倍。但问题是此代码如何反映您的用例。

public class FastSetContains {

    public static class SetContainedAwareObject {
        private final int state;
        private boolean contained;

        public SetContainedAwareObject(int state) {
            this.state = state;
        }

        public void markAsContained() {
            contained = true;
        }

        public boolean isContained() {
            return contained;
        }

        public void markAsRemoved() {
            contained = false;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + state;
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            SetContainedAwareObject other = (SetContainedAwareObject) obj;
            if (state != other.state)
                return false;
            return true;
        }
    }

    public static class FastContainsSet extends
            HashSet<SetContainedAwareObject> {

        @Override
        public boolean contains(Object o) {
            SetContainedAwareObject obj = (SetContainedAwareObject) o;
            if (obj.isContained()) {
                return true;
            }
            return super.contains(o);
        }

        @Override
        public boolean add(SetContainedAwareObject e) {
            boolean add = super.add(e);
            e.markAsContained();
            return add;
        }

        @Override
        public boolean addAll(Collection<? extends SetContainedAwareObject> c) {
            boolean addAll = super.addAll(c);
            for (SetContainedAwareObject o : c) {
                o.markAsContained();
            }
            return addAll;
        }

        @Override
        public boolean remove(Object o) {
            boolean remove = super.remove(o);
            ((SetContainedAwareObject) o).markAsRemoved();
            return remove;
        }

        @Override
        public boolean removeAll(Collection<?> c) {
            boolean removeAll = super.removeAll(c);
            for (Object o : c) {
                ((SetContainedAwareObject) o).markAsRemoved();
            }
            return removeAll;
        }
    }

    private static final Random random = new Random(1234L);
    private static final int additionalObjectsPerIteration = 10;
    private static final int iterations = 100000;
    private static final int differentObjectCount = 100;
    private static final int containsCountPerIteration = 50;

    private static long nanosSpentForContains;

    public static void main(String[] args) {
        Map<SetContainedAwareObject, SetContainedAwareObject> objectPool = new HashMap<>();

        // switch comment use different Set implementaiton
        //Set<SetContainedAwareObject> set = new FastContainsSet();
        Set<SetContainedAwareObject> set = new HashSet<>();

        //warm up
        for (int i = 0; i < 100; i++) {
            addAdditionalObjects(objectPool, set);
            callSetContainsForSomeObjects(set);
        }
        objectPool.clear();
        set.clear();
        nanosSpentForContains = 0L;

        for (int i = 0; i < iterations; i++) {
            addAdditionalObjects(objectPool, set);
            callSetContainsForSomeObjects(set);
        }
        System.out.println("nanos spent for contains: " + nanosSpentForContains);
    }

    private static void callSetContainsForSomeObjects(
            Set<SetContainedAwareObject> set) {
        int containsCount = set.size() > containsCountPerIteration ? set.size()
                : containsCountPerIteration;
        int[] indexes = new int[containsCount];
        for (int i = 0; i < containsCount; i++) {
            indexes[i] = random.nextInt(set.size());
        }
        Object[] elements = set.toArray();

        long start = System.nanoTime();
        for (int index : indexes) {
            set.contains(elements[index]);
        }
        long end = System.nanoTime();
        nanosSpentForContains += (end - start);
    }

    private static void addAdditionalObjects(
            Map<SetContainedAwareObject, SetContainedAwareObject> objectPool,
            Set<SetContainedAwareObject> set) {
        for (int i = 0; i < additionalObjectsPerIteration; i++) {
            SetContainedAwareObject object = new SetContainedAwareObject(
                    random.nextInt(differentObjectCount));
            SetContainedAwareObject pooled = objectPool.get(object);
            if (pooled == null) {
                objectPool.put(object, object);
                pooled = object;
            }
            set.add(pooled);
        }
    }
}

Anothe编辑:

使用以下作为Set.contains实现使其比普通hashset快8倍:

    @Override
    public boolean contains(Object o) {
        SetContainedAwareObject obj = (SetContainedAwareObject) o;
        return obj.isContained();
    }

修改 这种技术与OpenJPA的类增强有一点共同之处。 OpenJPA的增强使类能够跟踪实体管理器使用的持久状态。建议的方法使对象能够跟踪其自身是否包含在算法使用的集合中。