在字符串集合中搜索的最快方法

Posted

技术标签:

【中文标题】在字符串集合中搜索的最快方法【英文标题】:Fastest way to search in a string collection 【发布时间】:2014-02-18 04:43:28 【问题描述】:

问题:

我有一个包含大约 120,000 个用户(字符串)的文本文件,我想将其存储在一个集合中,然后再对该集合执行搜索。

每次用户更改TextBox 的文本时都会出现该搜索方法,结果应该是包含TextBox 中的文本的字符串。

我不必更改列表,只需将结果拉出并放入ListBox

到目前为止我所做的尝试:

我尝试了两个不同的集合/容器,我正在从外部文本文件中转储字符串条目(当然是一次):

    List<string> allUsers; HashSet<string> allUsers;

使用以下LINQ 查询:

allUsers.Where(item => item.Contains(textBox_search.Text)).ToList();

我的搜索事件(当用户更改搜索文本时触发):

private void textBox_search_TextChanged(object sender, EventArgs e)

    if (textBox_search.Text.Length > 2)
    
        listBox_choices.DataSource = allUsers.Where(item => item.Contains(textBox_search.Text)).ToList();
    
    else
    
        listBox_choices.DataSource = null;
    

结果:

两者都给了我很短的响应时间(每次按键之间大约 1-3 秒)。

问题:

你认为我的瓶颈在哪里?我用过的合集?搜索方法?两者都有?

如何才能获得更好的性能和更流畅的功能?

【问题讨论】:

HashSet<T> 在这里对您没有帮助,因为您正在搜索字符串的 部分 查看Suffix arrays。 不要问“最快的方法是什么”,因为这实际上需要数周到数年的研究。而是说“我需要一个运行时间少于 30 毫秒的解决方案”,或者无论您的性能目标是什么。您不需要最快设备,您需要足够快设备。 另外,获取分析器。不要猜测慢的部分在哪里;这样的猜测往往是错误的。瓶颈可能出人意料。 @Basilevs:我曾经写过一个可爱的 O(1) 哈希表,在实践中非常慢。我对其进行了分析以找出原因,并发现在每次搜索时它都会调用一种方法——不开玩笑——最终会询问注册表“我们现在在泰国吗?”。 不缓存用户是否在泰国是该 O(1) 代码的瓶颈。 瓶颈的位置可能非常违反直觉。使用分析器。 【参考方案1】:

更新:

我做了一些分析。

(更新 3)

列表内容:从 0 到 2.499.999 生成的数字 过滤文本:123(20.477 个结果) 酷睿 i5-2500,Win7 64 位,8GB 内存 VS2012 + JetBrains dotTrace

2.500.000 条记录的初始测试运行花了我 20.000 毫秒。

第一大罪魁祸首是在Contains 中调用textBox_search.Text。这会调用文本框的昂贵的 get_WindowText 方法来调用每个元素。只需将代码更改为:

    var text = textBox_search.Text;
    listBox_choices.DataSource = allUsers.Where(item => item.Contains(text)).ToList();

将执行时间减少到 1.858 毫秒

更新 2:

另外两个重要的瓶颈现在是对string.Contains 的调用(大约45% 的执行时间)和set_Datasource 中列表框元素的更新(30%)。

我们可以通过创建后缀树来在速度和内存使用之间进行权衡,因为 Basilevs 建议减少必要的比较次数,并将一些处理时间从按键后的搜索推到名称的加载来自可能更适合用户的文件。

为了提高将元素加载到列表框中的性能,我建议仅加载前几个元素并向用户指示还有其他可用元素。通过这种方式,您可以向用户反馈有可用的结果,以便他们可以通过输入更多字母来优化搜索,或者只需按一下按钮即可加载完整列表。

使用BeginUpdateEndUpdate 不会改变set_Datasource 的执行时间。

正如其他人在这里指出的那样,LINQ 查询本身运行得非常快。我相信你的瓶颈是列表框本身的更新。你可以试试这样的:

