【问题标题】:C# search large text file quicklyC#快速搜索大文本文件
【发布时间】:2021-02-08 22:32:17
【问题描述】:

我浏览了一篇与视图相关的文章,但还没有找到适合我查询的解决方案。 在一个大的纯文本文件(~150MB,~1.800.000 行)中,我很快想使用 C# 找到具有某些功能的特定行。

每行有 132 个字符,每一行都有一个区域-、一个节-、一个小节代码和一个标识。 这四个特征的组合是独一无二的。 根据部分代码,其他部分的确切位置可能会有所不同。

基本上,我想用一种方法检索多达 50 个元素,理想情况下需要不到一秒的时间。

到目前为止,我的代码可以运行,但对于我的目的来说速度很慢(30 个条目的执行时间约为 29 秒):

//icaoCode is always 2 char long
public static List<Waypoint> Retrieve(List<(string ident, string icaoCode, char sectionCode, char subSectionCode)> wpData)
        {
            List<Waypoint> result = new List<Waypoint>();

            using StreamReader reader = new StreamReader(dataFile);
            while (!reader.EndOfStream)
            {
                string data = reader.ReadLine();
                if (data.Length != 132) continue;

                foreach(var x in wpData)
                {
                    int subsPos = (x.sectionCode, x.subSectionCode) switch
                    {
                        ('P', 'N') => 5,
                        ('P', _) => 12,
                        (_, _) => 5
                    };

                    if (data[4].Equals(x.sectionCode) && data[subsPos].Equals(x.subSectionCode))
                    {
                       //IsNdb() and others look at the sectionCode and subSectionCode to determine data type
                        if (IsNdb(data) && data[13..17].Trim() == x.ident && data[19..21] == x.icaoCode) result.Add(ArincHelper.LoadNdbEntry(data));

                        else if (IsVhf(data) && data[13..17].Trim() == x.ident && data[19..21] == x.icaoCode) result.Add(ArincHelper.LoadVhfEntry(data));
                        else if (IsTacan(data) && data[13..17].Trim() == x.ident) result.Add(ArincHelper.LoadTacanEntry(data));
                        else if (IsIls(data) && data[13..17].Trim() == x.ident && data[10..12] == x.icaoCode) result.Add(ArincHelper.LoadIlsEntry(data));
                        else if (IsAirport(data) && data[6..10] == x.ident && data[10..12] == x.icaoCode) result.Add(ArincHelper.LoadAirportEntry(data));
                        else if (IsRunway(data) && (data[6..10] + data[15..18].Trim()) == x.ident && data[10..12] == x.icaoCode) result.Add(ArincHelper.LoadRunwayEntryAsWaypoint(data));
                        else if (IsWaypoint(data) && data[13..18].Trim() == x.ident && data[19..21] == x.icaoCode) result.Add(ArincHelper.LoadWaypointEntry(data));
                    }
                }
            }
            reader.Close();

            return result;
        }

IsNdb() 和其他识别方法都是这样的:

private static bool IsNdb(string data) => (data[4], data[5]) == ('D', 'B') || (data[4], data[5]) == ('P', 'N');

一些示例数据行是:

SEURPNEBBREB OP    EB004020HOM  N50561940E004353360                       E0010           WGEBRUSSELS                      169641609
SEURP EDDFEDAFRA     0FL100131Y N50015990E008341364E002300364250FFM ED05000          MWGE    FRANKFURT/MAIN                331502006
SEURD        CHA   ED011535VDHB N49551597E009022334CHA N49551597E009022334E0020005292  249WGECHARLIE                       867432005
SEURP LFFKLFCFK404 LF0    W F   N46262560W000480430                       E0000     WGE           FK404                    331071909

我想避免将整个文件加载到内存中,因为这需要大约 400MB 的 RAM,尽管这当然是可能的。

提前感谢您的帮助。

编辑: 当前的解决方案将此数据文件转换为 SQLite DB,然后使用该数据库。 然而,这需要大约 3 小时的时间将文件转换为数据库,这是我想避免的,因为数据文件会定期换出。 这就是为什么我想尝试解析这个文本。

【问题讨论】:

  • 您是否考虑将文件加载到更适合查询的地方,例如数据库?
  • 您是否知道大约需要阅读的记录才能获得所需数量的元素?
  • 参数列表最多可以有大约 50 个条目,代码只是检索所有条目。
  • 不幸的是,没有,因为它们没有必要订购..
  • 当前的解决方案将其转换为 SQLite DB,但我想摆脱它,因为它可以更轻松地换出数据文件。转换一个需要大约 3 小时,这有点烦人。

标签: c# parsing search text


【解决方案1】:

正如@mjwills 所建议的,有更适合这项工作的工具,我会将这些数据保存在数据库中。如果我要尝试使您当前的代码更快,我会尝试以下方法。我会将数据块读入一个数组,并在并行 for 循环中处理这部分数据,当你有足够的元素时退出循环。下面是一些可以帮助您入门的伪代码。我无法编写完整的代码,因为我没有你的对象/文件。

List<Waypoint> result = new List<Waypoint>();

var max = 1800000; //set the to the max rows in your file
var allLines = new string[max];
var dataFile = "";

using (StreamReader sr = File.OpenText(dataFile))
{
    int x = 0;
               
    while (!sr.EndOfStream)
    {
        allLines[x] = sr.ReadLine();
        x += 1;

        if (x % 5000 == 0)
        {
             var i = x - 5000;
             Parallel.For(i, allLines.Length, x =>
             {
                //do your process here and exit if you have enough elements also set
                //a flag to exit the while loop
             });
        }

        //you would have to write some code to handle the last group of records that are less than 5k
    }
}

【讨论】:

  • 感谢您的努力,我会尝试一下。正如我刚刚在上面评论的,当前的解决方案实际上使用了到 SQLite 的转换,但这需要 3h 每个文件,它会定期换出,这就是我想尝试使用原始数据文件的原因。
  • 你的 SQLite 加载应该比这快得多。我可以使用批量插入快速加载非常大的文件。
  • 没错,SQLite 的实现非常好。这更像是一种“试试看我能不能达到性能”之类的东西。
  • @mjwills 抱歉 - 我想知道自动填充不起作用...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-14
  • 2012-03-17
  • 2011-07-30
  • 1970-01-01
  • 2014-08-07
相关资源
最近更新 更多