从StreamReader中删除最后x行

3
我需要在C#中从文件中读取除最后x行以外的所有内容到StreamReader中。 什么是最好的方法?
非常感谢!

2
从文件末尾读取x个换行符,然后从文件开头读取直到该位置。 - M.Babcock
您计划从文件中读取的记录是否具有某种统一性(如公共记录长度,除\n以外的任何其他内容)? - M.Babcock
3个回答

4

如果它是一个大文件,有可能只需定位到文件末尾,然后反向检查字节是否为 '\n' 字符吗?我知道存在 \n 和 \r\n。我编写了以下代码,并在一个相当简单的文件上进行了测试。您能否尝试将其应用于您所拥有的文件?我知道我的解决方案看起来很长,但我认为您会发现它比从开头读取并重写整个文件更快。

public static void Truncate(string file, int lines)
{
    using (FileStream fs = File.Open(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
    {
        fs.Position = fs.Length;

        // \n \r\n (both uses \n for lines)
        const int BUFFER_SIZE = 2048;

        // Start at the end until # lines have been encountered, record the position, then truncate the file
        long currentPosition = fs.Position;
        int linesProcessed = 0;

        byte[] buffer = new byte[BUFFER_SIZE];
        while (linesProcessed < linesToTruncate && currentPosition > 0)
        {
            int bytesRead = FillBuffer(buffer, fs);

            // We now have a buffer containing the later contents of the file
            for (int i = bytesRead - 1; i >= 0; i--)
            {
                 currentPosition--;
                 if (buffer[i] == '\n')
                 {
                     linesProcessed++;
                     if (linesProcessed == linesToTruncate)
                         break;
                 }
            }
        }

        // Truncate the file
        fs.SetLength(currentPosition);
    }
}

private static int FillBuffer(byte[] buffer, FileStream fs)
{
    if (fs.Position == 0)
        return 0;

    int bytesRead = 0;
    int currentByteOffset = 0;

    // Calculate how many bytes of the buffer can be filled (remember that we're going in reverse)
    long expectedBytesToRead = (fs.Position < buffer.Length) ? fs.Position : buffer.Length;
    fs.Position -= expectedBytesToRead;

    while (bytesRead < expectedBytesToRead)
    {
        bytesRead += fs.Read(buffer, currentByteOffset, buffer.Length - bytesRead);
        currentByteOffset += bytesRead;
    }

    // We have to reset the position again because we moved the reader forward;
    fs.Position -= bytesRead;
    return bytesRead;
}

既然你只计划删除文件末尾,重写整个文件似乎很浪费,特别是对于大文件和小的N而言。当然,有人可能会认为,如果想要删除所有行,从开头到结尾更加高效。


谢谢,我明天一早就会尝试,看起来它能够满足我的需求。 - John Griffiths
工作得非常好。感谢大家的帮助。 - John Griffiths

3

你不需要真正地读取一个StreamReader。实际上,对于你所要求的模式,你根本不需要使用StreamReader。System.IO.File有一个有用的静态方法'ReadLines',你可以利用它来代替:

IEnumerable<string> allBut = File.ReadLines(path).Reverse().Skip(5).Reverse();

之前那个有缺陷的版本,是为了回应评论而发布的。
List<string> allLines = File.ReadLines(path).ToList();
IEnumerable<string> allBut = allLines.Take(allLines.Count - 5);

你是提议将整个文件作为高性能的替代方案读取(ReadLines.Count will 会读取整个文件)吗? - M.Babcock
1
你说得对。我刚刚进行了几次定时测试。第二种方法始终更快。谢谢。我会更新我的答案,删除第一个选项。 - xcud
1
这将取决于文件长度。如果文件无法适应你的空闲内存,那么你就会出现问题。 - Andrew Savinykh
1
我们需要知道那个显然缺席的提问者是否打算将文件(减去最后5行)读入某个东西中,如果是这种情况,我们已经承担了内存需求,这是一个非常好的答案。或者他想逐行处理文件(当然不包括你的5艘船),在这种情况下,M.Babcock的原始评论是最正确的解决方案;通过向后读取设置标记,然后从前到后处理,直到达到标记。 - xcud
1
我相当怀疑你是否正在进行有效的测试。不质疑你是否在没有附加调试器的发布模式下运行等等,我首先会问你是否实际上具体化了查询结果?例如,allBut = File.ReadLines(path).Reverse().Skip(5).Reverse();什么也不做,直到你执行它(即,迭代它)。 - Anthony Pegram
显示剩余7条评论

3

假设你是在提及文件中的行,那么我认为这是一个文本文件。如果你只想获取行,可以将它们读入到一个字符串数组中,如下所示:

string[] lines = File.ReadAllLines(@"C:\test.txt");

如果您确实需要使用StreamReaders:

using (StreamReader reader = new StreamReader(@"C:\test.txt"))
        {
            while (!reader.EndOfStream)
            {
                Console.WriteLine(reader.ReadLine());
            }
        }

使用 StreamReader 应该足以推断出 OP 正在谈论文本。 - M.Babcock
我猜我不确定提问者是否知道他们需要一个StreamReader,还是只知道他们需要读取一个文件并快速在网上搜索并看到StreamReader出现。但是是的,你是正确的。 - BryanJ

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