📜  文件的最后 N 行 - C# (1)

📅  最后修改于: 2023-12-03 15:40:04.647000             🧑  作者: Mango

文件的最后 N 行 - C#

在程序中常常需要读取文件的最后几行数据,本文将介绍如何使用 C# 实现这一功能。

读取文本文件的最后 N 行
方法一:使用 File.ReadLines() 方法
public static IEnumerable<string> ReadLastLines(string path, int numLines)
{
    var lines = File.ReadLines(path);
    return lines.Skip(Math.Max(0, lines.Count() - numLines));
}

使用 File.ReadLines() 方法读取文本文件的所有行数据,然后使用 Skip() 方法跳过前面的行,保留最后 numLines 行,最后返回结果。该方法适用于文本文件较小的情况。

方法二:使用 StreamReader
public static IEnumerable<string> ReadLastLines(string path, int numLines)
{
    using (var reader = new StreamReader(path))
    {
        var lines = new Queue<string>();
        string line;

        while ((line = reader.ReadLine()) != null)
        {
            lines.Enqueue(line);
            if (lines.Count > numLines)
            {
                lines.Dequeue();
            }
        }

        return lines;
    }
}

使用 StreamReader 类从指定路径读取文本文件的内容,通过一个 Queue<string> 保存读取的所有行数据,当 Queue 中元素的数量大于 numLines 时,则弹出队首元素。最后返回队列中剩余的所有元素。该方法适用于文本文件较大的情况,因为 StreamReader 类可以一次只读取一行数据,不需要一次性将整个文件读入内存。

读取二进制文件的最后 N 行
方法一:使用 MemoryMappedFile
public static IEnumerable<string> ReadLastLines(string path, int numLines)
{
    using (var mmf = MemoryMappedFile.CreateFromFile(path, FileMode.Open))
    {
        var fileSize = new FileInfo(path).Length;
        var buffer = mmf.CreateViewAccessor(fileSize - numLines, numLines).ReadArray<byte>(0, numLines);
        var lines = Encoding.Default.GetString(buffer).Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
        return lines;
    }
}

使用 MemoryMappedFile 类从指定路径读取二进制文件的内容,通过 CreateViewAccessor() 方法读取文件指定区域的数据,然后转换为字符串并使用 Split() 方法分割为多行。最后返回结果。该方法适用于二进制文件较小的情况。

方法二:使用自定义缓冲区
public static IEnumerable<string> ReadLastLines(string path, int numLines)
{
    var bufferSize = numLines * 100; // 估计每行平均字符数为100
    var buffer = new byte[bufferSize];
    long position = 0;
    var lines = new List<string>();

    using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    {
        position = Math.Max(0, stream.Length - bufferSize);
        stream.Position = position;
        int bytesRead = 0;

        do
        {
            bytesRead = stream.Read(buffer, 0, buffer.Length);
            for (int i = bytesRead - 1; i >= 0; i--)
            {
                if (buffer[i] == 0x0a) // 找到换行符
                {
                    var line = Encoding.Default.GetString(buffer, i + 1, bytesRead - i - 1);
                    lines.Insert(0, line);
                    if (lines.Count >= numLines)
                    {
                        break;
                    }
                }
            }
        } while (bytesRead == buffer.Length && lines.Count < numLines);
    }

    if (lines.Count < numLines) // 如果文件总行数少于要读取的行数
    {
        var allLines = new List<string>(File.ReadAllLines(path));
        lines.InsertRange(0, allLines.Skip(Math.Max(0, allLines.Count - numLines)));
    }

    return lines;
}

使用自定义缓冲区的方式读取文件的最后 numLines 行数据,需要先估算每行数据的平均长度,根据需要读取的行数计算需要读取的字节数。从指定位置开始读取文件,每次读取缓冲区大小的数据,然后从后往前扫描,找到换行符并将其后面的字符解码为字符串,插入到行列表的最前面,直到列表中的行数达到要求。如果文件总行数少于要读取的行数,则使用 File.ReadAllLines() 方法读取文件的所有内容,并从后往前跳过指定行数的行数据,最后返回结果。该方法适用于二进制文件较大的情况,因为它可以一次读取指定大小的数据而不必一次性将整个文件读入内存。

总结

通过本文的介绍,我们可以了解到如何使用 C# 读取文件的最后几行数据。在处理二进制文件时,内存映射文件和自定义缓冲区的方式可以有效地避免一次性将整个文件读入内存的问题,从而提高程序的性能和可靠性。