循环访问列表并从中删除项目的最佳方式
本文关键字:删除项目 最佳 方式 访问 列表 循环 | 更新日期: 2024-09-25 11:02:57
我需要遍历一个List<myObject>
并删除满足特定条件的项目。
我看到了这个答案(https://stackoverflow.com/a/1582317/5077434(:
使用 for 循环反向迭代列表:
for (int i = safePendingList.Count - 1; i >= 0; i--) { // some code // safePendingList.RemoveAt(i); }
例:
var list = new List<int>(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); } list.ForEach(i => Console.WriteLine(i));
但我明白for
的效率不如foreach
,
所以我想使用后者如下:
foreach (var item in myList.ToList())
{
// if certain condition applies:
myList.Remove(item)
}
一种方法比另一种更好吗?
编辑:
我不想使用 RemoveAll(...)
,因为在条件之前,循环内有大量代码。
无论如何,你必须遍历列表,而for
循环是最有效的循环:
for (int i = safePendingList.Count - 1; i >= 0; --i)
if (condition)
safePendingList.RemoveAt(i);
如果要在范围内(而不是整个列表中(中删除,只需修改 for 循环:
// No Enumarable.Range(1, 10) - put them into "for"
for (int i = Math.Min(11, safePendingList.Count - 1); i >= 1; --i)
if (condition)
safePendingList.RemoveAt(i);
或者,如果必须在正向循环中删除项目:
for (int i = 0; i < safePendingList.Count;) // notice ++i abscence
if (condition)
safePendingList.RemoveAt(i);
else
i += 1; // ++i should be here
相反,safePendingList.ToList()
创建初始safePendingList
的副本,这意味着内存和 CPU开销:
// safePendingList.ToList() - CPU and Memory overhead (copying)
foreach (var item in safePendingList.ToList()) {
if (condition)
myList.Remove(item); // Overhead: searching
}
但是,在许多情况下,最合理的计划只是让 .Net 为您工作:
safePendingList.RemoveAll(item => condition);
删除具有特定条件的项目,您可以使用
list.RemoveAll(item => item > 5);
而不是
var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i] > 5)
list.RemoveAt(i);
}
这两种方法都没有显示出显着差异。 for
和RemoveAll
看起来更快一些(更有可能是因为使用Remove
您正在复制列表并且需要重新创建它......使用值类型,这也意味着将所需的内存加倍(。我做了一些分析:
public static void MethodA()
{
var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i] > 5)
list.RemoveAt(i);
}
}
public static void MethodB()
{
var list = new List<int>(Enumerable.Range(1, 10));
foreach (var item in list.ToList())
{
if (item > 5)
list.Remove(item);
}
}
public static void MethodC()
{
var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(x => x > 5);
}
public static void Measure(string meas, Action act)
{
GC.Collect();
GC.WaitForPendingFinalizers();
var st = new Stopwatch();
st.Start();
for (var i = 0; i < 10000000; i++)
act();
st.Stop();
Console.WriteLine($"{meas}: {st.Elapsed}");
}
static void Main(string[] args)
{
Measure("A", MethodA);
Measure("B", MethodB);
Measure("C", MethodC);
}
结果:
答:00:00:04.1179163
B: 00:00:07.7417853
C: 00:00:04.3525255
其他运行给出的结果更相似(请注意,这是对 10 个项目列表的 1000 万次迭代 [因此执行 1 亿次操作]......~3 秒的差异不是我所说的"显着",但您的里程可能会有所不同(
所以选择你认为你可以更好地阅读的东西(我个人会使用RemoveAll
方法(
注意:随着列表的增长(只有 10 个项目(,MethodB
(复制列表foreach
项目(会变慢,差异会更大,因为列表的复制成本会更高。例如,列出 1,000 个项目(条件为 500 而不是 5(和 100,000 次迭代,使MethodB
为 34 秒,而其他两个项目在我的机器上运行的时间都不到 2 秒。所以是的,绝对不要使用MethodB
:-(
这并不意味着foreach
效率低于for
,它只意味着要使用foreach
从列表中删除项目,您需要复制所述列表,这是昂贵的。
另一种方法(使用 foreach
(应该与for
或RemoveAll
方法一样快(在我的分析中它有点慢 - 因为它分配了一个新列表,我认为 - 但几乎可以忽略不计(:
public static void MethodD()
{
var originalList = new List<int>(Enumerable.Range(1, 1000));
var list = new List<int>(originalList.Count);
foreach (var item in originalList)
{
if (item <= 500)
list.Add(item);
}
list.Capacity = list.Count;
}
你只需要扭转条件。
我不是在建议这种方法,只是证明foreach
不一定比for
慢
问题是foreach
在修改时无法使用集合,这将导致InvalidOperationException
。
在 foreach
中使用 .ToList()
可以避免这种情况,但这会复制整个集合,导致循环访问集合两次(第一次复制所有元素时,第二次过滤它(。
for
循环是更好的选择,因为我们可以进行一次收集。除此之外,您还可以使用LINQ
Where()
方法过滤集合,其中包括以下行:
var myFilteredCollection = myList.Where(i => i <= 5).ToList();
如果你的条件很复杂,你可以把这个逻辑移到单独的方法:
private bool IsItemOk(myObject item)
{
... your complicated logic.
return true;
}
并在 LINQ 查询中使用它:
var myFilteredCollection = myList.Where(i => IsItemOk(i)).ToList();
另一种选择是反之亦然。创建新集合并根据条件添加新元素。比你可以使用foreach
循环:
var newList = new List<MyObject>(myList.Count);
foreach (var item in myList)
{
// if certain condition does not apply:
newList.Add(item);
}
附言
但如前所述.RemoveAll()
更好,因为这样您就不必"重新发明轮子">
另一种方法是对列表进行搜索,将项目设置为 null,然后执行 linq 删除到(项目 => null(,如前所述,由您选择更适合您的方式