【问题标题】:What is the overhead of using a dictionary instead of a list?使用字典而不是列表的开销是多少?
【发布时间】:2015-09-18 02:52:44
【问题描述】:

我在我的一个项目中遇到了一种情况,我可以使用列表或字典,但我很难选择使用哪一个。

我正在分析大量项目 (>400k)。而且我将拥有(> 400k)我会经常使用的列表或字典。 (获取/设置/更新)

在我的特定情况下,如果我根本不考虑性能,使用字典感觉比列表更方便。但是,我知道我可以使用列表来编写相同的内容。

我应该追求可读性并使用字典还是使用字典可能会增加太多开销,从内存和时间的角度来看,这会大大降低我的性能。

我知道这个问题有点太宽泛了。但我想在做完这个决定后开始构建我的所有逻辑之前先问一下。

简述我的情况:

我有键值0,1,...,n。目前,键将始终是从 0n 的整数,我可以将其保存在一个列表中。

但是,我可以想到将来可能出现的一些情况,我需要为非整数键保留一些项目。或不连续的整数。

所以,问题是如果首先使用字典而不是列表不会增加太多的内存/时间成本,那么我将首先使用字典。但是,我不确定拥有 >400k 的字典与拥有 >400k 的列表在性能方面是否有很大差异。

【问题讨论】:

  • 您能描述一下您要做什么吗?通常,您会根据要如何使用对象在列表和字典之间进行选择。
  • 如果您需要订购并且主要是要遍历项目的列表。如果您需要随机访问并且主要是要获取/更新项目,请使用字典。
  • 您也可以使用 OrderedDict,它具有与字典相同的性能,因此“排序”元素在 Python 2.7 或 3.x 中并不完全正确。简而言之,如果您需要键/值对,则使用 dict 更为重要,而如果值是实际条目,则列表非常好。
  • 没关系。如果 dict 使它更具可读性,你应该这样做 - 特别是因为你知道你将需要其他键。在具有合理 RAM 量的计算机上,400k dict 不是问题。如果您做出了错误的选择,那么切换数据结构通常并不需要太多工作。
  • 这完全取决于您如何处理这些数据。只有你自己知道。

标签: python python-2.7


【解决方案1】:

直接回答您的问题:字典的开销明显高于列表:

  1. 与仅用于列表的值相比,每个项目都会为键和值消耗内存。
  2. 添加或删除项目需要查阅哈希表。

尽管 Python 字典设计得非常好并且速度惊人,但如果你有一个可以使用直接索引的算法,你将节省空间和时间。

