3

我正在尝试计算文本文件中字符串的出现次数。文本文件看起来像这样,每个文件大约 200MB。

String1 30
String2 100
String3 23
String1 5
.....

我想将计数保存到字典中。

count  = {}
for filename in os.listdir(path):
    if(filename.endswith("idx")):
        continue
    print filename  
    f = open(os.path.join(path, filename))
    for line in f:
        (s, cnt) = line[:-1].split("\t")
        if(s not in count):
            try:
                count[s] = 0 
            except MemoryError:
                print(len(count))
                exit()
        count[s] += int(cnt)  
    f.close()
    print(len(count))

我在 时遇到内存错误count[s] = 0,但我的计算机中仍有更多可用内存。
我该如何解决这个问题?谢谢!

更新:我在这里复制了实际代码。我的python版本是2.4.3,机器运行linux,内存48G左右,但是只消耗不到5G。代码停在len(count)=44739243.

UPDATE2:字符串可以重复(不是唯一的字符串),所以我想将字符串的所有计数相加。我想要的操作只是读取每个字符串的计数。每个文件大约有 10M 行,我有 30 多个文件。我预计这个数字不到 1000 亿。

UPDATE3:操作系统是 linux 2.6.18。

4

3 回答 3

4

cPython 2.4 可能会出现大内存分配问题,即使在 x64 上也是如此:

$ python2.4 -c "'a' * (2**31-1)"
Traceback (most recent call last):
  File "<string>", line 1, in ?
MemoryError
$ python2.5 -c "'a' * (2**31-1)"
$

更新到最近的 python 解释器(如 cPython 2.7)以解决这些问题,并确保安装 64 位版本的解释器。

如果字符串的大小不平凡(即比您的示例中的 <10 字节长),您可能还想简单地存储它们的哈希值,或者甚至使用概率(但更有效)存储,如布隆过滤器。要存储它们的哈希值,请将文件处理循环替换为

import hashlib
# ...
for line in f:
    s, cnt = line[:-1].split("\t")
    idx = hashlib.md5(s).digest()
    count[idx] = count.get(idx, 0) + int(cnt)
# ...
于 2012-10-03T22:43:24.677 回答
1

如果您要做的只是计算唯一字符串的数量,则可以通过散列每个字符串来大大减少内存占用:

    (s, cnt) = line[:-1].split("\t")
    s = hash(s)
于 2012-10-03T22:43:43.790 回答
1

我不确定为什么会发生这种崩溃。您的字符串的估计平均大小是多长?4400 万个字符串,如果它们有点长,你也许应该考虑散列它们,就像已经建议的那样。缺点是,您失去了列出唯一键的选项,您只需检查字符串是否在您的数据中。

关于已经达到 5 GB 的内存限制,可能与您过时的 python 版本有关。如果您可以选择更新,请获取 2.7。相同的语法(加上一些额外的),没有问题。好吧,我什至不知道下面的代码是否仍然与2.4兼容,也许你必须再次踢掉with语句,至少你在2.7中会这样写。

您的版本的主要区别是手动运行垃圾收集。此外,您可以提高 python 使用的内存限制。正如你所提到的,它只使用实际内存的一小部分,所以如果有一些奇怪的默认设置禁止它变大,试试这个:

MEMORY_MB_MAX = 30000
import gc
import os
import resource
from collections import defaultdict
resource.setrlimit(resource.RLIMIT_AS, (MEMORY_MB_MAX * 1048576L, -1L))

count  = defaultdict(int)
for filename in os.listdir(path):
    if(filename.endswith("idx")):
        continue
    print filename  
    with open(os.path.join(path, filename)) as f:
        for line in f:
            s, cnt = line[:-1].split("\t")
            count[s] += int(cnt)  
    print(len(count))
    gc.collect()

除此之外,我不明白你的意思s, cnt = line[:-1].split("\t"),尤其是[:-1]. 如果文件看起来像您记下的那样,那么这将删除您号码的最后一位。这是故意的吗?

于 2012-10-03T22:47:38.150 回答