if (textBox_search.Text.Length > 2)

    listBox_choices.BeginUpdate(); 
    listBox_choices.DataSource = allUsers.Where(item => item.Contains(textBox_search.Text)).ToList();
    listBox_choices.EndUpdate(); 

我希望这会有所帮助。

【讨论】:

我认为这不会改善任何事情,因为 BeginUpdateEndUpdate 旨在单独添加项目或使用 AddRange() 时使用。 这取决于DataSource属性是如何实现的。可能值得一试。 您的分析结果与我的非常不同。我能够在 30 毫秒内搜索 12 万个字符串,但将它们添加到列表框需要 4500 毫秒。听起来您在 600 毫秒内向列表框添加了 250 万个字符串。这怎么可能? @Gabe 在进行分析时,我使用了一个输入,其中过滤器文本消除了原始列表的很大一部分。如果我使用过滤器文本从列表中删除任何内容的输入,我会得到与您相似的结果。我将更新我的回复以澄清我所测量的内容。【参考方案2】:

您可以考虑在后台线程上执行过滤任务,该任务完成后会调用回调方法,或者在输入更改时重新启动过滤。

一般的想法是能够像这样使用它:

public partial class YourForm : Form

    private readonly BackgroundWordFilter _filter;

    public YourForm()
    
        InitializeComponent();

        // setup the background worker to return no more than 10 items,
        // and to set ListBox.DataSource when results are ready

        _filter = new BackgroundWordFilter
        (
            items: GetDictionaryItems(),
            maxItemsToMatch: 10,
            callback: results => 
              this.Invoke(new Action(() => listBox_choices.DataSource = results))
        );
    

    private void textBox_search_TextChanged(object sender, EventArgs e)
    
        // this will update the background worker's "current entry"
        _filter.SetCurrentEntry(textBox_search.Text);
    

粗略的草图类似于:

public class BackgroundWordFilter : IDisposable

    private readonly List<string> _items;
    private readonly AutoResetEvent _signal = new AutoResetEvent(false);
    private readonly Thread _workerThread;
    private readonly int _maxItemsToMatch;
    private readonly Action<List<string>> _callback;

    private volatile bool _shouldRun = true;
    private volatile string _currentEntry = null;

    public BackgroundWordFilter(
        List<string> items,
        int maxItemsToMatch,
        Action<List<string>> callback)
    
        _items = items;
        _callback = callback;
        _maxItemsToMatch = maxItemsToMatch;

        // start the long-lived backgroud thread
        _workerThread = new Thread(WorkerLoop)
        
            IsBackground = true,
            Priority = ThreadPriority.BelowNormal
        ;

        _workerThread.Start();
    

    public void SetCurrentEntry(string currentEntry)
    
        // set the current entry and signal the worker thread
        _currentEntry = currentEntry;
        _signal.Set();
    

    void WorkerLoop()
    
        while (_shouldRun)
        
            // wait here until there is a new entry
            _signal.WaitOne();
            if (!_shouldRun)
                return;

            var entry = _currentEntry;
            var results = new List<string>();

            // if there is nothing to process,
            // return an empty list
            if (string.IsNullOrEmpty(entry))
            
                _callback(results);
                continue;
            

            // do the search in a for-loop to 
            // allow early termination when current entry
            // is changed on a different thread
            foreach (var i in _items)
            
                // if matched, add to the list of results
                if (i.Contains(entry))
                    results.Add(i);

                // check if the current entry was updated in the meantime,
                // or we found enough items
                if (entry != _currentEntry || results.Count >= _maxItemsToMatch)
                    break;
            

            if (entry == _currentEntry)
                _callback(results);
        
    

    public void Dispose()
    
        // we are using AutoResetEvent and a background thread
        // and therefore must dispose it explicitly
        Dispose(true);
    

    private void Dispose(bool disposing)
    
        if (!disposing)
            return;

        // shutdown the thread
        if (_workerThread.IsAlive)
        
            _shouldRun = false;
            _currentEntry = null;
            _signal.Set();
            _workerThread.Join();
        

        // if targetting .NET 3.5 or older, we have to
        // use the explicit IDisposable implementation
        (_signal as IDisposable).Dispose();
    

