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


80

问题:

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

每当用户更改a的文本时,都会出现搜索方法,TextBox并且结果应为中包含文本的字符串TextBox

我不必更改列表,只需拉出结果并将其放在即可ListBox

到目前为止,我已经尝试过:

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

  1. List<string> allUsers;
  2. 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秒)。

题:

您认为我的瓶颈在哪里?我使用过的收藏?搜索方法?都?

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


10
HashSet<T>在这里对您无济于事,因为您正在搜索字符串的一部分
丹尼斯2014年


66
不要问“最快的方法是什么”,因为这实际上需要数周到数年的研究。而是说“我需要一个运行时间少于30毫秒的解决方案”,或者说您的性能目标是什么。您不需要最快的设备,而是需要足够快的设备。
埃里克·利珀特

44
另外,获取分析器。不要猜测最慢的部分在哪里;这样的猜测通常是错误的。瓶颈可能出人意料。
埃里克·利珀特

4
@Basilevs:我曾经写过一个可爱的O(1)哈希表,在实践中非常慢。我对其进行了分析,以找出原因,并发现在每次搜索中,它都在调用一种方法-绝不是在开玩笑-最终询问注册表“我们现在在泰国吗?”。不缓存用户是否在泰国是该O(1)代码的瓶颈。瓶颈的位置可能会很违反直觉。使用探查器。
Eric Lippert 2014年

Answers:


48

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

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

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();
    }
}

另外,您实际上应该在处置_filter父项时处置该实例Form。这意味着您应该打开并编辑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 2014年

@etaiso:这很奇怪,您在哪里_signal.Dispose()叫它是BackgroundWordFilter课堂以外的地方?
Groo 2014年

1
@Groo这是显式的实现,意味着您不能直接调用它。你应该使用一个using块,或拨打电话WaitHandle.Close()
马修·沃森

1
好的,这很有意义,该方法已在.NET 4中公开。.NET 4的MSDN页面在公共方法下列出了该方法,而.NET 3.5的页面在受保护的方法下列出了该方法。这也解释了为什么在Mono语言中为WaitHandle提供条件定义。
Groo 2014年

1
@Groo对不起,我应该提到我是在谈论.Net的较旧版本-对此感到抱歉!但是请注意,他不需要强制转换-他可以呼叫.Close(),后者本身称为.Dispose()
马修·沃森

36

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

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

listBox_choices.DataSource = ...

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

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

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

还要注意(正如其他人指出的那样),您正在访问中的TextBox.Text每个项目的属性allUsers。可以很容易地解决此问题,如下所示:

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

但是,我对访问TextBox.Text500,000次所花的时间进行了计时,只花了0.7秒,远远少于OP中提到的1-3秒。尽管如此,这还是值得的优化。


1
谢谢马修。我尝试了您的解决方案,但我认为问题不在于列表框的数量。我认为我需要一种更好的方法,因为这种过滤非常幼稚(例如-搜索“ abc”返回0个结果,那么我什至不应该寻找“ abcX”,依此类推。)
etaiso

@etaiso是正确的(即使您不需要真正设置所有匹配项,即使Matthew的解决方案可能效果很好),这也是为什么我建议第二步来优化搜索,而不是每次都执行完整搜索。
Adriano Repetti 2014年

5
@etaiso嗯,就像我说的那样,搜索时间可以忽略不计。我用120,000个字符串进行了尝试,并搜索了一个没有匹配的长字符串和一个很短的字符串,这两个字符串都花费了不到1/50秒的时间。
马修·沃森

3
是否textBox_search.Text贡献了可衡量的时间?获取Text上的文本框属性每个120K串的一次大概120K发送消息到编辑控件窗口。
2014年

@Gabe是的。有关详细信息,请参见我的答案。
Andris 2014年

28

使用后缀树作为索引。或者只是构建一个排序字典,将每个名称的每个后缀与相应名称的列表相关联。

输入:

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”。

  1. 根据用户输入将字典二等分,以找到用户输入或输入可能到达的位置。这样,我们发现“ barbara”-最后一个键低于“ bra”。它被称为“ bra”的下限。搜索将花费对数时间。
  2. 从找到的键开始迭代,直到用户输入不再匹配为止。这将得到“ bram”-> Abram和“ braham”-> Abraham。
  3. 连接迭代结果(Abram,Abraham)并输出。

这样的树旨在用于子字符串的快速搜索。它的性能接近O(log n)。我相信这种方法将足够快地工作,可以直接由GUI线程使用。此外,由于没有同步开销,它将比线程解决方案更快地工作。


据我所知,后缀数组通常比后缀树更好。易于实现并降低内存使用量。
CodesInChaos

我建议使用SortedList,它很容易构建和维护,但要消耗内存开销,而可以通过提供列表容量将其最小化。
Basilevs 2014年

