如何在C#中从文本文件中删除一行?

62

我有一个问题:如何在C#中从文本文件中删除一行?


他/她的意思是如何通过编程删除它,我猜。 - splattne
11个回答

108

对于非常大的文件,我会采用以下方法

string tempFile = Path.GetTempFileName();

using(var sr = new StreamReader("file.txt"))
using(var sw = new StreamWriter(tempFile))
{
    string line;

    while((line = sr.ReadLine()) != null)
    {
         if(line != "removeme")
             sw.WriteLine(line);
    }
}

File.Delete("file.txt");
File.Move(tempFile, "file.txt");

更新我最初在2009年写过这篇文章,现在我认为通过使用LINQ和延迟执行可以实现上述功能。

var tempFile = Path.GetTempFileName();
var linesToKeep = File.ReadLines(fileName).Where(l => l != "removeme");

File.WriteAllLines(tempFile, linesToKeep);

File.Delete(fileName);
File.Move(tempFile, fileName);

上面的代码与第一个示例几乎完全相同,逐行读取并在内存中保留最少量的数据。

不过需要说明的是,由于我们在讨论文本文件,所以你很少需要使用磁盘作为中间存储介质。如果你没有处理非常大的日志文件,那么将内容读入内存而不必处理临时文件应该没有问题。

File.WriteAllLines(fileName, 
    File.ReadLines(fileName).Where(l => l != "removeme").ToList());

请注意,这里的.ToList是至关重要的,可以强制立即执行。还要注意,所有的示例都假定文本文件是UTF-8编码。


34

读取文件,将行从内存中删除并将内容放回文件(覆盖)。如果文件很大,您可能希望逐行读取文件,并创建一个临时文件,稍后替换原始文件。


21

