从文件中删除包含多个字符串的行的最有效方法?

时间:2013-03-11 15:59:54

标签: c#

我想找到一种在读取文件(主机文件)时删除字符串1和字符串2的最有效方法,并删除包含字符串1或字符串2的整行。

目前我有,而且显然很迟钝。还有哪些更好的方法?

using(StreamReader sr = File.OpenText(path)){
    while ((stringToRemove = sr.ReadLine()) != null)
    {
        if (!stringToRemove.Contains("string1"))
        {
            if (!stringToRemove.Contains("string2"))
            {
                emptyreplace += stringToRemove + Environment.NewLine;
            }
        }
    }
    sr.Close();
    File.WriteAllText(path, emptyreplace);
    hostFileConfigured = false;
    UInt32 result = DnsFlushResolverCache();
    MessageBox.Show(removeSuccess, windowOffline);
}

6 个答案:

答案 0 :(得分:3)

您遇到的主要问题是您经常使用大型常规字符串并将数据附加到最后。这是每次重新创建字符串并消耗大量时间,特别是内存。通过使用string.Join,它将避免创建(非常大量)中间字符串值。

您还可以使用File.ReadLines缩短代码以获取文本行,而不是直接使用流。它不是更好或更糟,只是更漂亮。

var lines = File.ReadLines(path)
    .Where(line => !line.Contains("string1") && !line.Contains("string2"));

File.WriteAllText(path, string.Join(Environment.NewLine, lines));

另一种选择是流式传输输出。由于没有好的库方法来写出IEnumerable<string>而没有急切地评估输入,我们必须编写自己的(这很简单):

public static void WriteLines(string path, IEnumerable<string> lines)
{
    using (var stream = File.CreateText(path))
    {
        foreach (var line in lines)
            stream.WriteLine(line);
    }
}

另请注意,如果我们输出输出,那么我们需要一个临时文件,因为我们不想同时读取和写入同一个文件。

//same code as before
var lines = File.ReadLines(path)
    .Where(line => !line.Contains("string1") && !line.Contains("string2"));

//get a temp file path that won't conflict with any other files
string tempPath = Path.GetTempFileName();
//use the method from above to write the lines to the temp file
WriteLines(tempPath, lines);
//rename the temp file to the real file we want to replace, 
//both deleting the temp file and the old file at the same time
File.Move(tempPath, path);

与第一个选项相反,此选项的主要优点是它将消耗更少的内存。实际上,它一次只需要在内存中保存文件的行,而不是整个文件。它确实占用了磁盘上的一些额外空间(暂时)。

答案 1 :(得分:1)

向我发光的第一件事是,在while循环(string)中使用emptyreplace类型变量是错误的(不高效),使用StrinBuilder类型,它将是<强大>多内存效率高。

例如:

 StringBuilder emptyreplace = new StringBuilder(); 

using(StreamReader sr = File.OpenText(path)){
    while ((stringToRemove = sr.ReadLine()) != null)
    {
        if (!stringToRemove.Contains("string1"))
        {
            if (!stringToRemove.Contains("string2"))
            {
                //USE StringBuilder.Append, and NOT string concatenation
                emptyreplace.AppendLine(stringToRemove + Environment.NewLine);
            }
        }
    }
   ...
}

其余似乎足够好。

答案 2 :(得分:0)

两个建议:

  1. 创建要检测的字符串数组(我将其称为stopWords)并使用Linq的Any扩展方法。

  2. 不是一次性构建文件并将其全部写入,而是在读取源文件时将每一行写入输出文件,并在完成后替换源文件。

  3. 结果代码:

    string[] stopWords = new string[]
    {
        "string1",
        "string2"
    }
    
    using(StreamReader sr = File.OpenText(srcPath))
    using(StreamWriter sw = new StreamWriter(outPath))
    {
        while ((stringToRemove = sr.ReadLine()) != null)
        {
            if (!stopWords.Any(s => stringToRemove.Contains(s))
            {
                sw.WriteLine(stringToRemove);
            }
        }
    }
    
    File.Move(outPath, srcPath);
    

答案 3 :(得分:0)

有很多方法可以改善这一点:

  • 将您正在搜索的单词数组编译为正则表达式(例如,word1|word2;注意特殊字符),这样您只需要遍历字符串一次。 (这也允许您使用\b仅匹配单词)

  • 通过StreamWriter将每一行写入新文件,以便在构建时不需要将整个内容存储在内存中。 (完成后,删除原始文件并重命名新文件)

答案 4 :(得分:0)

您的主机文件是否真的那么大,您需要一行一行地阅读它?为什么不简单地这样做?

var lines = File.ReadAllLines(path);
var lines = lines.Where(x => !badWords.Any(y => x.Contains(y))).ToArray();
File.WriteAllLines(path, lines);

答案 5 :(得分:0)

更新:我刚刚意识到你实际上是在谈论“主机文件”。假设您的意思是%windir%\system32\drivers\etc\hosts,则此文件的大小不可能非常大(例如超过几KB)。所以个人而言,我会选择最具可读性的方法。例如,像@servy那样的那个。

最后,您必须阅读每一行并写下与您的标准不符的每一行。因此,您将始终拥有无法避免的基本IO开销。取决于文件的实际(平均)大小,这可能会掩盖您在代码中使用的所有其他优化技术,以实际过滤行。

说到这里,你可以通过不在缓冲区中收集所有输出行,而是在读取它们时直接将它们写入输出文件,从而在内存方面浪费少一点(同样,这可能如果文件不是很大,那就毫无意义了。)

using (var reader = new StreamReader(inputfile))
{
  using (var writer = new StreamWriter(outputfile))
  {
    string line;
    while ((line = reader.ReadLine()) != null)
    {
       if (line.IndexOf("string1") == -1 && line.IndexOf("string2") == -1)
       {
          writer.WriteLine(line);
       }
    }
  }
}

File.Move(outputFile, inputFile);