Python列表的内存消耗:嵌套列表

5

最近我在工作中发现,一段代码运行时需要耗用大约200MB的内存,我不知道为什么它需要这么多内存。

基本上,它将一个文本文件映射到列表上,在该列表中,文件中的每个字符都是其自己的列表,其中包含字符和它已经出现了多少次(从零开始)作为它的两个项。

因此,'abbac...'将是[['a','0'],['b','0'],['b','1'],['a','1'],['c','0'],...]

对于一个长度为100万个字符的文本文件,它使用了200MB的内存。

这合理吗?还是我的代码有问题?如果是合理的,那是不是因为列表太多了?将[a,0,b,0,b,1,a,1,c,0...]采用这种方式,占用的空间会少得多吗?


3
每个字符都有多次出现记录是否很重要?在您的示例中,仅计算每个字符出现的次数,例如[a,2],[b,2],[c,1] ...是否足够? - ODiogoSilva
4
你是如何测量你的内存使用情况的? - Robᵩ
2
正如@OdiegoSilva所提到的,你是否被迫逐个案例地计算出现次数? - jester112358
2
也许可以使用一个字典,以字母为键,并根据出现次数添加值。 - jester112358
1
你可以使用元组列表而不是列表列表来节省一些空间 - 从语义上讲,你的子列表就是元组 - 但它仍然会占用相当多的内存。如果你不需要将整个列表保存在内存中,那么生成器可能是一个好选择。否则,另一个可能的数据结构是将字母映射到一个(索引,迄今出现次数)元组列表的字典,但我不确定它会节省多少空间(甚至是否会节省任何空间)。 - bruno desthuilliers
显示剩余7条评论
2个回答

2
如果您不需要列表本身,则可以完全使用生成器的解决方案,就像@Lattyware所提出的那样。
但是,如果这不是一个选项,那么也许您可以通过仅存储文件中每个字符的位置来压缩列表中的数据,而不会丢失信息。
import random
import string

def track_char(s):
    # Make sure all characters have the same case
    s = s.lower()
    d = dict((k, []) for k in set(s))
    for position, char in enumerate(s):
         d[char].append(position)
    return d

st = ''.join(random.choice(string.ascii_uppercase) for _ in range(50000))
d = track_char(st)

len(d["a"])

# Total number of occurrences of character 2
for char, vals in d.items():
    if 2 in vals:
         print("Character %s has %s occurrences" % (char,len(d[char]))
Character C has 1878 occurrences

# Number of occurrences of character 2 so far
for char, vals in d.items():
    if 2 in vals:
        print("Character %s has %s occurrences so far" % (char, len([x for x in d[char] if x <= 2))
Character C has 1 occurrences so far

这种方法可以避免在每次出现时重复复制字符字符串,而且可以保留所有出现的信息。

为了比较原始列表和这种方法的对象大小,这里进行了一个测试。

import random
import string
from sys import getsizeof

# random generation of a string with 50k characters
st = ''.join(random.choice(string.ascii_uppercase) for _ in range(50000))

# Function that returns the original list for this string
def original_track(s):
    l = []
    for position, char in enumerate(s):
        l.append([char, position])
    return l

# Testing sizes
original_list = original_track(st)
dict_format = track_char(st)

getsizeof(original_list)
406496
getsizeof(dict_format)
1632

正如您所看到的,dict_format 大约比原字符串小 250 倍。然而,在更大的字符串中,这种大小差异应该更为明显。


非常感谢。如果我说一个 '[a,0,b,0,b,1,a,1,c,0...]' 列表需要大约一半的空间,因为每个字母只出现一次,但所有列表总共仍需要 n 个项目,那么我的说法正确吗? - user2998454
1
这取决于字符串的大小。我会在帖子中更新一些测试。 - ODiogoSilva
虽然使用字典格式比列表格式更复杂,但仍然是可能的。我会进行更新。 - ODiogoSilva
只是为了确保,我的意思是到目前为止,而不是总计。列表中第n个字母右侧的数字。 - user2998454
很抱歉如果我误解了,但是包含for循环难道不意味着这个过程需要比访问list[n+1]花费更长的时间吗?列表的实现是O(C),这很重要。 - user2998454
显示剩余3条评论

1

当涉及到内存使用和列表时,减少内存使用最好的方法之一是完全避免使用列表 - Python在生成器形式的迭代器方面有很好的支持。如果你可以生成一个生成器而不是构造一个列表,你应该能够用非常少的内存使用做出这样的事情。当然,这取决于你之后对数据做什么(比如说你正在将这个结构写入文件,你可以一块一块地完成,而不是一次性存储整个东西)。

from collections import Counter

def charactersWithCounts():
    seen = Counter()
    for character in data:
        yield (character, seen[character])
        seen[character] += 1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接