我在迭代算法中使用HashSet
,通过添加新对象(通过方法add
)在每次算法迭代时动态放大。我经常使用HashSet
方法检查生成的对象是否已经放在contains
内。 观察HashSet
可能包含数千个对象。
以下是有关课程HashSet
的文章的引文:
“此类为基本操作(添加,删除,包含和大小)提供恒定的时间性能,假设散列函数在桶之间正确地分散元素。”
除了doc中提供的其他注意事项(为简单起见未报告),我发现add
和contains
是在恒定时间内执行的。
请您能否在Java中建议另一种数据结构,以便针对我的问题为“包含”操作提供更好的性能?
也接受来自Apache Commons或Guava的类。
答案 0 :(得分:3)
如果对象具有正确实现的hashCode()方法,HashSet.contains()的性能将尽可能好。这将确保在桶之间正确分配。
答案 1 :(得分:1)
正如其他答案已经说明的那样,“恒定时间”是您可以获得的最佳运行时行为。 如果你得到它取决于你的hashcode实现,但是因为你使用NetBeans建议你不应该太糟糕。
关于如何保持“恒定时间”尽可能小:
答案 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的增强使类能够跟踪实体管理器使用的持久状态。建议的方法使对象能够跟踪其自身是否包含在算法使用的集合中。