此外,您应该在处置父 Form 时实际处置 _filter 实例。这意味着您应该打开并编辑您的FormDispose 方法(在YourForm.Designer.cs 文件中)看起来像:

// inside "xxxxxx.Designer.cs"
protected override void Dispose(bool disposing)

    if (disposing)
    
        if (_filter != null)
            _filter.Dispose();

        // this part is added by Visual Studio designer
        if (components != null)
            components.Dispose();
    

    base.Dispose(disposing);

在我的机器上,它运行得非常快,所以在寻求更复杂的解决方案之前,您应该对其进行测试和分析。

话虽如此,“更复杂的解决方案”可能是将最后几个结果存储在字典中,然后仅在新条目仅与最后一个字符的第一个不同时过滤它们。

【讨论】:

我刚刚测试了您的解决方案,它运行良好!做得很好。我唯一的问题是我无法编译_signal.Dispose();(关于保护级别的错误)。 @etaiso:这很奇怪,你在哪里调用 _signal.Dispose() 是不是在 BackgroundWordFilter 类之外的某个地方? @Groo 是显式实现,意味着不能直接调用。你应该使用using 块,或调用WaitHandle.Close() 好的,现在说得通了,该方法在 .NET 4 中公开。.NET 4 的 MSDN 页面将其列在 public methods 下,而 .NET 3.5 的页面将其显示在 @ 987654322@ 的。这也解释了为什么Mono source for WaitHandle 中有条件定义。 @Groo 对不起,我应该提到我在谈论旧版本的 .Net - 很抱歉造成混乱!但请注意,他不需要投射 - 他可以改为调用 .Close(),而后者本身调用 .Dispose()【参考方案3】:

尝试使用 BinarySearch 方法,它应该比 Contains 方法工作得更快。

包含将是一个 O(n) BinarySearch 是一个 O(lg(n))

我认为排序后的集合在搜索时应该更快,而在添加新元素时应该更慢,但据我了解,您只有搜索性能问题。

【讨论】:

【参考方案4】:

首先我将更改ListControl 如何查看您的数据源,您将结果IEnumerable&lt;string&gt; 转换为List&lt;string&gt;。特别是当您只输入几个字符时,这可能效率低下(并且不需要)。 不要复制大量数据

我会将.Where() 结果包装到一个仅实现IList(搜索)所需内容的集合中。这将节省您为输入的每个字符创建一个新的大列表。 作为替代方案,我会避免使用 LINQ,我会写一些更具体(和优化)的东西。将您的列表保存在内存中并构建一个匹配索引的数组,重复使用数组,这样您就不必为每次搜索重新分配它。

第二步,小一个就够了,不要在大列表中搜索。当用户开始键入“ab”并添加“c”时,您无需在大列表中进行研究,在过滤列表中搜索就足够了(而且速度更快)。 优化搜索每次都可以,不要每次都执行完整搜索。

第三步可能更难:保持数据井井有条,以便快速搜索。现在您必须更改用于存储数据的结构。想象一棵这样的树:

甲乙丙 添加更好的天花板 骨轮廓上方

这可以简单地用一个数组来实现(如果你使用的是 ANSI 名称,否则字典会更好)。像这样构建列表(说明目的,它匹配字符串的开头):

var dictionary = new Dictionary<char, List<string>>();
foreach (var user in users)

    char letter = user[0];
    if (dictionary.Contains(letter))
        dictionary[letter].Add(user);
    else
    
        var newList = new List<string>();
        newList.Add(user);
        dictionary.Add(letter, newList);
    

然后将使用第一个字符完成搜索:

char letter = textBox_search.Text[0];
if (dictionary.Contains(letter))

    listBox_choices.DataSource =
        new MyListWrapper(dictionary[letter].Where(x => x.Contains(textBox_search.Text)));

请注意,我按照第一步的建议使用了MyListWrapper()(但为了简洁起见,我在第二个建议中省略了,如果您为字典键选择正确的大小,您可以使每个列表简短而快速——也许——避免其他任何事情)。此外请注意,您可以尝试在字典中使用前两个字符(更多列表和更短的列表)。如果你扩展它,你将有一棵树(但我认为你没有这么多的项目)。

