【问题标题】:Reading large files backwards (from end to start) in C# [duplicate]在 C# 中向后(从头到尾)读取大文件 [重复]
【发布时间】:2019-12-27 10:37:29
【问题描述】:

我有一个包含一系列价格数据的文本文件。 该问题可能与历史数据的任何长期历史记录相匹配,例如温度、空气湿度、价格、日志文件……

我的历史文件的头部如下所示:

如果我想读取和处理一个内存太大的文件,我通常会选择以下代码:

using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
    string line;
    while ((line = sr.ReadLine()) != null)
    {
        // Process Data
    }
}

在我的例子中,每 1000 毫秒创建一条记录。最新数据位于文件末尾。 尝试处理最新数据时会出现此问题。

示例:
我想生成过去 30 天的平均值。
从文件末尾开始并朝着开头移动直到达到 X 天阈值是最有效的。 上面的示例代码将读取在这种情况下几乎无法使用的整个文件。每次我需要更新最近的数据指标时都是最坏的情况。 这个问题当然适用于您要处理最后 x 个元素的任何操作。

是否有从头到尾读取文件的功能?

【问题讨论】:

    标签: c# file bigdata streamreader


    【解决方案1】:

    试试下面的代码。最后一行可以为空。不确定处理最后一行空白的最佳方法。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    
    namespace GetFileReverse
    {
        class Program
        {
            const string FILENAME = @"c:\temp\test.txt";
            static void Main(string[] args)
            {
                GetFileReverse getFileReverse = new GetFileReverse(FILENAME);
                string line = "";
                while ((line = getFileReverse.ReadLine()) != null)
                {
                    Console.WriteLine(line);
                }
            }
        }
        public class GetFileReverse : IDisposable
        {
            const int BUFFER_SIZE = 1024;
            private FileStream stream { get; set; }
            private string data { get; set; }
            public Boolean SOF { get; set; }
            private long position { get; set; }
            public GetFileReverse(string filename)
            {
                stream = File.OpenRead(filename);
                if (stream != null)
                {
                    position = stream.Seek(0, SeekOrigin.End);
                    SOF = false;
                    data = string.Empty;
                }
                else
                {
                    SOF = true;
                }
            }
            private byte[] ReadStream()
            {
                byte[] bytes = null;
                int size = BUFFER_SIZE;
                if (position != 0)
                {
                    bytes = new byte[BUFFER_SIZE];
                    long oldPosition = position;
                    if (position >= BUFFER_SIZE)
                    {
                        position = stream.Seek(-1 * BUFFER_SIZE, SeekOrigin.Current);
                    }
                    else
                    {
                        position = stream.Seek(-1 * position, SeekOrigin.Current);
                        size = (int)(oldPosition - position);
                        bytes = new byte[size];
                    }
                    stream.Read(bytes, 0, size);
                    stream.Seek(-1 * size, SeekOrigin.Current);
                }
                return bytes;
    
            }
            public string ReadLine()
            {
                string line = "";
                while (!SOF && (!data.Contains("\r\n")))
                {
                    byte[] bytes = ReadStream();
                    if (bytes != null)
                    {
                        string temp = Encoding.UTF8.GetString(bytes);
                        data = data.Insert(0, temp);
                    }
                    SOF = position == 0;
                }
    
    
                int lastReturn = data.LastIndexOf("\r\n");
                if (lastReturn == -1)
                {
                    if (data.Length > 0)
                    {
                        line = data;
                        data = string.Empty;
                    }
                    else
                    {
                        line = null;
                    }
                }
                else
                {
                    line = data.Substring(lastReturn + 2);
                    data = data.Remove(lastReturn);
                }
    
                return line;
            }
            public void Close()
            {
                stream.Close();
            }
            public void Dispose()
            {
                stream.Dispose();
                data = string.Empty;
                position = -1;
            }
        }
    }
    

    【讨论】:

    • 谢谢你。它适用于添加 if(line == "") continue; 但不幸的是,即使我调用 getFileReverse.Close()getFileReverse.Dispose() 文件似乎被锁定
    • 你在使用我最新的代码吗?在我最初发布后做了一些改进。如果代码挂起,则使用 VS 菜单 Debug : BreakAll 查找挂起的位置。如果您的文件没有两个“\r\n”,或者编码是 unicode,则该代码可能不起作用。我做了很多调试。我可能没有正确处理边界条件,例如大于而不是大于和等于(或小于)。
    • 在 using 语句中处理包装可能会更好地处理: using (GetFileReverse getFileReverse = new GetFileReverse(FILENAME)){ enter code here}
    • 嘿,谢谢你的代码。这很棒!不幸的是,我的问题被标记为已回答,尽管其他线程没有为我的问题提供有效的答案。
    【解决方案2】:

    您可以使用 Seek 转到文件末尾,但是您需要“猜测”或计算距离末尾还有多远...例如读取最后的 1024字节:

        stream.Seek(-1024, SeekOrigin.End);
    

    只要计算出最后 30 行最多可以有多少字节,然后在文件末尾之前搜索到那个位置,然后只读取文件的那部分

    【讨论】:

    • 如果我理解正确,我会先 Filestream fs = ... 然后 fs.Seek(x) 然后像往常一样 sr.ReadLine() ?
    • 确实如此,但正如我提到的,您需要计算正确的值以传递给 Seek() 以到达您想要开始阅读的地方
    猜你喜欢
    • 2013-03-27
    • 1970-01-01
    • 2011-10-27
    • 2013-08-17
    • 1970-01-01
    • 2011-09-21
    • 2011-08-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多