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

时间:2011-12-14 15:41:44

标签: c# linq file text-processing

我有大的日志文件,每一秒都包含时间戳。我需要的是从这个巨大的文件中剪切用户定义的部分并将其保存在另一个文本文件中...我很困惑,因为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 
等等到EOF。

2 个答案:

答案 0 :(得分:3)

ReadLine根本不是您想要做的...打开文件阅读器...寻找您想要的位置,读取您想要的数据(进入另一个文件流)。

“ReadLine”必须实际读取数据...而寻求(myStream.Position = whereIWantToGo)基本上是即时的。

您将以与排序数据库相同的方式处理此问题。一个拥有1,000,000条记录的数据库只需要20次“搜索”操作就可以找到...中途开始,太高了?刚刚节省了50万次寻求......中途回来......太高了?只是削减了250,000个寻求......冲洗,重复。

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

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

应该让你入门的C#控制台应用

免责声明......这段代码很讨厌,并且可能存在无限循环的错误:)...但是,这里有一个适合您的控制台应用程序。

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;
        }
    }
}

答案 1 :(得分:3)

从一个有根据的猜测开始,关于时间戳到文件的深度。如果你不能这样做,从中间开始 - 实际上,进行二分查找。

一旦你找到了一个地方,请阅读几行(*),直到找到时间戳。此时您要么有时间戳,要么可以确定它是在当前点之前还是之后。如果不是您的时间戳,请向后或向前搜索逻辑金额并重复,直至找到您要查找的时间戳。

使用这种技术,你可以找到你的时间戳,只需几十次读取。

您可能希望阅读seek on MSDN

*请注意,在搜索时,文件指针可能不在行的开头。当然,这种技术仍然有效,但是当你将搜索范围缩小到很小的范围时,需要注意这一点。