如何用java HashSet实现高效的哈希缺点

时间:2014-06-06 11:20:34

标签: java hashtable hashset

我正在尝试在java中实现hash cons,与String.intern对字符串的作用相当。即,我希望一个类将一个数据类型T的所有不同值存储在一个集合中,并提供一个T intern(T t)方法来检查集合中是否已t。如果是,则返回集合中的实例,否则将t添加到集合中并返回。原因是可以使用引用相等性来比较结果值,因为从intern返回的两个相等值肯定也是相同的实例。

当然,哈希缺点最明显的候选数据结构是java.util.HashSet<T>。但是,它的界面似乎有缺陷并且不允许有效插入,因为没有方法可以检索已经在集合中的元素,或者如果它不在那里则插入一个元素。

使用HashSet的算法如下所示:

class HashCons<T>{
    HashSet<T> set = new HashSet<>();

    public T intern(T t){
        if(set.contains(t)) {
           return ???;  // <----- PROBLEM
        } else {
           set.add(t); // <--- Inefficient, second hash lookup
           return t;
    }
}

如您所见,问题有两个:

  1. 这个解决方案效率很低,因为我会访问哈希表两次,一次用于contains,一次用于add。但好吧,这可能不是一个太大的性能影响,因为contains之后正确的存储区将位于缓存中,因此add不会触发缓存未命中因此非常快。
  2. 我无法检索集合中已有的元素(请参阅标记为PROBLEM的行)。没有方法可以检索集合中的元素。所以这是不可能实现的。
  3. 我在这里遗漏了什么吗?或者用java.util.HashSet构建通常的哈希缺点真的不可能吗?

2 个答案:

答案 0 :(得分:1)

我认为使用HashSet可能无法做到。您可以使用某种Map,并将您的值用作键作为值。 java.util.concurrent.ConcurrentMap也恰好具有相当方便的方法

putIfAbsent(K key, V value)

如果已经存在,则返回该值。但是,我不知道这种方法的性能(与检查&#34;手动&#34;对Map的非并发实现相比)。

以下是使用HashMap

的方法
class HashCons<T>{
    Map<T,T> map = new HashMap<T,T>();

    public T intern(T t){
        if (!map.containsKey(t))
            map.put(t,t);
        return map.get(t);
    }
}

我认为HashSet无法实现的原因非常简单:如果符合contains(t),则表示给定的t 等于其中一个t&#39;在集合中。没有理由能够归还它(就像你已经拥有它一样)。

答案 1 :(得分:1)

HashSet在OpenJDK中实现为HashMap包装器,因此与aRestless建议的解决方案相比,您在内存使用方面不会获胜。

10分钟草图

class HashCons<T> {
    T[] table;
    int size;
    int sizeLimit;
    HashCons(int expectedSize) {
        init(Math.max(Integer.highestOneBit(expectedSize * 2) * 2, 16));
    }

    private void init(int capacity) {
        table = (T[]) new Object[capacity];
        size = 0;
        sizeLimit = (int) (capacity * 2L / 3);
    }

    T cons(@Nonnull T key) {
        int mask = table.length - 1;
        int i = key.hashCode() & mask;
        do {
            if (table[i] == null) break;
            if (key.equals(table[i])) return table[i];
            i = (i + 1) & mask;
        } while (true);
        table[i] = key;
        if (++size > sizeLimit) rehash();
        return key;
    }

    private void rehash() {
        T[] table = this.table;
        if (table.length == (1 << 30))
            throw new IllegalStateException("HashCons is full");
        init(table.length << 1);
        for (T key : table) {
            if (key != null) cons(key);
        }
    }
}