清空私有集合或将其设置为null?

8
我有一个可变类,其中包含一个私有的List<T>字段。在我的类的Reset()方法中,我应该使用其Clear()方法清除列表,还是只需分配一个新列表给它的字段?请注意,该列表不是公共的,只由类本身使用。因此,分配一个新列表应该使旧列表无法访问。由于Clear()方法 是O(n)操作,我想知道仅分配新列表的缺点是什么。
3个回答

7
我能想到的唯一缺点是,如果您需要再次使用列表,则必须为其分配新空间。 将其置空只会使该列表及其内容(假设没有其他引用)符合GC的条件。清空它将删除项目但保留已分配的内存。 个人而言,我倾向于将其置空,即使我需要它再次使用,其大小也会完全改变。 更新:针对下面的评论,您指出这些对象将在对象池中管理。我建议创建一个小型的性能分析控制台应用程序来获取最终答案。讨论现在正在进入您的实施细节和对象池的预期使用范围,这可能很容易改变答案。 一般而言,如果您有不太改变长度并且总是需要的列表,则我会使用“ Clear”以避免为列表分配新内存。如果列表长度容易发生变化,或者使用有时稀疏 - 我会更喜欢将其置空,以便回收内存并通过惰性实例化列表获得一些微小的好处。

如果我有一个包含2k字节的字节列表,哪种方法更快?是调用Clear或GC,还是重新分配内存?我问这个问题是因为我有大约1-2k个对象,这些对象内部都有这些列表,所以我想确定哪种方法在性能方面更好。 - Şafak Gür
个人而言,我不会担心性能问题,直到你证明它是一个问题。这取决于你对列表想要做什么。如果项已不再需要,但可能需要列表大小或者想要避免为列表增长重新分配内存,则使用 Clear 方法。如果之后对列表不再关心或者它可能会大量缩小,则使用 list = null; list = new List<T>()。在任何一种情况下,垃圾回收器都会收集 x 个对象,所以这并不重要。任何潜在的性能差异都在 O(n) 迭代与列表增长重新分配之间的差异。 - Adam Houldsworth
所以,再问一个问题:为什么我要“避免为列表增长重新分配内存”?我为这些对象使用池,只要应用程序在运行,它们就会被反复重用。每次都进行重新分配听起来很昂贵,但我不太了解垃圾回收以及这个过程对性能的影响有多大。 - Şafak Gür
如果您正在池化预定大小的 List<T>,那么我会更倾向于使用 Clear,因为它更能表达意图。在池化中,您希望项目长时间存在,因为它返回到池中。将列表置空意味着 GC 必须回收先前列表的内存,然后获取下一个块以供新列表使用。我所说的分配是指除非您指定列表容量,否则它会在需要时自动增长。既然您想管理池,我建议根据您的池需求自己进行清空与 Clear 的性能分析。Stopwatch 在这里很有帮助。 - Adam Houldsworth
使用Clear的优点是它保持了列表的容量并避免了重新分配,而重新创建它的优点是这种方法提供了懒惰使用的好处。因此,如果我不需要该列表(这可能会发生,因为该列表是我正在池化的类的私有成员,我不直接池化列表),则永远不会创建它。在我接受之前,您能否通过合并您之前评论的这些内容来更新您的帖子? - Şafak Gür
这样做可以使你的回答更通用(关于实际问题),而不是关于我的具体实现细节(我在评论中提出的问题)。 - Şafak Gür

0
那么为什么要将其置空呢?这样做可以让旧列表保留在堆上等待垃圾回收,同时现有访问列表的方法可以继续在一个新的空列表上运行,从而达到你想要的效果。
this.FooList = new List<Foo>();

1
我也写了“将其字段分配为新列表”。我使用“nullifying”的原因是因为它使该字段无法访问,并允许GC对其进行收集。但你说得对,我应该编辑问题。无论如何,问题是哪种方法更快,以及它们相互之间的优缺点是什么。 - Şafak Gür
将其置空或重新赋值在GC的眼中是一回事。如果您将其置空并稍后重新赋值,则可以获得延迟实例化的轻微好处。 - Adam Houldsworth

0
在调用 Reset 后,我会让对象保持与构造函数调用后相同的状态。
如果您的构造函数创建了一个新的空 List,那就这样做。否则将其设置为 null

这是我试图决定的事情。在构造函数中设置列表一次,并通过清除它来反复使用,或者根据需要懒惰地创建它,并在重置时将其设置为null(或新列表)。 - Şafak Gür
在我看来,我认为性能不应该成为这个决定的驱动因素。我认为将其设为null并进行惰性加载是最清晰的,我会选择这种方式。 - ForkandBeard

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