有many different algorithms用于字符串搜索(有相关的数据结构),仅举几例:

基于有限状态自动机的搜索:在这种方法中,我们通过构建识别存储搜索字符串的确定性有限自动机 (DFA) 来避免回溯。它们的构建成本很高——它们通常是使用 powerset 结构创建的——但使用起来非常快。 Stubs:Knuth–Morris–Pratt 计算一个 DFA,该 DFA 以要搜索的字符串作为后缀来识别输入,Boyer–Moore 从指针的末端开始搜索,因此它通常可以向前跳转每一步的针长。 Baeza-Yates 跟踪前面的 j 个字符是否是搜索字符串的前缀,因此适用于模糊字符串搜索。 bitap 算法是 Baeza–Yates 方法的应用。 索引方法:更快的搜索算法基于文本的预处理。建立子字符串索引后,例如后缀树或后缀数组,可以快速找到模式的出现。 其他变体:某些搜索方法(例如三元组搜索)旨在查找搜索字符串和文本之间的“接近度”分数,而不是“匹配/不匹配”。这些有时称为“模糊”搜索。

关于并行搜索的几句话。这是可能的,但它很少是微不足道的,因为使其并行的开销很容易比搜索本身高得多。我不会并行执行搜索本身(分区和同步很快就会变得过于庞大而且可能很复杂),但我会将搜索移至单独的线程。如果主线程不,您的用户在输入时不会感到任何延迟(他们不会注意到列表是否会在 200 毫秒后出现,但如果必须这样做他们会感到不舒服输入后等待 50 毫秒)。当然,搜索本身必须足够快,在这种情况下,您不使用线程来加快搜索速度,而是保持 UI 响应式。请注意,单独的线程不会使您的查询更快,它不会挂起 UI,但如果您的查询速度很慢,它在单独的线程中仍然会很慢(此外,您还必须处理多个顺序请求)。

【讨论】:

正如一些人已经指出的那样,OP 不想将结果仅限于前缀(即他使用Contains,而不是StartsWith)。附带说明一下,在搜索键以避免装箱时,通常最好使用通用 ContainsKey 方法,甚至更好地使用 TryGetValue 以避免两次查找。 @Groo 你是对的,正如我所说的,这仅用于说明目的。该代码的重点不是一个有效的解决方案,而是一个提示:如果您尝试了其他所有方法 - 避免复制,优化搜索,将其移动到另一个线程 - 这还不够,那么您必须更改您正在使用的数据结构.示例是字符串的开头只是为了保持简单。 @Adriano 感谢您清晰详细的回答!我同意你提到的大部分事情,但正如 Groo 所说,保持数据组织的最后一部分不适用于我的情况。但我认为可能会持有一个类似的字典,其中包含的字母作为键(尽管仍然会有重复) 经过快速检查和计算后,“包含字母”的想法不适用于仅一个字符(如果我们使用两个或多个字符的组合,我们最终会得到一个非常大的哈希表) @etaiso 是的,您可以保留一个包含两个字母的列表(以快速减少子列表),但真正的树可能效果更好(每个字母都链接到它的后继者,无论它在字符串,所以对于“HOME”,你有“H->O”、“O->M”和“M->E”。如果你正在搜索“om”,你会很快找到它。问题是它变得很漂亮更复杂,对你来说可能太多了(IMO)。【参考方案5】:

我进行了一些测试,搜索包含 120,000 个项目的列表并使用条目填充新列表所需的时间可以忽略不计(即使所有字符串都匹配,也大约需要 1/50 秒)。

因此,您看到的问题一定来自数据源的填充,这里:

listBox_choices.DataSource = ...

我怀疑您只是将太多项目放入列表框中。

也许您应该尝试将其限制为前 20 个条目,如下所示:

listBox_choices.DataSource = allUsers.Where(item => item.Contains(textBox_search.Text))
    .Take(20).ToList();

另请注意(正如其他人指出的那样)您正在访问allUsers 中每个项目的TextBox.Text 属性。这可以很容易地修复如下:

string target = textBox_search.Text;
listBox_choices.DataSource = allUsers.Where(item => item.Contains(target))
    .Take(20).ToList();

但是,我计算了访问TextBox.Text 500,000 次所需的时间,只用了 0.7 秒,远低于 OP 中提到的 1-3 秒。不过,这是值得优化的。

【讨论】:

谢谢马修。我尝试了您的解决方案,但我认为问题不在于 ListBox 的数量。我认为我需要一种更好的方法,因为这种过滤非常幼稚(例如 - 搜索“abc”返回 0 个结果,那么我什至不应该寻找“abcX”等等..) @etaiso 正确(即使 Matthew 的解决方案可能会在您真的不需要预设所有匹配项的情况下工作得很好),这就是为什么 I suggested 作为 refine 搜索的第二步每次执行完整搜索。 @etaiso 好吧,就像我说的那样,搜索时间可以忽略不计。我用 120,000 个字符串进行了尝试,搜索一个很长但没有匹配的字符串和一个很短但提供很多匹配的字符串都花了不到 1/50 秒。 textBox_search.Text 对时间的贡献是多少?为 120k 字符串中的每一个在文本框中获取一次 Text 属性可能会向编辑控制窗口发送 120k 消息。 @Gabe 是的。详情见我的回答。【参考方案6】:

我怀疑你能否让它更快,但你肯定应该:

a) 使用 AsParallel LINQ 扩展方法

