Java 使用putIfAbsent重叠ConcurrentHashMap放置

Java 使用putIfAbsent重叠ConcurrentHashMap放置,java,multithreading,hashmap,wrapper,concurrenthashmap,Java,Multithreading,Hashmap,Wrapper,Concurrenthashmap,在哈希表中插入数据似乎有问题。我创建了大约8个线程,在每个线程中我执行以下代码。每个线程接收一个char[]数组。每个线程的任务是标记这个数组(查找空格)。一旦找到令牌,如果它不存在,我需要将其添加到哈希表中。如果它确实存在,那么我需要将1添加到该令牌(密钥)的当前值 您可能会问的问题: 为什么不将字符[]转换为字符串? 我尝试过这个,由于字符串是不可变的,我最终耗尽了内存(我正在处理一个10g文件),或者我花了太长时间收集垃圾。使用字符[],我可以重用相同的变量,而不会占用内存中的额外空间 什

在哈希表中插入数据似乎有问题。我创建了大约8个线程,在每个线程中我执行以下代码。每个线程接收一个char[]数组。每个线程的任务是标记这个数组(查找空格)。一旦找到令牌,如果它不存在,我需要将其添加到哈希表中。如果它确实存在,那么我需要将1添加到该令牌(密钥)的当前值

您可能会问的问题:

为什么不将字符[]转换为字符串?

我尝试过这个,由于字符串是不可变的,我最终耗尽了内存(我正在处理一个10g文件),或者我花了太长时间收集垃圾。使用字符[],我可以重用相同的变量,而不会占用内存中的额外空间

什么问题?

处理完整个文件后,我运行以下代码:

for (Entry<Character [], Integer> e : wordCountMap.entrySet()) {
    System.out.println(Arrays.toString(e.getKey()) + " = " + e.getValue());
}

被执行。因此,我的问题是,如何传递值?现在它实际上非常合理,因为最终大约有320个键/值对-8个线程,40个循环(每个线程每次迭代获得250/8 MB)。

当使用数组作为键时,使用的是数组本身的引用,而不是内容。因此,更改内容不会在地图中创建更多条目,只会不断更新相同的值。考虑简单的程序:

public static void main(String[] args) throws Exception {
    Character[] charArray = new Character[8];
    charArray[1] = 'A';
    Set<Character[]> set = new HashSet<Character[]>();
    set.add(charArray);
    charArray[1] = 'B';
    System.out.println(set.contains(charArray));
}
你必须把它放在某个地方!如果内存太大,则需要分配更多内存或使用某种外部存储。也许在MD5上键入字符串的映射,并在MD5->原始字符串的磁盘上保留一个NoSQL数据库,以便您可以稍后将其取回?

在您的代码中,您在运行时删除了数据,但希望它最终仍然存在

我认为,您需要定义自己的类来表示8字符数组(*),而不是使用
字符[]
作为映射键。您需要在该类中重新定义
equals()
hashCode()
;定义
equals()
以便在所有8个字符都相同时返回
true
,并将
hashCode()
定义为依赖于这8个字符的某个值。不能为数组重新定义
equals()
hashCode()
;这就是为什么您需要定义自己的类。该类将在内部使用
char[]
Character[]

该类还应具有某种类型的
copy
clone
方法,或复制构造函数,以便您可以创建数据(8个字符)与现有对象相同的新对象

现在,不是这样:

check = wordCountMap.putIfAbsent(charArray, 1);
if (check != null) { 
    wordCountMap.put(charArray, wordCountMap.get(charArray) + 1);
}
将新密钥放入地图时,您需要确保使用副本。如上所述,使用
putIfAbsent
将在映射中放置对局部变量的引用,这是错误的,因为局部变量可能会更改。这也是错误的:

check = wordCountMap.putIfAbsent(new CharArray(charArray), 1);
其中,
newchararray(CharArray)
复制现有数组——这就是我所说的“复制构造函数”。(我假设
CharArray
是您为新类指定的名称。)这是错误的,因为在不需要新对象的情况下,您将创建新对象,这是您试图避免的。所以可能是

Integer existing = wordCountMap.get(charArray);
if (existing == null) {
    wordCountMap.put(new CharArray(charArray), 1);
} else {
    wordCountMap.put(charArray, existing + 1);
}
这应该只在需要时创建一个新的
CharArray
,并且它不会在地图中放置对您计划不断更改的
CharArray
的引用。您可能需要在上面添加一些锁定,以防止出现竞争条件


