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

时间:2015-06-09 22:49:01

标签: c# .net linq

我有一个文本文件包含以下类似的行,例如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值,那么我使用{ {1}}但这需要很长时间。有没有更好的方法来做到这一点?如果我为ISMAINBCCH = YES取FREQ值,则连接值ISMAINBCCH = NO在上下10行的范围内,但我不知道如何实现它。可能我应该获得ISMAINBCCH=YES FREQ的当前行。以下是我到目前为止所做的代码

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

3 个答案:

答案 0 :(得分:1)

以下代码段可用于读取整个文本文件:

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操作。以下是一些数字:优质HDD的平均寻道时间约为5毫秒加上实际读取时间(每行)。很可能整个内存文件数据处理所花费的时间少于单个HDD IO读取(有时显着;顺便说一下,SSD工作得更好但仍然不能与DDR3 RAM匹配)。现代PC的RAM内存大小相当大(通常4 ... 8 GB RAM足以处理大多数文本文件)。因此,我的解决方案的核心思想是最小化磁盘IO读取操作并在内存中执行整个文件数据处理。显然,实施可能会有所不同。

希望这可能会有所帮助。最好的问候,

答案 1 :(得分:1)

from ss in File.ReadLines(filename)

这将读取整个文件,生成一个数组,然后在循环中使用该数组(本身来自读取同一文件),以便抛弃该数组然后再次创建。如果在此期间没有更改,您将同时读取相同的文件number_of_lines + 1次。

因此,显而易见的提升只需调用File.ReadLines(filename)一次,存储数组然后将该数组用于循环而不是while ((s = sr.ReadLine()) != null)并在循环中而不是重复调用{{ 1}}。

但是,即使反复看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的{​​{1}}对象,其中Gtrx为每个匹配行的值的逗号分隔列表。

答案 2 :(得分:0)

我认为这或多或少会让你得到你想要的东西。

首先阅读所有数据:

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