a) 使用某种计时器来延迟过滤

b) 将过滤方法放在另一个线程上

在某处保留某种string previousTextBoxValue。做一个有延迟的计时器 1000 毫秒,如果 previousTextBoxValue 与您的 textbox.Text 值相同,则会触发搜索。如果不是 - 将 previousTextBoxValue 重新分配给当前值并重置计时器。将计时器启动设置为文本框更改事件,它会使您的应用程序更流畅。在 1-3 秒内过滤 120,000 条记录是可以的,但您的 UI 必须保持响应。

【讨论】:

我不同意让它平行,但我绝对同意其他两点。它甚至可能足以满足 UI 要求。 忘了提及,但我使用的是 .NET 3.5,所以 AsParallel 不是一个选项。【参考方案7】:

您也可以尝试使用BindingSource.Filter 函数。我已经使用过它,它就像从一堆记录中过滤的魅力一样,每次使用正在搜索的文本更新此属性。另一种选择是将AutoCompleteSource 用于TextBox 控件。

希望对你有帮助!

【讨论】:

【参考方案8】:

根据我所见,我同意对列表进行排序的事实。

但是在构建列表时排序会很慢,在构建时排序,执行时间会更好。

否则,如果您不需要显示列表或保持顺序,请使用哈希图。

hashmap 将散列您的字符串并在确切的偏移量处搜索。我认为应该更快。

【讨论】:

Hashmap 用什么键?我希望能够找到包含在字符串中的关键字。 对于他的键,您可以将数字放在列表中,如果您想要更复杂,您可以添加数字和名称,选择是您的。 对于其余部分,要么我没有阅读所有内容,要么有一个不好的解释(可能两者都有;))[quote] 有一个我想存储的大约 120,000 个用户(字符串)的文本文件在集合中,然后在该集合上执行搜索。 [/quote] 我以为这只是一个字符串搜索。【参考方案9】:

WinForms ListBox 控件确实是您的敌人。加载记录会很慢,而且 ScrollBar 会与您争夺所有 120,000 条记录。

尝试使用老式的 DataGridView 数据源到具有单列 [UserName] 的 DataTable 来保存您的数据:

private DataTable dt;

public Form1() 
  InitializeComponent();

  dt = new DataTable();
  dt.Columns.Add("UserName");
  for (int i = 0; i < 120000; ++i)
    DataRow dr = dt.NewRow();
    dr[0] = "user" + i.ToString();
    dt.Rows.Add(dr);
  
  dgv.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
  dgv.AllowUserToAddRows = false;
  dgv.AllowUserToDeleteRows = false;
  dgv.RowHeadersVisible = false;
  dgv.DataSource = dt;

