插入哈希表似乎存在问题。我创建了大约8个线程,并在每个线程中我执行以下代码。每个线程都接收一个char []数组。每个线程的工作是标记化此数组(查找空格)。一旦找到令牌,我需要将它添加到哈希表中(如果它不存在)。如果确实存在,那么我需要将1添加到该令牌的当前值(密钥)。
您可能会问的问题:
为什么不从char []转换为String?
我试过这个,因为字符串是不可变的,我最终耗尽了内存(我处理的是10g文件),或者我花了太长时间收集垃圾。使用Character [],我可以重用相同的变量,而不占用内存中的额外空间。
有什么问题?
当我完成整个文件的处理后,我运行代码:
for (Entry<Character [], Integer> e : wordCountMap.entrySet()) {
System.out.println(Arrays.toString(e.getKey()) + " = " + e.getValue());
}
在我的主要功能中。我得到的结果是少于100个键/值对。我知道应该有大约20,000。似乎有些重叠。
Character [] charArray = new Character[8];
for (i = 0; i < newbyte.length; i++) { //newbyte is a char[] from main
if (newbyte[i] != ' ') {
charArray[counter] = newbyte[i];
counter++;
}
else {
check = wordCountMap.putIfAbsent(charArray, 1);
if (check != null) {
wordCountMap.put(charArray, wordCountMap.get(charArray) + 1);
}
for (j = 0; j < counter; j++) {
charArray[j] = null;
}//Null out the array
ConcurrentMap<Character [], Integer> wordCountMap //this is the definition in main
正如下面的一些评论所暗示的那样,我实际上是在将行引用传递给charArray时:
wordCountMap.put(charArray, wordCountMap.get(charArray) + 1);
已执行。所以我的问题是,如何传递该值?它现在实际上非常有意义,因为最终有大约320个键/值对--8个线程,40个循环(每个线程获得250个/每次迭代8 MB)。
答案 0 :(得分:1)
如果不同步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
}
这将解决您的内存问题,因为只有在必须插入表格时才执行 new()(通过 copy())。因此,使用的内存是您需要的最小值(不包括i,j,检查器等)。但是,你几乎失去了所有并行性。
如果我是你,我会将文件分解为多个片段,并在一个单独的线程中处理每个片段。每个线程都可以维护自己的hashmap。在整个文件的末尾,您将有n个哈希表(n是线程数)。然后,您可以合并n hashmap。所需的内存将是 n 乘以前一个hashmap的大小。
如果您想了解更多有关此方法的详细信息,请与我联系。我将尽力提供帮助。
答案 1 :(得分:0)
当您使用数组作为键时,它是对数组本身的引用,而不是内容。因此,更改内容不会在地图中创建更多条目,它只是继续更新相同的值。考虑一下简单的程序:
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));
}
输出为true
,因为charArray仍然是相同的数组,其内容未被考虑。
如果您希望以后能够在最后获取内容,例如:
for (Entry<Character [], Integer> e : wordCountMap.entrySet()) {
System.out.println(Arrays.toString(e.getKey()) + " = " + e.getValue());
}
你必须把它放在某个地方!如果内存太大,则需要分配更多内存或使用某种外部存储。也许键入字符串的MD5上的地图,并在MD5->原始字符串的磁盘上保留一个NoSQL数据库,以便以后可以将它们取回?
在您的代码中,您在去的时候删除了数据,但预计它最终仍会存在!
答案 2 :(得分:0)
我认为不需要使用Character[]
作为映射键,而是需要定义自己的类来表示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);
其中new CharArray(charArray)
制作现有数组的副本 - 这就是&#34;复制构造函数&#34;的含义。 (我假设CharArray
是您给新课程的名称。)这是错误的,因为在您不需要新对象的情况下,您将创建新对象,你试图避免。所以可能像
Integer existing = wordCountMap.get(charArray);
if (existing == null) {
wordCountMap.put(new CharArray(charArray), 1);
} else {
wordCountMap.put(charArray, existing + 1);
}
这应该只在需要时创建一个新的CharArray
,并且它不会在地图中添加您计划不断更改的CharArray
的引用。您可能需要在上面添加一些锁定以防止竞争条件。
(*)再次查看您的帖子后,我不确定8个字符的数组是否是您真正想要的,但您确实在代码中说new Character[8]
。但是,该技术应适用于任何缓冲区大小。您可以设置您的类,以便可变的实例具有更大的缓冲区,并且您在哈希映射中放置的实例仅保留所需的字符数。
答案 3 :(得分:0)
至少有三个问题:你修改了地图的键,数组的hashCode是基于引用的,并且这里有竞争条件:
check = wordCountMap.putIfAbsent(charArray, 1);
if (check != null) {
wordCountMap.put(charArray, wordCountMap.get(charArray) + 1);
}
其他答案解决了哈希问题,因此我将解决竞争状况。 putIfAbsent是原子的,但put(increment(get()))不是。您可以使用AtomicInteger而不是普通的Integer来解决此问题:
AtomicInteger check = wordCountMap.putIfAbsent(key.copy(), new AtomicInteger(1));
if (check != null) {
check.incrementAndGet();
}
这里有密钥和值的分配,但如果密钥已经存在,它们将很容易被垃圾收集。如果你想避免它们,你可能会产生额外的get()开销,或者你可以使用@ Chip的答案中的其他建议之一。