(*)在再次查看您的帖子后,我不确定8字符数组是否是您真正想要的,但您确实在代码中说了
新字符[8]
。不过,这种技术应该适用于任何缓冲区大小。您可以设置类,使可变的实例具有更大的缓冲区,并且您放入哈希映射中的实例只保留所需的字符数。

如果不同步get()和put()操作,我认为这是不可能实现的

按照

检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠。检索反映了最近完成的更新操作在开始时的结果

这意味着如果两个线程同时遇到相同的计数器,get()将返回相同的值(比如2),并且两个线程都将插入2+1=3。因此,令牌的数量将被低估-即3而不是4

为了保持一致,您需要在执行get()操作之前进行同步,这将大大降低多线程的好处

如果你想这样做,你可以这样做:

class Key {
   char[] buffer = new char[8];
   Key copy() {
       Key copy = new Key();
       for ( int i =0; i < 8; i++) {
          copy.buffer[i] = this.buffer[i];        
       }
   }
   public int hashCode() {
      return Arrays.hashCode(buffer);
   }
   public boolean equals(Object obj) {
      if ( obj instanceof Key) {
        return Arrays.equals(((Key) obj).buffer, this.buffer); 
      }
      return false;
   }
}
//YOur code modified:
Key checker = new Key();
for (i = 0; i < newbyte.length; i++) { //newbyte is a char[] from main
    if (newbyte[i] != ' ') {
        checker.buffer[counter] = newbyte[i];
        counter++;
    }
    else { 
            synchronized (wordCountMap) {
               Integer value = workCountMap.get(checker);
               if ( value == null ) {
                  workCountMap.put(checker.copy(), 1);    
               } else {
                  wordCountMap.put(checker.copy(), value + 1);
               }
            }
        for (j = 0; j < counter; j++) {
            checker.buffer[j] = null;
        }//Null out the array
   }
类密钥{
char[]buffer=新字符[8];
密钥副本(){
密钥副本=新密钥();
对于(int i=0;i<8;i++){
copy.buffer[i]=这个.buffer[i];
}
}
公共int hashCode(){
返回数组。哈希代码(缓冲区);
}
公共布尔等于(对象obj){
if(obj键实例){
返回array.equals(((Key)obj.buffer,this.buffer);
}
返回false;
}
}
//您的代码已修改:
密钥检查器=新密钥();
对于(i=0;i
这将解决您的内存问题,因为您需要执行new()
check = wordCountMap.putIfAbsent(new CharArray(charArray), 1);
Integer existing = wordCountMap.get(charArray);
if (existing == null) {
    wordCountMap.put(new CharArray(charArray), 1);
} else {
    wordCountMap.put(charArray, existing + 1);
}
class Key {
   char[] buffer = new char[8];
   Key copy() {
       Key copy = new Key();
       for ( int i =0; i < 8; i++) {
          copy.buffer[i] = this.buffer[i];        
       }
   }
   public int hashCode() {
      return Arrays.hashCode(buffer);
   }
   public boolean equals(Object obj) {
      if ( obj instanceof Key) {
        return Arrays.equals(((Key) obj).buffer, this.buffer); 
      }
      return false;
   }
}
//YOur code modified:
Key checker = new Key();
for (i = 0; i < newbyte.length; i++) { //newbyte is a char[] from main
    if (newbyte[i] != ' ') {
        checker.buffer[counter] = newbyte[i];
        counter++;
    }
    else { 
            synchronized (wordCountMap) {
               Integer value = workCountMap.get(checker);
               if ( value == null ) {
                  workCountMap.put(checker.copy(), 1);    
               } else {
                  wordCountMap.put(checker.copy(), value + 1);
               }
            }
        for (j = 0; j < counter; j++) {
            checker.buffer[j] = null;
        }//Null out the array
   }
check = wordCountMap.putIfAbsent(charArray, 1);
if (check != null) { 
    wordCountMap.put(charArray, wordCountMap.get(charArray) + 1);
}
AtomicInteger check = wordCountMap.putIfAbsent(key.copy(), new AtomicInteger(1));
if (check != null) { 
    check.incrementAndGet();
}