文件.ReadLines需要很长时间来处理文本文件



我有一个文本文件包含以下类似的行,例如500k行。

ADD GTRX:TRXID=0, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-0", FREQ=81, TRXNO=0, CELLID=639, IDTYPE=BYID, ISMAINBCCH=YES, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=1, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-1", FREQ=24, TRXNO=1, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=5, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-2", FREQ=28, TRXNO=2, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;
ADD GTRX:TRXID=6, TRXNAME="M_RAK_JeerExch_G_1879_18791_A-3", FREQ=67, TRXNO=3, CELLID=639, IDTYPE=BYID, ISMAINBCCH=NO, ISTMPTRX=NO, GTRXGROUPID=2556;

我的意图是首先获得FREQ的价值,其中ISMAINBCCH=YES我很容易做到,但如果ISMAINBCCH=NO然后连接FREQ值,我已经使用File.ReadLines完成了,但它需要很长时间。有更好的方法吗?如果我采取FREQ值为ISMAINBCCH=YES,然后连接值ISMAINBCCH=NO在上下10行的范围内,但我不知道如何实现它。也许我应该得到ISMAINBCCH=YESFREQ的当前行。下面是我到目前为止所做的代码

using (StreamReader sr = File.OpenText(filename))
{
    while ((s = sr.ReadLine()) != null)
    {
        if (s.Contains("ADD GTRX:"))
        {
            try
            {
                var gtrx = new Gtrx
                {
                    CellId = int.Parse(PullValue(s, "CELLID")),
                    Freq = int.Parse(PullValue(s, "FREQ")),
                    //TrxNo = int.Parse(PullValue(s, "TRXNO")),
                    IsMainBcch = PullValue(s, "ISMAINBCCH").ToUpper() == "YES",
                    Commabcch = new List<string> { PullValue(s, "ISMAINBCCH") },
                    DEFINED_TCH_FRQ = null,
                    TrxName = PullValue(s, "TRXNAME"),
                };
                var result = String.Join(",",
                    from ss in File.ReadLines(filename)
                    where ss.Contains("ADD GTRX:")
                    where int.Parse(PullValue(ss, "CELLID")) == gtrx.CellId
                    where PullValue(ss, "ISMAINBCCH").ToUpper() != "YES"
                    select int.Parse(PullValue(ss, "FREQ")));
            }
        }
    }
    gtrx.DEFINED_TCH_FRQ = result;
}

from ss in File.ReadLines(filename)

读取整个文件,生成一个数组,然后在循环中使用该数组(本身来自读取同一文件),因此该数组被丢弃,然后再次创建。你正在读取同一个文件number_of_lines + 1次,当它在此期间没有改变。

因此,一个明显的提升将是只调用File.ReadLines(filename)一次,存储数组,然后在循环中使用该数组而不是while ((s = sr.ReadLine()) != null),并在循环中使用该数组而不是重复调用ReadLines()

但是你的逻辑有一个缺陷,即使是反复看ReadLines();你已经浏览了整个文件,所以你会在后面遇到与同一个CELLID相关的所有行:

var gtrxDict = new Dictionary<int, Gtrx>();
using (StreamReader sr = File.OpenText(filename))
{
  while ((s = sr.ReadLine()) != null)
  {
    if (s.Contains("ADD GTRX:"))
    {
      int cellID = int.Parse(PullValue(s, "CELLID"));
      Gtrx gtrx;
      if(gtrxDict.TryGetValue(cellID, out gtrx)) // Found previous one
        gtrx.DEFINED_TCH_FRQ += "," + int.Parse(PullValue(ss, "FREQ"));
      else // First one for this ID, so create a new object
        gtrxDict[cellID] = new Gtrx
        {
          CellId = cellID,
          Freq = int.Parse(PullValue(s, "FREQ")),
          IsMainBcch = PullValue(s, "ISMAINBCCH").ToUpper() == "YES",
          Commabcch = new List<string> { PullValue(s, "ISMAINBCCH") },
          DEFINED_TCH_FRQ = int.Parse(PullValue(ss, "FREQ")).ToString(),
          TrxName = PullValue(s, "TRXNAME"),
        };
    }
  }
}
通过这种方式,我们根本不需要在内存中保存文件中的多行,更不用说重复这样做了。运行完之后,gtrxDict将为文件中的每个不同的CELLID包含一个Gtrx对象,DEFINED_TCH_FRQ作为每个匹配行的逗号分隔值列表。

