保存一个巨大文本文件的特定部分(超过2GB)

本文关键字:定部 超过 2GB 文本 一个 巨大 保存 文件 | 更新日期: 2023-09-27 18:18:03

我有一个大的日志文件,其中包含每秒钟的时间戳。我需要的是从这个巨大的文件中剪切一个用户定义的部分,并将其保存在另一个文本文件中。我很困惑,因为fstream类可以处理最大2GB的文件大小,读取所有行是时间和内存灾难。

时间戳:<<Dd.mm.yyyy hh:min:sec>每秒一次,每行一次。一位教授建议使用LINQ和readline()。

文件的示例:

!<<14.12.2012 16:20:03>
some text some text some 
some text some text some 
some text some text some 
!<<14.12.2012 16:20:04>
some text some text some 
some text some text some 
some text some text some 
some text some text some 
some text some text some 
!<<14.12.2012 16:20:05>
some text some text some
!<<14.12.2012 16:20:06>
some text some text some 
some text some text some 

保存一个巨大文本文件的特定部分(超过2GB)

ReadLine根本不是你想做的…打开文件阅读器…查找到你想要的位置,读出你想要的数据(到另一个文件流)。

"ReadLine"必须实际读取数据…而寻找(myStream)。Position = whereIWantToGo)基本上是即时的。

您将以与排序数据库相同的方式处理此问题。拥有1,000,000条记录的DB只需要20次"seek"操作即可找到…半途起步,太高了?刚刚节省了50万份……半路回来……太高了吗?刚刚又裁掉了25万名求职者……冲洗,重复。

如果你发现有趣的字符(编码错误)

根据你的电子邮件(顺便说一句,你真的应该继续使用S.O,而不是电子邮件——这样其他人就可以受益)…答案是,您需要尝试不同的编码类型。您的文件可能没有编码UTF8(这是我下面的代码所期望的)。因此,请使用new StreamReader("MyLogFile.txt", Encoding.ASCII)或其他编码,直到它适合您为止。

c#控制台应用程序,应该让你开始

免责声明…这段代码很讨厌,并且可能有无限循环的bug:)…但是,这里有一个控制台应用程序应该为你工作。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // example dates
            var lookFor = new DateTime(2012, 12, 14, 16, 20, 02);
            var readUntilDate = new DateTime(2012, 12, 14, 16, 20, 05);
            using (var stream = File.OpenText("MyLogFile.txt"))
            {
                if (SeekToEntry(stream, lookFor) == false)
                {
                    Console.WriteLine("Could not find entry for date {0}", lookFor);
                    return;
                }
                foreach (var line in ReadEntriesUntil(stream, readUntilDate))
                {
                    Console.WriteLine("Line: {0}", line);
                }
            }
        }
        // This method simply spits out one line at a time until it hits
        // the target cut-off.
        static IEnumerable<string> ReadEntriesUntil(StreamReader stream, DateTime target)
        {
            while (true)
            {
                string line = stream.ReadLine();
                if (line == null)
                {
                    break;
                }
                if (line.StartsWith("!<<"))
                {
                    DateTime entryDate;
                    if (DateTime.TryParseExact(line.Substring(3, 19).Replace(".", ""), @"ddMMyyyy HH:mm:ss",
                        CultureInfo.InvariantCulture, DateTimeStyles.None, out entryDate))
                    {
                        if (entryDate >= target)
                        {
                            break;
                        }
                    }
                }
                yield return line;
            }
        }
        // This method will bounce around the stream till it finds your
        // target entry date.
        static bool SeekToEntry(StreamReader stream, DateTime target)
        {
            long from = 0;
            long to = stream.BaseStream.Length;
            while (true)
            {
                long testIndex = (to - from) / 2;
                stream.BaseStream.Seek(testIndex, SeekOrigin.Begin);
                var entryDate = GetNextEntryDate(stream, out testIndex);
                if (entryDate == null || (from == to))
                {
                    return false;
                }
                switch (entryDate.Value.CompareTo(target))
                {
                    case -1:
                        // Found too low...
                        from = testIndex;
                        break;
                    case 1:
                        // Fount too high...
                        to = testIndex;
                        break;
                    default: return true;
                }
            }
        }
        // This is a function that is meant to keep seeking forward until
        // it hits an entry date.
        static DateTime? GetNextEntryDate(StreamReader stream, out long actualIndex)
        {
            actualIndex = stream.BaseStream.Position;
            DateTime? result = null;
            string line = null;
            // Find the next entry.
            while ((line = stream.ReadLine()) != null && line.StartsWith("!<<") == false) ;
            if (line != null)
            {
                actualIndex = stream.BaseStream.Position - line.Length;
                DateTime timeStamp;
                if (DateTime.TryParseExact(line.Substring(3, 19).Replace(".", ""), @"ddMMyyyy HH:mm:ss",
                    CultureInfo.InvariantCulture, DateTimeStyles.None, out timeStamp))
                {
                    result = timeStamp;
                }
            }
            return result;
        }
    }
}

首先对时间戳在文件中的深度进行有根据的猜测。如果你不能做到这一点,从中间开始——实际上,做一个二分搜索。

找到一个点后,读取几行(*),直到找到时间戳。此时,您可以得到时间戳,也可以确定它是在当前点之前还是之后。如果它不是你的时间戳,向后或向前查找一个逻辑量,并重复,直到找到你要找的时间戳。

使用这种技术,您可能只需要几十次读取就可以找到时间戳。

你可能需要阅读一下MSDN上的seek

*请注意,当您查找时,文件指针可能不在一行的开头。当然,这种方法仍然有效,但是当您将搜索范围缩小到非常小的范围时,需要注意这一点。