然后在 TextBox 的 TextChanged 事件中使用 DataView 来过滤数据:

private void textBox1_TextChanged(object sender, EventArgs e) 
  DataView dv = new DataView(dt);
  dv.RowFilter = string.Format("[UserName] LIKE '%0%'", textBox1.Text);
  dgv.DataSource = dv;

【讨论】:

+1 当其他人都在尝试优化只需要 30 毫秒的搜索时,您是唯一认识到问题实际上在于填充列表框的人。【参考方案10】:

使用并行LINQPLINQ 是 LINQ to Objects 的并行实现。 PLINQ 将整套 LINQ 标准查询运算符实现为 T:System.Linq 命名空间的扩展方法,并具有用于并行操作的附加运算符。 PLINQ 将 LINQ 语法的简单性和可读性与并行编程的强大功能相结合。就像以任务并行库为目标的代码一样,PLINQ 查询根据主机的功能扩展并发程度。

Introduction to PLINQ

Understanding Speedup in PLINQ

你也可以使用Lucene.Net

Lucene.Net 是 Lucene 搜索引擎库的移植,写在 C# 并针对 .NET 运行时用户。 Lucene 搜索库是 基于倒排索引。 Lucene.Net 有三个主要目标:

【讨论】:

【参考方案11】:

使用Suffix tree 作为索引。或者更确切地说,只是构建一个排序字典,将每个名称的每个后缀与对应名称的列表相关联。

输入:

Abraham
Barbara
Abram

结构如下:

a -> Barbara
ab -> Abram
abraham -> Abraham
abram -> Abram
am -> Abraham, Abram
aham -> Abraham
ara -> Barbara
arbara -> Barbara
bara -> Barbara
barbara -> Barbara
bram -> Abram
braham -> Abraham
ham -> Abraham
m -> Abraham, Abram
raham -> Abraham
ram -> Abram
rbara -> Barbara

搜索算法

假设用户输入“bra”。

    Bisect 用户输入字典以查找用户输入或它可以去的位置。这样我们就可以找到“barbara”——最后一个键低于“bra”。它被称为“胸罩”的下限。搜索需要对数时间。 从找到的键开始迭代,直到用户输入不再匹配。这将给出 "bram" -> Abram 和 "braham" -> Abraham。 连接迭代结果(Abram, Abraham)并输出。

这样的树是为快速搜索子字符串而设计的。它的性能接近 O(log n)。我相信这种方法的运行速度足够快,可以直接被 GUI 线程使用。此外,由于没有同步开销,它会比线程解决方案运行得更快。

【讨论】:

据我所知,后缀数组通常比后缀树更好。更容易实现并降低内存使用量。 我提出了 SortedList,它非常易于构建和维护,但会以内存开销为代价,通过提供列表容量可以将其最小化。 另外,数组(和原始的 ST)似乎是为处理大文本而设计的,而这里我们有大量的短块,这是不同的任务。 +1 是个好方法,但我会使用哈希映射或实际搜索树,而不是手动搜索列表。 用后缀树代替前缀树有什么好处吗?【参考方案12】:

我会尝试对集合进行排序,搜索以仅匹配开始部分并将搜索限制为某个数字。

等初始化

allUsers.Sort();

搜索

allUsers.Where(item => item.StartWith(textBox_search.Text))

也许你可以添加一些缓存。

【讨论】:

他没有使用字符串的开头(这就是他使用 String.Contains() 的原因)。使用 Contains(),排序列表不会改变性能。 是的,“包含”是没用的。我喜欢带有后缀树的建议***.com/a/21383731/994849 线程中有很多有趣的答案,但这取决于他可以花多少时间来完成这项任务。【参考方案13】:

您需要文本搜索引擎(如Lucene.Net)或数据库(您可以考虑使用嵌入式搜索引擎,如SQL CE、SQLite 等)。换句话说,您需要一个索引搜索。基于哈希的搜索在这里不适用,因为您搜索子字符串,而基于哈希的搜索适合搜索精确值。

