10

问题:非常非常大的文件,我需要逐行解析以从每行获取 3 个值。一切正常,但解析整个文件需要很长时间。有可能在几秒钟内做到这一点吗?其花费的典型时间在 1 分钟到 2 分钟之间。

示例文件大小为 148,208KB

我正在使用正则表达式来解析每一行:

这是我的 C# 代码:

private static void ReadTheLines(int max, Responder rp, string inputFile)
{
    List<int> rate = new List<int>();
    double counter = 1;
    try
    {
        using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 1024))
        {
            string line;
            Console.WriteLine("Reading....");
            while ((line = sr.ReadLine()) != null)
            {
                if (counter <= max)
                {
                    counter++;
                    rate = rp.GetRateLine(line);
                }
                else if (max == 0)
                {
                    counter++;
                    rate = rp.GetRateLine(line);
                }
            }
            rp.GetRate(rate);
            Console.ReadLine();
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("The file could not be read:");
        Console.WriteLine(e.Message);
    }
}

这是我的正则表达式:

public List<int> GetRateLine(string justALine)
{
    const string reg = @"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$";
    Match match = Regex.Match(justALine, reg,
                                RegexOptions.IgnoreCase);

    // Here we check the Match instance.
    if (match.Success)
    {
        // Finally, we get the Group value and display it.

        string theRate = match.Groups[3].Value;
        Ratestorage.Add(Convert.ToInt32(theRate));
    }
    else
    {
        Ratestorage.Add(0);
    }
    return Ratestorage;
}

这是一个要解析的示例行,通常大约 200,000 行:

10.10.10.10 - - [2002 年 11 月 27 日:16:46:20 -0500] “GET /solr/ HTTP/1.1”200 4926 789

4

4 回答 4

16

内存映射文件任务并行库寻求帮助。

  1. 创建具有多个随机访问视图的持久 MMF。每个视图对应于文件的特定部分
  2. 定义带参数的解析方法 like IEnumerable<string>,基本上是为了抽象出一组未解析的行
  3. Parse(IEnumerable<string>)使用任务操作为每个 MMF 视图创建和启动一个 TPL 任务
  4. 每个worker任务将解析​​后的数据添加到BlockingCollection类型的共享队列中
  5. 另一个任务监听 BC ( GetConsumingEnumerable() ) 并处理所有已被工作任务解析的数据

请参阅MSDN 上的管道模式

必须说这个解决方案适用于.NET Framework >=4

于 2012-12-10T22:57:11.270 回答
5

现在,您Regex每次调用时都会重新创建您的GetRateLine,每次您阅读一行时都会发生这种情况。

如果您提前创建一个 Regex 实例,然后使用非静态Match方法,您将节省 regex 编译时间,这可能会提高您的速度。

话虽这么说,它可能不会让你从几分钟到几秒钟......

于 2012-12-10T22:56:15.783 回答
2

简而言之,我会尝试一些事情......

首先,将文件流缓冲区增加到至少 64kb:

using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 65536))

其次,构造一次正则表达式,而不是在循环内使用字符串:

static readonly Regex rateExpression = new Regex(@"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$", RegexOptions.IgnoreCase);
//In GetRateLine() change to:
Match match = rateExpression.Match(justALine);

第三,通过让 Responder.GetRate() 返回一个列表或数组来使用单个列表实例。

// replace: 'rp.GetRate(rate)', with:
rate = rp.GetRate();

我会将列表预先分配到“合理”的限制:

List<int> rate = new List<int>(10000);

如果可用且适用于您的特定需求,您还可以考虑将编码从 UTF-8 更改为 ASCII。

评论

通常,如果这确实需要缩短解析时间,您将需要构建一个标记器并完全跳过 Regex。由于您的输入格式看起来都是 ascii 并且相当简单,这应该很容易做到,但可能比正则表达式更脆弱。最后,您需要权衡和平衡对速度的需求与代码的可靠性和可维护性。

如果您需要一些手动解析示例,请查看此问题的答案

于 2012-12-10T23:52:20.927 回答
1

不要为每次调用重新创建一个正则表达式,GetRateLine而是提前创建它,将RegexOptions.Compiled选项传递给Regex(String,RegexOptions)构造函数。

您可能还想尝试将整个文件读入内存,但我怀疑这是您的瓶颈。从磁盘读取约 100MB 的数据应该不会花一分钟时间。

于 2012-12-10T23:03:44.490 回答