regex 如何在C#中使用正则表达式查找和替换较大文件(150 MB-250 MB)中的文本?

yrwegjxp  于 2023-03-13  发布在  C#
关注(0)|答案(3)|浏览(197)

我正在处理大小介于150 MB和250 MB之间的文件,我需要在匹配集合中找到的每个匹配项后附加一个换页(/f)字符。目前,每个匹配项的正则表达式如下:

Regex myreg = new Regex("ABC: DEF11-1111(.*?)MORE DATA(.*?)EVEN MORE DATA(.*?)\f", RegexOptions.Singleline);

并且我想修改文件中的每个匹配项(然后覆盖文件),使其成为以后可以使用更短的正则表达式找到的内容:

Regex myreg = new Regex("ABC: DEF11-1111(.*?)\f\f, RegexOptions.Singleline);

换句话说,我只想向在文件中找到的每个匹配项附加一个换页字符(\f)并保存它。
我看过很多关于替换文本的堆栈溢出的例子,但对于更大的文件就不多了。典型的例子包括:

  • 使用streamreader将整个文件存储在一个字符串中,然后在该字符串中执行查找和替换。
  • 将MatchCollection与File.ReadAllText()结合使用
  • 逐行阅读文件并查找匹配项。

前两个选项的问题是它会消耗大量的内存,我担心程序是否能够处理所有这些。第三个选项的问题是我的正则表达式跨越了许多行,因此不会在一行中找到。我也看到了其他帖子,但它们涉及替换特定的文本字符串,而不是使用正则表达式。
对于我来说,将换页字符附加到文件中找到的每个匹配项,然后保存该文件的好方法是什么?

编辑:

根据一些建议,我尝试了一下StreamReader.ReadLine()。具体来说,我将读取一行,看看它是否与我的表达式匹配,然后基于该结果写入文件。如果它与表达式匹配,我将写入文件。如果它与表达式不匹配,我将把它附加到字符串,直到它与表达式匹配为止。如下所示:
正则表达式myreg =新正则表达式(“ABC:DEF 11 -1111(.?)更多数据(.?)甚至更多数据(.*?)\f”,正则表达式选项,单行);

//For storing/comparing our match.
string line, buildingmatch, match, whatremains;
buildingmatch = "";
match = "";
whatremains = "";

//For keep track of trailing bits after our match.
int matchlength = 0;

using (StreamWriter sw = new StreamWriter(destFile))
using (StreamReader sr = new StreamReader(srcFile))
{
    //While we are still reading lines in the file...
    while ((line = sr.ReadLine()) != null)
    {
        //Keep adding lines to buildingmatch until we can match the regular expression.
        buildingmatch = buildingmatch + line + "\r\n";
        if (myreg.IsMatch(buildingmatch)
        {
            match = myreg.Match(buildingmatch).Value;
            matchlength = match.Lengh;
            
            //Make sure we are not at the end of the file.
            if (matchlength < buildingmatch.Length)
            {
                whatremains = buildingmatch.SubString(matchlength, buildingmatch.Length - matchlength);
            }
            
            sw.Write(match, + "\f\f");
            buildingmatch = whatremains;
            whatremains = "";
        }
    }
}

问题是,这花了大约55分钟来运行一个大约150 MB的文件。必须有更好的方法来做到这一点...

qco9c6ql

qco9c6ql1#

如果您可以将整个字符串数据加载到单个字符串变量中,则无需首先进行匹配,然后在循环中将文本追加到匹配项。您可以使用单个Regex.Replace操作:

string text = File.ReadAllText(srcFile);
using (StreamWriter sw = new StreamWriter(destfile, false, Encoding.UTF8, 5242880))
{
     sw.Write(myregex.Replace(text, "$&\f\f"));
}
  • 详细信息 *:
  • string text = File.ReadAllText(srcFile);-将srcFile文件读取到text变量(match可能会混淆)
  • myregex.Replace(text, "$&\f\f")-将所有出现的myregex匹配项替换为它们自己($&是对整个匹配值的反向引用),同时在每个匹配项后立即附加两个\f字符。
z31licg0

z31licg02#

我能够在合理的时间内找到一个有效的解决方案;它可以在5分钟内处理我的整个150 MB文件。
首先,正如在注解中提到的,在每次迭代之后将字符串与正则表达式进行比较是一种浪费,相反,我从以下内容开始:

string match = File.ReadAllText(srcFile);
MatchCollection mymatches = myregex.Matches(match);

字符串最多可以容纳2GB的数据,所以虽然不是很理想,但我认为大约150 MB的数据存储在字符串中不会有什么损失。这样,与从文件中读取x行时检查一个匹配项不同,我可以一次检查文件中的所有匹配项!
接下来,我使用了这个:

StringBuilder matchsb = new StringBuilder(134217728);
foreach (Match m in mymatches)
{
     matchsb.Append(m.Value + "\f\f");
}

因为我已经知道了文件的大小,我可以继续初始化我的stringbuilder。更不用说,如果你在一个字符串上做多个操作(我就是这样做的),使用string builder效率会高得多。从那里开始,只需要把表单提要附加到我的每个匹配项上。
最后,性能成本最高的部分:

using (StreamWriter sw = new StreamWriter(destfile, false, Encoding.UTF8, 5242880))
{
     sw.Write(matchsb.ToString());
}

初始化StreamWriter的方式很关键。通常,您只需将其声明为:

StreamWriter sw = new StreamWriter(destfile);

这对于大多数用例来说都很好,但是当你处理更大的文件时,这个问题就变得很明显了。当你这样声明的时候,你是在用一个4KB的默认缓冲区写文件。对于一个更小的文件,这是很好的。但是对于150 MB的文件?这将花费很长的时间。所以我通过将缓冲区改为大约5 MB来纠正这个问题。
我发现这个资源确实帮助我理解了如何更有效地写入文件:https://www.jeremyshanks.com/fastest-way-to-write-text-files-to-disk-in-c/
希望这对下一个人也有帮助。

2jcobegt

2jcobegt3#

在C#中处理大型文本文件并需要执行搜索和替换操作时,可以考虑使用几种方法来优化性能。
一种方法是使用内存Map文件,内存Map文件允许您访问大型文件,就好像它们是内存数组一样,这比使用标准文件I/O更有效。要使用内存Map文件,可以使用C#中的MemoryMappedFile类。
如果内存Map文件是一个可行的选择,那么它们可以提供比传统阅读方法更快的文件内容访问。

相关问题