下面的代码片段可用于读取整个文本文件:

using System.IO;
/// Read Text Document specified by full path
private string ReadTextDocument(string TextFilePath)
{
    string _text = String.Empty;
    try
    {
        // open file if exists
        if (File.Exists(TextFilePath))
        {
            using (StreamReader reader = new StreamReader(TextFilePath))
            {
                _text = reader.ReadToEnd();
                reader.Close();
            }
        }
        else
        {
            throw new FileNotFoundException();
        }
        return _text;
    }
    catch { throw; }
}

获取内存字符串,然后应用Split()函数创建string[],并以与原始文本文件中的行相同的方式处理数组元素。在处理非常大的文件的情况下,该方法提供了按数据块读取它的选项,处理它们,然后在完成后处理(re: https://msdn.microsoft.com/en-us/library/system.io.streamreader%28v=vs.110%29.aspx)。

正如@Michael Liu在评论中提到的,还有另一个使用File.ReadAllText()的选择,它提供了更紧凑的解决方案,可以代替reader.ReadToEnd()使用。File类的其他有用方法详见:https://msdn.microsoft.com/en-us/library/system.io.file%28v=vs.110%29.aspx

最后,FileStream类可以用于不同粒度级别的文件读写操作(re: https://msdn.microsoft.com/en-us/library/system.io.filestream%28v=vs.110%29.aspx)。

总结

为了回应有趣的评论线程,这里有一个简短的总结。

与PO问题中描述的过程相关的最大瓶颈是磁盘IO操作。这里有一些数字:在高质量的硬盘中,平均寻道时间大约是5毫秒加上实际的读取时间(每行)。很可能整个内存文件数据处理比单个HDD IO读取花费的时间更少(有时显着;顺便说一句,SSD工作得更好,但仍然不能与DDR3 RAM相匹配)。现代个人电脑的RAM内存大小相当显著(通常为4…8)GB RAM足以处理大多数文本文件)。因此,我的解决方案的核心思想是最小化磁盘IO读取操作,并在内存中完成整个文件数据处理。显然,实现可以有所不同。

希望这能有所帮助。最好的祝福,

我认为这或多或少能得到你想要的。

首先读取所有数据:

var data =
(
    from s in File.ReadLines(filename)
    where s != null
    where s.Contains("ADD GTRX:")
    select new Gtrx
    {
        CellId = int.Parse(PullValue(s, "CELLID")),
        Freq = int.Parse(PullValue(s, "FREQ")),
        //TrxNo = int.Parse(PullValue(s, "TRXNO")),
        IsMainBcch = PullValue(s, "ISMAINBCCH").ToUpper() == "YES",
        Commabcch = new List<string> { PullValue(s, "ISMAINBCCH") },
        DEFINED_TCH_FRQ = null,
        TrxName = PullValue(s, "TRXNAME"),
    }
).ToArray();

根据加载的数据创建一个查找以返回基于每个单元格id的频率:

var lookup =
    data
        .Where(d => !d.IsMainBcch)
        .ToLookup(d => d.CellId, d => d.Freq);

现在基于查找更新DEFINED_TCH_FRQ:

foreach (var d in data)
{
    d.DEFINED_TCH_FRQ = String.Join(",", lookup[d.CellId]);
}

相关内容

  • 没有找到相关文章

最新更新