java中的并发双向映射
我正在为文本处理编写代码,如果我先将字符串转换为整数,事情会变得更快。为此,我创建了一个Dictionary类,每次我看到一个新字符串时,我都给它一个索引,并保留两个映射,一个从字符串到int,一个从int到string,这样我就可以很容易地查找两种方式。代码如下:java中的并发双向映射,java,concurrency,hashmap,concurrenthashmap,Java,Concurrency,Hashmap,Concurrenthashmap,我正在为文本处理编写代码,如果我先将字符串转换为整数,事情会变得更快。为此,我创建了一个Dictionary类,每次我看到一个新字符串时,我都给它一个索引,并保留两个映射,一个从字符串到int,一个从int到string,这样我就可以很容易地查找两种方式。代码如下: class Dictionary { private Map<String, Integer> map; private Map<Integer, String> reverse_map;
class Dictionary {
private Map<String, Integer> map;
private Map<Integer, String> reverse_map;
private int nextIndex;
public Dictionary() {
map = new HashMap<String, Integer>();
reverse_map = new HashMap<Integer, String>();
nextIndex = 1;
}
public int getIndex(String string) {
if (!map.containsKey(string)) {
map.put(string, nextIndex);
reverse_map.put(nextIndex, string);
nextIndex++;
}
return map.get(string);
}
public String getString(int index) {
// getIndex is always called first, so we don't need to check anything
return reverse_map.get(index);
}
}
类字典{
私人地图;
私有地图;
私有int nextIndex;
公共词典(){
map=新的HashMap();
反向映射=新的HashMap();
nextIndex=1;
}
public int getIndex(字符串){
如果(!map.containsKey(字符串)){
map.put(字符串,nextIndex);
反向映射put(nextIndex,string);
nextIndex++;
}
返回map.get(字符串);
}
公共字符串getString(int索引){
//getIndex总是先被调用,所以我们不需要检查任何东西
返回反向映射get(索引);
}
}
在我的单线程代码中,这对我来说工作得很好。但是现在我想给它多个线程来加速它,我不知道怎么做。我曾想过使用ConcurrentHashMap,但我不确定
putIfAbsent
是否能保证我不会两次使用索引。我不想使用Collections.synchronizedMap,因为跨线程访问此词典的频率非常高,因此我可能不会比使用单个线程好多少,因为它会阻止每次读写。有什么方法可以使这项工作正常进行吗?并发解决方案的问题是原子性。以下是我的想法:
private final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
private final ConcurrentMap<Integer, String> reverse_map = new ConcurrentHashMap<Integer, String>();
private final AtomicInteger nextIndex = new AtomicInteger(1);
public int getIndex(String string) {
Integer i = map.get(string);
if (i == null) {
final Integer newI = nextIndex.getAndIncrement();
i = map.putIfAbsent(string, newI);
if (i == null) {
reverse_map.put(newI, string);
return newI;
}
}
return i;
}
private final ConcurrentMap=new ConcurrentHashMap();
私有最终ConcurrentMap reverse_map=新ConcurrentHashMap();
私有最终AtomicInteger nextIndex=新的AtomicInteger(1);
public int getIndex(字符串){
整数i=map.get(字符串);
如果(i==null){
最终整数newI=nextIndex.getAndIncrement();
i=map.putIfAbsent(字符串,newI);
如果(i==null){
反向映射put(newI,string);
返回newI;
}
}
返回i;
}
这有一个非常良性的故障模式:一些索引将不使用
请注意,我无条件地放入了第二个映射,因为此时我知道我负责手头的字符串。最简单的事情是只标记两个方法(
getIndex
和getString
)已同步。看看你能得到什么样的加速。也许足够了
要使用ConcurrentHashMap
,您可以尝试以下方法:
private AtomicInteger nextIndex;
public int getIndex(String string) {
Integer n = map.get(string);
if (n == null) {
int idx = nextIndex.getAndIncrement();
n = map.putIfAbsent(string, idx);
if (n != null) return n;
reverse_map.put(idx, string);
return idx;
}
return n;
}
如果两个线程同时插入同一个字符串,这可能会偶尔跳过索引,但不会经常这样做。CHM也使用锁,我想说,可以使用非锁读取器双向映射。永远不需要它,它也没有足够的兴趣去尝试。我认为唯一真正需要原子化的部分是第一次插入;第二个只是重复,如果我能保证在第一个上得到正确的结果,事情应该会好起来。我的问题是nextIndex++
和map.putIfAbsent
之间的相互作用。您可能可以做到,我只是对线程编程了解不够,无法确保它正确运行。@bestsss不只是在写操作上锁定CHM吗?@mattg我明白了,我错过了那部分putIfAbsent
返回键下的上一个值,因此如果它返回一个非空值,您就知道其他线程已经在您前面处理该字符串了。在这种情况下,只需跳过第二步。我认为可能是这样,但必须再仔细考虑一下。当然,是的,新的CHM v8.x实际上更好,Cliff Click版本的并发哈希表完全是无锁的。看起来它可以工作了。我不知道AtomicInteger
中的getAndIncrement
,我担心可能会使用相同的索引引用两个不同的字符串。如果我总是getAndIncrement
,我可能会像你说的那样跳过索引,但永远不会有重复的索引,这将非常糟糕。谢谢n
几乎保证在您将其用作第二个映射中的键时为null
putIfAbsent
返回键下的上一个值,您刚刚检查该值为null
。对,如果n==null
,请使用idx
作为反向映射中的键,否则实际上使用n
@mattg(参见我的解决方案),否则不要使用任何东西--跳过该步骤。嗯,因此,您只需要insert/get,并且密钥是自生成的(即增量的?),如果是这样的话,算法甚至可以比上面的算法更快