同样,数组(和原始ST)似乎是为处理大文本而设计的,而这里我们有大量的短块,这是不同的任务。
Basilevs 2014年

+1是一种很好的方法,但是我会使用哈希图或实际的搜索树,而不是手动搜索列表。
OrangeDog 2014年

使用后缀树而不是前缀树有什么优势吗?
jnovacho 2014年

15

您需要文本搜索引擎(例如Lucene.Net)或数据库(您可以考虑使用嵌入式搜索引擎,例如SQL CESQLite等)。换句话说,您需要索引搜索。基于散列的搜索不适用于此处,因为您搜索的是子字符串,而基于散列的搜索非常适合搜索精确值。

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


索引基于哈希的搜索。您只需将所有子字符串添加为键,而不仅仅是值。
OrangeDog 2014年

3
@OrangeDog:不同意。索引搜索可以通过索引键实现为基于哈希的搜索,但这不是必须的,并且它不是基于字符串值本身的基于哈希的搜索。
丹尼斯

@丹尼斯同意。+1取消重影-1。
用户

+1是因为诸如文本搜索引擎之类的实现比的智能(更)优化string.Contains。就是 搜索babcaaaabaa会导致(索引)跳跃列表。b考虑了第一个,但由于下一个是a c,所以不匹配,因此它将跳到next b
Caramiriel 2014年

12

发生“反跳”类型的事件也可能很有用。这与限制不同,因为它会在触发事件之前等待一段时间(例如200毫秒)以完成更改。

请参阅“防抖动和油门”:有关防抖动的更多信息的直观说明。我赞赏本文是针对JavaScript的,而不是C#,但该原则适用。

这样做的好处是,即使您仍然输入查询,它也不会搜索。然后,它应该停止尝试一次执行两次搜索。


请参见Algorithmia库中的EventThrotler类,以获取事件调节器的C#实现:github.com/SolutionsDesign/Algorithmia/blob/master/…–
Frans Bouma

11

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

您也可以尝试并行化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);
}

1
我知道这是有可能的。但是我的问题是,是否以及如何缩短此过程?
etaiso 2014年

1
@etaiso除非您是在某些非常低端的硬件上进行开发,否则它应该不是真正的问题,请确保您没有运行调试器CTRL + F5
animaonline 2014年

1
这不是PLINQ的理想选择,因为该方法String.Contains并不昂贵。msdn.microsoft.com/en-us/library/dd997399.aspx
Tim Schmelter 2014年

1
@TimSchmelter,当我们谈论大量的弦乐时,它是!
animaonline 2014年

4
@TimSchmelter我不知道您要尝试证明什么,使用我提供的代码很可能会提高OP的性能,这是一个演示其工作原理的基准:pastebin.com/ATYa2BGt --- Period- -
animaonline

11

更新:

我做了一些分析。

(更新3)

  • 列表内容:从0到2.499.999的数字
  • 过滤文本:123(20.477结果)
  • 核心i5-2500,Win7 64位,8GB RAM
  • VS2012 + JetBrains dotTrace

最初的2.500.000条记录的测试耗时20.000ms。

罪魁祸首是textBox_search.Text内线的呼唤Contains。这使每个元素都调用get_WindowText了文本框的昂贵方法。只需将代码更改为:

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

将执行时间减少到1.858ms

更新2:

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

通过创建后缀树,我们可以在速度和内存使用之间进行权衡,因为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(); 
}

我希望这有帮助。


我不认为这将提高任何作为BeginUpdate,并EndUpdate打算单独添加项目或使用时用于AddRange()
etaiso 2014年

这取决于DataSource属性的实现方式。可能值得一试。
Andris 2014年

您的分析结果与我的截然不同。我能够在30毫秒内搜索120k字符串,但是将它们添加到列表框中需要4500毫秒。听起来您要在600毫秒内将250万个字符串添加到列表框中。那怎么可能?
加布2014年

@Gabe分析时,我使用了一个输入,其中过滤器文本消除了原始列表的很大一部分。如果在过滤器文本不会从列表中删除任何内容的情况下使用输入,则会得到与您相似的结果。我将更新我的回答以阐明我的测量。
Andris 2014年

9

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

此线程显示如何在C#中创建特里。


1
假设他使用前缀过滤记录。
Tarec 2014年

1
请注意,他使用的是String.Contains()方法,而不是String.StartsWith(),因此它可能与我们要查找的不完全相同。尽管如此,您的想法无疑比在前缀方案中使用StartsWith()扩展进行普通过滤更好。
Tarec 2014年

如果他确实是从头开始的话,那么Trie可以与后台工作者方法结合使用以提高性能
Lyndon White

8

WinForms ListBox控件确实是您的敌人。加载记录会很慢,并且ScrollBar将使您无法显示所有120,000条记录。

