C# ListView.Items[i].remove非常缓慢。

6

这是我第一次来到这里,我正在努力解决这个问题。 我有这段代码:

try
{
    progressBar1.Maximum = lista.Items.Count;
    lista.BeginUpdate();

    for (int i = 0; lista.Items.Count > i; i++)

    //for (int i = lista.Items.Count - 1; -1 < i; i--)
    {
        if (lista.Items[i].SubItems[1].Text.ToLower().Contains(Text) == false)
        {                        
            lista.Items[i].Remove();                        
        }

        progressBar1.Value = progressBar1.Value + 1;
    }

    lista.EndUpdate();

    progressBar1.Value = 0;
}
catch (Exception errore)
{
    txt_info.Text = "" + errore.Message;
    progressBar1.Value = 0;
}

lista.items[i].remove方法非常慢。 lista是一个ListView,我正在处理一个超过50,000行的日志文件。 有没有什么方法可以加速这个过程?


3
“奇怪……lista.Items.RemoveAt(i)的速度有区别吗?也许(出人意料地),这个类必须自己返回去解决索引。” - DonBoitnott
4
在For循环内更改数据结构的大小(删除其中的项)是不应该的。尝试在不包含删除操作的循环中重新编写代码。例如,在For循环中标记需要删除的索引,然后在循环之外将它们删除。 - Reza Shirazian
2
@RezaShirazian 显然不能使用foreach来做到这一点...我认为使用for删除项目是合理的(最终某些代码必须这样做)- 但显然编写不正确(如上面示例中跳过刚刚删除的项旁边的项)是个坏主意。 - Alexei Levenkov
DonBoitnott是正确的。RemoveAt(i)就是答案-已验证。是的,循环中的逻辑没有意义。 - Yogee
6个回答

3
我会采取不同的方法,使用LINQ,像这样:

我会采取不同的方法,使用LINQ,像这样:

lista.Items = lista.Items.Where(x=>x.SubItems[1].Text.ToLower.Contains(Text)).AsParallel().ToList();

基本上,重新构建列表而不是一次又一次地尝试删除单个项目。

1
这是一个很好且紧凑的实现,虽然 AsParallel 让我感觉有些过度。 - Reacher Gilt
ToLower和Contains都是耗费CPU的操作,如果列表很大,则并行处理会有好处。 - David C
2
ListView.Items 是只读属性,因此这样做不起作用。 - Michael Liu
1
你应该将 .ToLower().Contains(Text) 替换为 .IndexOf(Text, StringComparison.CurrentCultureIgnoreCase) != -1,这样可以避免创建新的字符串进行比较。 - Richard Deeming
1
@MichaelLiu 哇,好主意,我甚至没有检查它是否有一个 setter。 - David C

2
ListViewItem[] allElements = new ListViewItem[listView1.Items.Count];
listView1.Items.CopyTo(allElements, 0);
List < ListViewItem > list = allElements.ToList();
list.RemoveAll(item => item.SubItems[1].Text.ToLower().Contains(TextToFind) == false);
listView1.BeginUpdate();
listView1.Clear();
listView1.Items.AddRange(list.ToArray());
listView1.EndUpdate();

第一条规则是不要在for循环中更新列表。你的逻辑只会运行到列表的一半。我猜这不是你想要的。
我发现即使使用了BeginUpdate和EndUpdate,操纵listview.items仍然非常缓慢。关键是在外部(列表或其他地方)进行操作,然后使用AddRange重新填充列表(比Add快得多)。


我已经尝试了上面的代码,但是返回给我一个空列表。我错过了什么吗?在您的评论中,您说您正在使用foreach循环,但我没有在您留下的代码中看到它。 - Jarlaxle2k5
@user2441083:我之前的评论使用了foreach,然后我从列表中删除了元素。但是后来我使用了RemoveAll,它不需要foreach。顺便说一下,列表什么时候变为空?在RemoveAll之后吗?如果是这样,你需要检查“RemoveAll”中写的条件是否可能导致这种情况。 - Yogee
@user2441083:我在我的应用程序中写了相同的逻辑,它运行良好且速度足够快。对于包含50,000个二维字符串作为listView1.Items中的ListViewItems的列表,删除10%的项目大约需要2450毫秒的时间。你确定要检查item.SubItems[1]而不是item.SubItems[0]吗? - Yogee
我进行了一些调试,问题实际上出现在 lista.clear() 之后。 看起来 AddRange 没有将列表填充回来。 - Jarlaxle2k5
1
它完美地运行了,我只是在使用AddRange之前重新添加了列lista.Columns.Add("Description")。 非常感谢您的支持! - Jarlaxle2k5
显示剩余2条评论

2
最简单的选择是使用列表自身的RemoveAll方法list.RemoveAll(x => !x.SubItems[1].Text.ToLower().Contains(Text)) 附注:
您可能希望在实际比较中寻找速度增益。如果符合要求,使用String.Compare会更快。如果您想检查子字符串,我建议使用ToUpperInvariant来处理不变性相关问题-它被设计得更快

1
ListViewItemCollection 没有 RemoveAll 方法,因此这种方法不起作用。 - Michael Liu
@Yogee,我建议直接比较时过滤几倍更快——而不是替代“Contains”。如果他想使用“Contains”,那么使用“ToUpperInvariant”更快。 - Asti
@Yogee SO也可以是一个提供一般建议的地方,而不仅仅是“给我代码”。尽管如此,我已经编辑过它以消除歧义。 - Asti
感谢Asti编辑答案。我将删除我的过时评论。顺便说一下,我在这个线程中发布的代码相当快。我不确定它是否会对性能产生任何影响,因为我发现我的list.RemoveAll对于50Krecords非常快。在这种情况下的瓶颈是直接操作(如编辑/删除)listview项目。我注意到,如果您清除并添加元素(特别是使用AddRange),速度会更快。 - Yogee

0
你可以将它放在后台工作线程中,让它自己完成这个过程。因此,在此过程发生时,您的用户仍然可以使用程序。

0
如果你在for循环中删除一个项目,你应该将计数器减1,这样就不会漏掉一个。因为你删除了[i],[旧i+1]变成了[新i](Items.Count也减少了1),你会错过检查[新i]的机会。

e.g: ListView.Items.Remove[i]; i--;


0

动态访问列表速度较慢,更适合迭代。我建议在列表视图之外操作数据(也许是通过将要显示的行逐个复制到临时列表中,同时遍历源集合),然后在最后分配给列表视图。这可以在不同的线程上完成(以提高UI性能),而且不需要昂贵的查找。


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