我同意John Saunders的观点,这并不是真正特定于C#的。然而,为了回答你的问题:你基本上需要重写文件。有两种方法可以做到这一点。

  • 将整个文件读入内存(例如使用File.ReadAllLines
  • 删除有问题的行(在这种情况下,最简单的方法可能是将字符串数组转换为List<string>,然后删除该行)
  • 将所有其余行重新写入文件(例如使用File.WriteAllLines)- 可能需要使用ToArray再次将List<string>转换为字符串数组

这意味着你必须知道你有足够的内存。另一个选择:

  • 打开输入文件和一个新的输出文件(作为TextReader / TextWriter,例如使用File.OpenTextFile.CreateText
  • 读取一行(TextReader.ReadLine) - 如果你不想删除它,请将其写入输出文件(TextWriter.WriteLine
  • 当你读取完所有行时,关闭读取器和写入器(如果你对两者都使用using语句,这将自动发生)
  • 如果你想用输出替换输入,请先删除输入文件,然后将输出文件移动到该位置。

我也遇到了同样的情况,需要删除文件头并将其附加到另一个文件中。在Windows上,如果我通过C#使用DOS函数,您认为这会提高性能吗?以下是命令...
MORE +1 "sourcefilepath" > "targetFilePath"
- Imran Amjad
@ImranAmjad:我不知道,但这似乎不难测试。这是你的工作流程中特别关键的性能部分吗? - Jon Skeet
是的,文件大小可以超过半GB,而且我经常需要这样做。使用DOS命令来完成所有工作可以减轻负担,我只确定它比其他操作系统层更快。我对内部机制不太确定。这也可以节省很多代码行,但代码可读性会受到一些人的影响。 - Imran Amjad
@ImranAmjad:半个G并不是很长。与其确定不如试一下?保存代码听起来是件好事,但如果速度对你来说真的很重要,那么至少值得一试。 - Jon Skeet
每个文件都有半GB大小,我正在将许多这样的文件追加到一个巨大的文件中。我已经尝试过了,它运行得非常好。但我还没有进行基准测试。 - Imran Amjad

13
从文本文件中删除一个项目,首先将所有文本移动到列表中并删除您想要删除的任何项目。然后将存储在列表中的文本写入文本文件:
List<string> quotelist=File.ReadAllLines(filename).ToList();
string firstItem= quotelist[0];
quotelist.RemoveAt(0);
File.WriteAllLines(filename, quotelist.ToArray());
return firstItem;

1
那么对于“另一半”(好吧,另外的0.05%)没有ReadAllLines()或WriteAllLines()在他们的.NET版本中怎么办? - B. Clay Shannon-B. Crow Raven
这对我来说真的非常有效,完全符合我的期望。感谢您分享这个。 - Leo Ramadani
List<string> quotelist = new List<string>(File.ReadAllLines(filename)); - undefined
File.WriteAllLines(filename, quotelist.ToArray()); - undefined

7

我在马库斯·奥尔松的建议基础上进行了扩展,并编写了这个类,它添加了多个搜索字符串和一些事件:

public static class TextLineRemover
{
    public static void RemoveTextLines(IList<string> linesToRemove, string filename, string tempFilename)
    {
        // Initial values
        int lineNumber = 0;
        int linesRemoved = 0;
        DateTime startTime = DateTime.Now;

        // Read file
        using (var sr = new StreamReader(filename))
        {
            // Write new file
            using (var sw = new StreamWriter(tempFilename))
            {
                // Read lines
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    lineNumber++;
                    // Look for text to remove
                    if (!ContainsString(line, linesToRemove))
                    {
                        // Keep lines that does not match
                        sw.WriteLine(line);
                    }
                    else
                    {
                        // Ignore lines that DO match
                        linesRemoved++;
                        InvokeOnRemovedLine(new RemovedLineArgs { RemovedLine = line, RemovedLineNumber = lineNumber});
                    }
                }
            }
        }
        // Delete original file
        File.Delete(filename);

        // ... and put the temp file in its place.
        File.Move(tempFilename, filename);

        // Final calculations
        DateTime endTime = DateTime.Now;
        InvokeOnFinished(new FinishedArgs {LinesRemoved = linesRemoved, TotalLines = lineNumber, TotalTime = endTime.Subtract(startTime)});
    }

    private static bool ContainsString(string line, IEnumerable<string> linesToRemove)
    {
        foreach (var lineToRemove in linesToRemove)
        {
            if(line.Contains(lineToRemove))
                return true;
        }
        return false;
    }

    public static event RemovedLine OnRemovedLine;
    public static event Finished OnFinished;

    public static void InvokeOnFinished(FinishedArgs args)
    {
        Finished handler = OnFinished;
        if (handler != null) handler(null, args);
    }

    public static void InvokeOnRemovedLine(RemovedLineArgs args)
    {
        RemovedLine handler = OnRemovedLine;
        if (handler != null) handler(null, args);
    }
}

public delegate void Finished(object sender, FinishedArgs args);

public class FinishedArgs
{
    public int TotalLines { get; set; }
    public int LinesRemoved { get; set; }
    public TimeSpan TotalTime { get; set; }
}

public delegate void RemovedLine(object sender, RemovedLineArgs args);

public class RemovedLineArgs
{
    public string RemovedLine { get; set; }
    public int RemovedLineNumber { get; set; }
}

使用方法:

TextLineRemover.OnRemovedLine += (o, removedLineArgs) => Console.WriteLine(string.Format("Removed \"{0}\" at line {1}", removedLineArgs.RemovedLine, removedLineArgs.RemovedLineNumber));
TextLineRemover.OnFinished += (o, finishedArgs) => Console.WriteLine(string.Format("{0} of {1} lines removed. Time used: {2}", finishedArgs.LinesRemoved, finishedArgs.TotalLines, finishedArgs.TotalTime.ToString()));
TextLineRemover.RemoveTextLines(new List<string> { "aaa", "bbb" }, fileName, fileName + ".tmp");

4
我写了一个方法来删除文件中的行。
这个程序使用了using System.IO
看看我的代码:
void File_DeleteLine(int Line, string Path)
{
    StringBuilder sb = new StringBuilder();
    using (StreamReader sr = new StreamReader(Path))
    {
        int Countup = 0;
        while (!sr.EndOfStream)
        {
            Countup++;
            if (Countup != Line)
            {
                using (StringWriter sw = new StringWriter(sb))
                {
                    sw.WriteLine(sr.ReadLine());
                }
            }
            else
            {
                sr.ReadLine();
            }
        }
    }
    using (StreamWriter sw = new StreamWriter(Path))
    {
        sw.Write(sb.ToString());
    }
}

3
我会尽可能简单地解释:
  • 打开文件以进行读写操作
  • 查找并定位到要删除的行的起始位置
  • 将写指针设置为当前的读指针
  • 读取直到要删除的行末尾,跳过换行符(同时计算字符数,我们称之为“nline”)
  • 逐字节读取并将每个字节写入文件
  • 完成后将文件截断为(原始长度-nline)。

2

我知道已经过了很长时间,但这对我非常有帮助,所以我想改进一下。来自Leonhard的答案对我非常有用。然而,如果您的文本文件像我的一样非常大,使用StringBuilder会导致OutOfMemory错误。因此,我是这样使用的:创建一个新文件并写入它。如果您愿意,可以删除第一个文件。请注意,Line变量确定将删除哪些行之前的文件。您可以修改它以指定要删除的行间隔。

void File_DeleteLine(int Line, string Path, string newPath)
{
    StreamReader reader = new StreamReader(Path);
    StreamWriter writer = new StreamWriter(newPath);
    int Countup = 0;
    while (!reader.EndOfStream)
    {
        Countup++;
        if (Countup > Line)
        {

            writer.WriteLine(reader.ReadLine());

        }
        else
        {
            reader.ReadLine();
        }
    }
}

1
string fileIN = @"C:\myTextFile.txt";
string fileOUT = @"C:\myTextFile_Out.txt";
if (File.Exists(fileIN))
{
    string[] data = File.ReadAllLines(fileIN);
    foreach (string line in data)
        if (!line.Equals("my line to remove"))
            File.AppendAllText(fileOUT, line);
    File.Delete(fileIN);
    File.Move(fileOUT, fileIN);
}

0

从多个文件中删除一段代码块

为了补充@Markus Olsson的答案,我需要从多个文件中删除一段代码块。在核心项目中,我遇到了瑞典字符的问题,因此我需要安装System.Text.CodePagesEncodingProvider nuget包,并使用System.Text.Encoding.GetEncoding(1252)而不是System.Text.Encoding.UTF8。

    public static void Main(string[] args)
    {
        try
        {
            var dir = @"C:\Test";
            //Get all html and htm files
            var files = DirSearch(dir);
            foreach (var file in files)
            {
                RmCode(file);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
            throw;
        }

    }

    private static void RmCode(string file)
    {
        string tempFile = Path.GetTempFileName();

        using (var sr = new StreamReader(file, Encoding.UTF8))
        using (var sw = new StreamWriter(new FileStream(tempFile, FileMode.Open, FileAccess.ReadWrite), Encoding.UTF8))
        {
            string line;

            var startOfBadCode = "<div>";
            var endOfBadCode = "</div>";
            var deleteLine = false;

            while ((line = sr.ReadLine()) != null)
            {
                if (line.Contains(startOfBadCode))
                {
                    deleteLine = true;
                }
                if (!deleteLine)
                {
                    sw.WriteLine(line);
                }

                if (line.Contains(endOfBadCode))
                {
                    deleteLine = false;
                }
            }
        }

        File.Delete(file);
        File.Move(tempFile, file);
    }

    private static List<String> DirSearch(string sDir)
    {
        List<String> files = new List<String>();
        try
        {
            foreach (string f in Directory.GetFiles(sDir))
            {
                files.Add(f);
            }
            foreach (string d in Directory.GetDirectories(sDir))
            {
                files.AddRange(DirSearch(d));
            }
        }
        catch (System.Exception excpt)
        {
            Console.WriteLine(excpt.Message);
        }

        return files.Where(s => s.EndsWith(".htm") || s.EndsWith(".html")).ToList();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接