尝试使用具有单个列[UserName]的数据表中使用的老式DataGridView数据来保存数据:

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;
}

2
+1,而其他所有人都试图优化仅耗时30毫秒的搜索时,您是唯一认识到问题实际上出在填充列表框中的人。
加布2014年

7

首先,我将更改如何ListControl查看数据源,将结果转换IEnumerable<string>List<string>。尤其是当您仅键入几个字符时,这可能是低效的(并且是不必要的)。不要对数据进行扩展复制

  • 我将.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()按照第一步的建议使用(但为了简洁起见,我省略了第二条建议,如果您为字典键选择合适的大小,则可以使每个列表简短而快速-也许-避免其他事情)。此外,请注意,您可能会尝试在字典中使用前两个字符(更多列表,更短列表)。如果您对此进行扩展,则会有一棵树(但我认为您没有那么多的项目)。

字符串搜索有很多不同的算法(具有相关的数据结构),仅举几例:

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

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


1
正如某些人已经指出的那样,OP不想将结果限制为仅前缀(即,他使用Contains,而不是StartsWith)。附带说明一下,通常最好ContainsKey在搜索关键字时使用通用方法以避免装箱,甚至最好TryGetValue避免使用两次查找。
Groo 2014年

2
@Groo,您说得对,因为我说的只是出于说明目的。该代码的重点不是有效的解决方案,而是一个提示:如果您尝试了其他所有操作-避免复制,优化搜索,将其移至另一个线程-而且这还不够,那么您必须更改所使用的数据结构。示例是为了保持字符串简单起见。
Adriano Repetti 2014年

@Adriano感谢您提供清晰详细的答案!我同意您提到的大多数内容,但是正如Groo所说,保持数据有条理的最后一部分不适用于我的情况。但我认为也许要持有一个与键类似的字典作为所含字母(尽管仍然会有重复)
etaiso 2014年

经过快速检查和计算后,“包含字母”的概念仅适合于一个字符(如果我们将两个或多个字符组合使用,则会得到一个非常大的哈希表)
etaiso 2014年

@etaiso是的,您可以保留两个字母的列表(以快速减少子列表),但是一棵真正的树可能会更好(每个字母都与其后继链接,无论它在字符串中的什么位置,因此对于“ HOME” “ H-> O”,“ O-> M”和“ M-> E”。如果您要搜索“ om”,则会很快找到它。问题是它变得更加复杂并且可能太多了为您设想的情景(IMO)
Adriano Repetti 2014年

4

您可以尝试使用PLINQ(Parallel LINQ)。尽管这并不能保证提高速度,但是您需要通过反复试验来找出答案。


4

我怀疑您能否使其更快,但是可以肯定的是:

a)使用AsParallel LINQ扩展方法

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

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

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


1
我不同意将其平行,但我完全同意其他两点。它甚至可能足以满足UI要求。
2014年

忘记提及了,但是我使用的是.NET 3.5,因此AsParallel不是一个选择。
etaiso 2014年

3

您也可以尝试使用BindingSource.Filter函数。我已经使用过它,并且每次从被搜索的文本更新此属性时,它都像从列表中过滤掉的一种魅力。另一个选择是将AutoCompleteSource用于TextBox控件。

希望能帮助到你!


2

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

初始化

allUsers.Sort();

和搜索

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

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


1
他没有使用字符串的开头(这就是为什么他使用String.Contains()的原因)。使用Contains(),排序列表不会改变性能。
Adriano Repetti 2014年

是的,使用“包含”是没有用的。我喜欢有关sufix树的建议stackoverflow.com/a/21383731/994849线程中有很多有趣的答案,但这取决于他可以在此任务上花费多少时间。
hardsky

1

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

PLINQ简介

了解PLINQ中的加速

您也可以使用Lucene.Net

Lucene.Net是Lucene搜索引擎库的端口,使用C#编写,面向.NET运行时用户。Lucene搜索库基于反向索引。Lucene.Net具有三个主要目标:


1

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

但是,在构造列表时进行排序的速度非常慢,在构建列表时进行排序的时间会更长。

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

哈希图将对您的字符串进行哈希处理并以确切的偏移量进行搜索。我认为应该更快。


Hashmap用什么键?我希望能够找到字符串中包含的关键字。
etaiso 2014年

对于他的钥匙,您可以将数字放在列表中,如果您想更复杂,可以添加数字加名称,这是您的选择。
dada 2014年

其余的我要么都没看完,要么有一个不好的解释(可能是两个;))[quote]的文本文件大约有120,000个用户(字符串),我想将其存储在集合中,然后执行搜索该收藏。[/ quote]我以为这只是一个字符串搜索。
dada 2014年

1

尝试使用BinarySearch方法,它应比Contains方法更快。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.