否则它将是一个遍历集合的迭代搜索。

【讨论】:

索引基于哈希的搜索。您只需将所有子字符串添加为键,而不仅仅是值。 @OrangeDog:不同意。索引搜索可以通过索引键实现为基于哈希的搜索,但这不是必需的,它也不是基于字符串值本身的基于哈希的搜索。 @Dennis 同意。 +1 取消幽灵 -1。 +1 因为文本搜索引擎之类的实现比string.Contains 具有更智能的优化。 IE。在bcaaaabaa 中搜索ba 将产生一个(索引的)跳过列表。第一个b被考虑了,但是不匹配因为下一个是c,所以会跳到下一个b【参考方案14】:

设置“去抖动”类型的事件也可能很有用。这与限制不同,它在触发事件之前等待一段时间(例如 200 毫秒)让更改完成。

请参阅 Debounce and Throttle: a visual explanation 了解更多关于去抖动的信息。我很欣赏这篇文章专注于 javascript,而不是 C#,但原则适用。

这样做的好处是当您仍在输入查询时它不会进行搜索。然后它应该停止尝试同时执行两个搜索。

【讨论】:

查看 Algorithmia 库中 EventThrottler 类的事件限制器的 C# 实现:github.com/SolutionsDesign/Algorithmia/blob/master/…【参考方案15】:

在另一个线程上运行搜索,并在该线程运行时显示一些加载动画或进度条。

您也可以尝试并行化 LINQ 查询。

var queryResults = strings.AsParallel().Where(item => item.Contains("1")).ToList();

这是一个展示 AsParallel() 性能优势的基准测试:


    IEnumerable<string> queryResults;
    bool useParallel = true;

    var strings = new List<string>();

    for (int i = 0; i < 2500000; i++)
        strings.Add(i.ToString());

    var stp = new Stopwatch();

    stp.Start();

    if (useParallel)
        queryResults = strings.AsParallel().Where(item => item.Contains("1")).ToList();
    else
        queryResults = strings.Where(item => item.Contains("1")).ToList();

    stp.Stop();

    Console.WriteLine("useParallel: 0\r\nTime Elapsed: 1", useParallel, stp.ElapsedMilliseconds);

【讨论】:

我知道这是可能的。但我的问题是我是否以及如何缩短这个过程? @etaiso 除非您在一些非常低端的硬件上进行开发,否则这应该不是问题,请确保您没有运行调试器,CTRL + F5 这不是 PLINQ 的好选择,因为方法 String.Contains 并不昂贵。 msdn.microsoft.com/en-us/library/dd997399.aspx @TimSchmelter 当我们谈论大量字符串时,它是! @TimSchmelter 我不知道您要证明什么,使用我提供的代码很可能会提高 OP 的性能,这是一个演示它如何工作的基准:pastebin.com/ATYa2BGt --- 期间 ---【参考方案16】:

假设您仅通过前缀匹配,您要查找的数据结构称为trie,也称为“前缀树”。您现在使用的 IEnumerable.Where 方法必须在每次访问时遍历字典中的所有项目。

This thread 展示了如何在 C# 中创建 trie。

【讨论】:

假设他用前缀过滤他的记录。 注意他使用的是 String.Contains() 方法而不是 String.StartsWith(),所以它可能不是我们正在寻找的。仍然 - 你的想法无疑比在前缀场景中使用 StartsWith() 扩展的普通过滤更好。 如果他的意思是从头开始,那么可以将 Trie 与后台工作者方法结合起来以提高性能【参考方案17】:

您可以尝试使用PLINQ(并行 LINQ)。 虽然这并不能保证速度提升,但您需要通过反复试验来找出答案。

【讨论】:

以上是关于在字符串集合中搜索的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

Java中最快的子字符串搜索方法是啥

在给定字符串中搜索字符集的最快算法

什么 .NET 集合提供最快的搜索

C ++在固定大小9的右填充空终止字符数组中找到第一个空格的最快方法

在非常大的文件中快速搜索字符串

确定一个值是不是在 Java 中的一组值中的最快方法是啥?