值类型在(C#)通用集合中存储在哪里?

13

事实上,泛型集合在处理值类型时比非泛型集合表现更优。例如 List 相比 ArrayList。

但是除了避免装箱操作以外,为什么会出现这种情况呢?一旦将值类型对象添加到集合中,它们存储在哪里?在非泛型集合中,它们会被装箱并存储在堆上,那么泛型集合有何不同之处呢?


对于相同数量的整数,sizeof(List<int>) ~= sizeof(int[]) ~= 1/2 sizeof(ArrayList on x86) ~= 1/3 sizeof(ArrayList on x86-64)。此外,由于装箱,ArrayList的数据局部性很差,并且会产生比必要更高的内存碎片化。 - Julien Roncaglia
5个回答

13
在泛型中,例如List<T>,它们仍然存储在堆上。区别在于,内部List<int> 会创建一个整数数组,并直接存储数字。而使用ArrayList,则会存储对装箱整数值的引用数组。

8
相关的实现细节是,List<T> 的底层存储是一个 T[] 数组。因此,对于 List<int> 来说,这些值将存储在 int[] 数组中。整数存储在从垃圾收集堆分配的连续内存块中。
它之所以如此快,不仅仅是因为整数没有装箱,而且是因为 int[] 与 CPU 缓存非常相容。当您读取第一个元素时,您基本上可以免费获得接下来的 15 个元素,而无需读取缓慢的 RAM 或二级缓存。对于装箱的 int,这种效果并不好,因为它太大了,额外的引用可能具有较差的缓存局部性。然而,垃圾回收器通过压缩堆帮助减轻了这种成本。

啊,唯一明确表示List<T>实际上使用数组作为后备存储的答案(而且说得很好)。 List<T>只是一个包装器,用于添加支持调整大小的数组,实际上几乎所有集合类型都归结为数组,除了特殊集合,如LinkedList<T> - Allon Guralnek
因此,ArrayList 的性能会受到双重打击。首先,您必须取消引用数组中的值以获取对象(装箱值类型),然后您必须对其进行取消装箱。 - Jim Mischel
1
但是,如果ArrayList的后备存储为object[],那么首先您必须在数组中查找该项,这将为您提供包含int的对象的引用。您必须取消引用以获取对象,然后必须对其进行拆箱。与此相比,List只需从int[]后备存储中获取项目即可。 - Jim Mischel
@Jim,我认为你是对的,已经修改帖子以指出解引用的额外成本。 - Hans Passant

1

ArrayList是一个本地对象引用数组,存储在堆中的对象的引用。

泛型引用类型列表是一个本地对象引用数组,存储在堆中的对象的引用。

值类型的泛型列表是这些值类型的本地数组。

内存中有两个区域,大多数引用称之为“堆栈”和“堆”。使用这些术语的大多数人都不知道原因。 (“堆栈”可能是一个堆栈,但“堆”几乎肯定不是一个堆)。我更喜欢使用“这里”和“那里”的术语。当装箱时,值类型数据存储在“那边”。当存储在数组中(也许在泛型列表内部),值类型数据存储在“这里”。 “这里”更好一些。


“这边” - “那边”???什么意思???还有什么是“本地数组”?这个答案比什么都更加令人困惑。 - Dirk Vollmar
@0xA3:本地数组是存储在“堆栈”中的数组。这样表述更加清晰明了吗? - James Curran
2
好的,那么如果你的意思是常用的栈,那么你的说法是不正确的。ArrayListList<T>都使用System.Array作为内部存储,它是一个引用类型并且被存储在“那边”。 - Dirk Vollmar
哦,我认为答案很清楚了。我认为最后一条评论恰好说明了为什么使用术语“堆”和“栈”会让人感到困惑。确实,数组将在“堆”上分配,但真正的问题是在哪里分配内存以存储数组的每个元素?在List<T>中,用于存储值类型的内存位于System.Array分配的内存中(即“这里”)。在ArrayList中,每个元素只是对装箱值类型的引用,因此存储每个值类型的实际内存位于“堆”上的其他位置,即“那边”。 - Dr. Wily's Apprentice

0

0

泛型的性能提升通常只针对与非泛型等效物中存储的值类型相比使用泛型的值类型。

这是因为使用泛型时,值类型不需要转换为对象并存储在堆上(装箱)。实际上,它们可以保留在堆栈上,这样更具有性能优势。

http://msdn.microsoft.com/en-us/library/ms172181.aspx


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