我正在尝试在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;
}
}
如您所见,问题有两个:
contains
,一次用于add
。但好吧,这可能不是一个太大的性能影响,因为contains
之后正确的存储区将位于缓存中,因此add
不会触发缓存未命中因此非常快。PROBLEM
的行)。没有方法可以检索集合中的元素。所以这是不可能实现的。我在这里遗漏了什么吗?或者用java.util.HashSet
构建通常的哈希缺点真的不可能吗?
答案 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);
}
}
}