【问题标题】:.NET C# - Random access in text files - no easy way?.NET C# - 文本文件中的随机访问 - 没有简单的方法?
【发布时间】:2010-09-20 21:46:07
【问题描述】:

我有一个文本文件,其中包含多个“记录”。每条记录都包含一个名称和一组数字作为数据。

我正在尝试构建一个类来读取文件,只显示所有记录的名称,然后允许用户选择他/她想要的记录数据。

第一次浏览文件时,我只读取标题名称,但我可以跟踪文件中标题所在的“位置”。在用户请求后,我需要随机访问文本文件以查找每条记录的开头。

我必须这样做,因为文件太大而无法在内存 (1GB+) 中完全读入应用程序的其他内存需求。

我尝试使用 .NET StreamReader 类来完成此操作(它提供了非常易于使用的“ReadLine”功能,但无法捕获文件的真实位置(BaseStream 属性中的位置是倾斜的)由于类使用的缓冲区)。

在 .NET 中有没有简单的方法可以做到这一点?

【问题讨论】:

    标签: c# text streamreader random-access


    【解决方案1】:

    提供了一些很好的答案,但我找不到可以在我非常简单的情况下工作的源代码。就在这里,希望它可以节省其他人我花在搜索上的时间。

    我所指的“非常简单的情况”是:文本编码是固定宽度的,并且整个文件的行结束字符都是相同的。这段代码在我的情况下运行良好(我正在解析一个日志文件,有时我必须在文件中查找,然后再返回。我实现了足够做我需要做的事情(例如:只有一个构造函数,并且只覆盖 ReadLine()),所以很可能您需要添加代码...但我认为这是一个合理的起点。

    public class PositionableStreamReader : StreamReader
    {
        public PositionableStreamReader(string path)
            :base(path)
            {}
    
        private int myLineEndingCharacterLength = Environment.NewLine.Length;
        public int LineEndingCharacterLength
        {
            get { return myLineEndingCharacterLength; }
            set { myLineEndingCharacterLength = value; }
        }
    
        public override string ReadLine()
        {
            string line = base.ReadLine();
            if (null != line)
                myStreamPosition += line.Length + myLineEndingCharacterLength;
            return line;
        }
    
        private long myStreamPosition = 0;
        public long Position
        {
            get { return myStreamPosition; }
            set
            {
                myStreamPosition = value;
                this.BaseStream.Position = value;
                this.DiscardBufferedData();
            }
        }
    }
    

    这是一个如何使用 PositionableStreamReader 的示例:

    PositionableStreamReader sr = new PositionableStreamReader("somepath.txt");
    
    // read some lines
    while (something)
        sr.ReadLine();
    
    // bookmark the current position
    long streamPosition = sr.Position;
    
    // read some lines
    while (something)
        sr.ReadLine();
    
    // go back to the bookmarked position
    sr.Position = streamPosition;
    
    // read some lines
    while (something)
        sr.ReadLine();
    

    【讨论】:

      【解决方案2】:

      FileStream 有 seek() 方法。

      【讨论】:

      • 当我们不知道去哪里寻找时,这没有用。
      • 也许我们使用了不同的随机访问定义。我(显然还有 Jason)认为它是指具有特定字节大小的记录文件,因此记录的开头是 (recnum - 1) * recsize
      • 更重要的是,OP 建议他们可以记录单个记录开始的流索引,因此在这种情况下知道到哪里寻找是一个已解决的问题。
      • @Jon: “我第一次浏览文件时,我只读取标题名称,但我可以跟踪文件中标题所在的“位置”。我需要随机访问在用户请求后查找每条记录开头的文本文件。”听起来我们知道去哪里找。
      • “由于类使用的缓冲区,BaseStream 属性中的位置偏斜”。听起来我们知道去哪里找。
      【解决方案3】:

      您可以使用 System.IO.FileStream 代替 StreamReader。如果您确切知道文件包含什么(例如编码),则可以像使用 StreamReader 一样进行所有操作。

      【讨论】:

        【解决方案4】:

        如果您对数据文件的编写方式很灵活并且不介意它对文本编辑器不太友好,您可以使用 BinaryWriter 编写记录:

        using (BinaryWriter writer = 
            new BinaryWriter(File.Open("data.txt", FileMode.Create)))
        {
            writer.Write("one,1,1,1,1");
            writer.Write("two,2,2,2,2");
            writer.Write("three,3,3,3,3");
        }
        

        那么,最初读取每条记录很简单,因为您可以使用 BinaryReader 的 ReadString 方法:

        using (BinaryReader reader = new BinaryReader(File.OpenRead("data.txt")))
        {
            string line = null;
            long position = reader.BaseStream.Position;
            while (reader.PeekChar() > -1)
            {
                line = reader.ReadString();
        
                //parse the name out of the line here...
        
                Console.WriteLine("{0},{1}", position, line);
                position = reader.BaseStream.Position;
            }
        }
        

        BinaryReader 没有缓冲,因此您可以获得适当的位置以供以后存储和使用。唯一的麻烦是将名称解析出来,无论如何您都可能需要使用 StreamReader。

        【讨论】:

        • 如果你可以完全控制文件,你也可以在文件的开头写一个索引。
        【解决方案5】:

        编码是固定大小的(例如 ASCII 或 UCS-2)吗?如果是这样,您可以跟踪字符索引(根据您看到的字符数)并根据它找到二进制索引。

        否则,不 - 您基本上需要编写自己的 StreamReader 实现,它可以让您查看二进制索引。很遗憾 StreamReader 没有实现这一点,我同意。

        【讨论】:

          【解决方案6】:

          我认为 FileHelpers 库运行时记录功能可能会对您有所帮助。 http://filehelpers.sourceforge.net/runtime_classes.html

          【讨论】:

            【解决方案7】:

            一些可能感兴趣的项目。

            1) 如果行是长度固定的字符集,那么如果字符集具有可变大小(如 UTF-8),则这不是必要的有用信息。所以检查你的字符集。

            2) 您可以通过使用 BaseStream.Position 值 IF 首先从 StreamReader 确定文件光标的确切位置 Flush() 缓冲区(这将强制当前位置位于下一次读取将开始 - 最后一个字节读取后的一个字节)。

            3)如果您事先知道每条记录的确切长度将是相同的字符数,并且字符集使用固定宽度的字符(因此每行的长度是相同的字节数),您可以使用具有固定缓冲区大小以匹配行大小的 FileStream,每次读取结束时光标的位置将是下一行的开始。

            4) 如果行的长度相同(假设此处以字节为单位),是否有任何特殊原因,您不只是使用行号并根据行大小 x 行号计算文件中的字节偏移量?

            【讨论】:

              【解决方案8】:

              您确定文件“太大”吗?您是否尝试过这种方式并引起了问题?

              如果您分配了大量内存,而您现在没有使用它,Windows 只会将其换出到磁盘。因此,通过从“内存”访问它,您将完成您想要的——随机访问磁盘上的文件。

              【讨论】:

              • 如果文件的大小超过 1GB,并且您在 32 位上运行,那么即使 Windows 交换了它的小心脏,您也可能会用完地址空间。
              【解决方案9】:

              这个确切的问题是在 2006 年在这里提出的:http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic40275.aspx

              总结:

              "问题是StreamReader缓冲数据,所以返回的值在 BaseStream.Position 属性总是在实际处理的行之前。”

              但是,“如果文件以固定宽度的文本编码进行编码,您可以跟踪已读取的文本数量并将其乘以宽度”

              如果没有,您可以只使用 FileStream 并一次读取一个字符,然后 BaseStream.Position 属性应该是正确的

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2013-02-06
                • 1970-01-01
                • 2015-01-10
                • 1970-01-01
                • 2018-11-11
                • 2011-03-10
                • 2017-09-27
                • 1970-01-01
                相关资源
                最近更新 更多