但是,从您的问题和随后的讨论中,听起来您的需求可能会随着时间的推移而改变,并且您有一些不确定性(“但是,我可以想到将来可能出现的一些情况,我会需要为非整数的键保留一些项目")

如果是这种情况,我建议您创建自己的混合数据结构,以便随着您需求的发展,您可以在隔离的地方解决存储效率问题,同时允许您的应用程序使用简单、可读的代码来存储和检索对象。

例如,这是一个名为 maybelist 的 Python3 类,它派生自一个列表,但会检测非数字键的存在,将异常存储在字典中,同时为一些常见的列表操作提供映射:

class maybelist(list):

    def __init__(self, *args):
        super().__init__(*args)
        self._extras = dict()

    def __setitem__(self, index, val):
        try:
            super().__setitem__(index, val)
            return
        except TypeError:
            # Index is not an integer, store in dict
            self._extras[index] = val
            return
        except IndexError:
            pass
        distance = index - len(self)
        if distance > 0:
            # Put 'None' in empty slots if need be
            self.extend((None,) * distance)
        self.append(val)

    def __getitem__(self, index):
        try:
            return super().__getitem__(index)
        except TypeError:
            return self._extras[index]

    def __str__(self):
        return str([item for item in self])

    def __len__(self):
        return super().__len__() + len(self._extras)

    def __iter__(self):
        for item in itertools.chain(super().__iter__(), self._extras):
            yield item

所以,你可以把它当作一个数组,让它自动扩展:

>>> x = maybelist()
>>> x[0] = 'first'
>>> x[1] = 'second'
>>> x[10] = 'eleventh'
>>> print(x)
['first', 'second', None, None, None, None, None, None, None, None, 'eleventh']
>>> print(x[10])
eleventh

或者您可以添加带有非数字键的项目(如果它们存在):

>>> x['unexpected'] = 'something else'
>>> print(x['unexpected'])
something else

如果您使用迭代器或您选择的其他方法访问该对象,则该对象的行为似乎正常:

>>> print(x)
['first', 'second', None, None, None, None, None, None, None, None, 'eleventh', 'unexpected']
>>> print(len(x))
12

这只是一个示例,您需要定制这样一个类以满足您的应用程序的需要。例如,结果对象的行为严格来说不像列表(例如,x[len(x)-1] 不是最后一项)。但是,您的应用程序可能不需要如此严格的遵守,如果您仔细并妥善规划,您可以创建一个既提供高度优化的存储又为未来不断变化的数据结构需求留出空间的对象。

【讨论】:

  • 哇,这正是我所需要的。
【解决方案2】:

dict 使用比list 更多的内存。如果计算机不是很忙,可能不足以引起关注。当然也有例外——如果它是一个每秒有 100 个连接的 Web 服务器,您可能需要考虑以牺牲可读性为代价来节省内存

>>> L = range(400000)
>>> sys.getsizeof(L)
3200072   # ~3 Megabytes
>>> D = dict(zip(range(400000), range(400000)))
>>> sys.getsizeof(D)
25166104  # ~25 Megabytes

【讨论】:

  • 您没有考虑到提问者可能想要执行查找并从这些数据中读取这一事实
  • 谢谢。我以前不知道sys.getsizeof() 方法。这个答案在记忆方面给了我一些的想象力。
  • @DavidHeffernan,两者都有O(1) 查找
  • 是的。你没有提到那个。您也没有提到列表查找要快得多。
  • @DavidHeffernan,一旦你必须为非整数“键”添加特殊情况,就没有那么多了
【解决方案3】:

列表就是它们看起来的样子 - 一个值列表,但在字典中,您 有一个单词的“索引”,并且每个单词都有一个定义。

字典 是相同的,但字典的属性与列表不同,因为它们用于将键映射到值。这意味着您在以下情况下使用字典

  • 您必须根据某些标识符检索内容,例如姓名、地址或任何可以成为键的内容。
  • 你不需要事情井井有条。字典通常没有任何顺序的概念,因此您必须为此使用列表。
  • 您将添加和删除元素及其键。

在 Stack 帖子 Link1Link2 中讨论了效率约束。

如果您对未来的价值观有疑问,请查阅字典 也没有内存限制来打扰

Reference

【讨论】:

    【解决方案4】:

    对于您不太清楚的问题,答案并不完全正确,但这是我的想法:

    你说

    我正在分析大量项目 (>400k)

    在这种情况下,我建议您使用生成器和/或分块处理您的日期。

    更好的选择是将您的数据(即键值对)放入 Redis 并一次取出数据块。 Redis 可以非常轻松地处理您的大量数据。

    您可以编写一个一次处理一个块的脚本,并使用asyncio 模块,您可以并行化块处理。

    类似这样的:

        from concurrent import futures
    
        def chunk_processor(data):
            """
            Process your list data here
            """
            pass
    
        def parallelizer(map_func, your_data_list, n_workers=3):
            with futures.ThreadPoolExecutor(max_workers=n_workers) as executor:
                for result in executor.map(map_func, your_data_list):
                      # Do whatever with your result
    
        # Do the take out chunks of your data from Redis here
        chunk_of_list = get_next_chunk_from_redis()
    
        # Your processing starts here
        parallelizer(chunk_processor, your_data_list)
    

    同样,可以做一些更好的事情,但我向您展示了其中一种方法。

    【讨论】:

      猜你喜欢
      • 2021-07-26
      • 1970-01-01
      • 2011-01-17
      • 1970-01-01
      • 1970-01-01
      • 2011-02-27
      • 2013-01-18
      • 1970-01-01
      • 2011-11-23
      相关资源
      最近更新 更多