2010-12-09 50 views
0

我有一個6GB的文件,最後20行是壞的。我想在.NET 4中使用內存映射文件來讀取最後幾行,並將它們顯示在console.writelines中,然後轉到最後20行,並用String.Empty替換它們。使用帶C#示例的內存映射文件/流來做這件事很酷的方法是什麼?內存映射文件讀取文件結尾?

謝謝。

+2

你知道一個普通的方式,現在正在尋找一個很酷呢? – khachik 2010-12-09 20:23:30

+0

我現在不知道任何方式。我希望默認'酷'。現在我實際上使用File對象上的old-school流和readline讀取文件直到結束,並顯示結束,我甚至不在刪除部分。 – Snowy 2010-12-10 16:31:39

回答

1

從這個問題聽起來像你需要一個內存映射文件。但是,有一種方法可以在不使用內存映射文件的情況下執行此操作。

正常打開文件,然後將文件指針移到文件的末尾。一旦你在最後,反向讀取文件(每次讀取後遞減文件指針),直到獲得所需數量的字符。

酷酷的方式......將字符反轉載入數組,然後在完成讀取後不必將其反轉。

對數組進行修復然後將其寫回。關閉,沖洗,完成!

0

解決方案有兩個部分。對於第一部分,您需要向後讀取存儲器映射以獲取線條,直到讀取了所需的行數(在這種情況下爲20)。

對於第二部分,您希望截斷最後20行的文件(將它們設置爲string.Empty)。我不確定你是否可以用內存映射來做到這一點。 您可能必須在某處複製文件,並使用除最後xxx字節(代表最後20行)之外的源數據覆蓋原始文件

以下代碼將提取最後20行並顯示它。

您還將獲得最後20行開始的位置(lastBytePos變量) 。您可以使用該信息來了解截斷文件的位置。

UPDATE:截斷文件調用FileStream.SetLength(lastBytePos)

我不知道你所說的最後20行的意思是壞的。如果磁盤物理損壞且數據無法讀取,我添加了一個badPositions列表,該列表包含內存映射在讀取數據時遇到問題的位置。

我沒有+ 2GB文件來測試,但它應該工作(手指交叉)。

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.IO.MemoryMappedFiles; 
using System.IO; 

namespace ConsoleApplication 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      string filename = "textfile1.txt"; 
      long fileLen = new FileInfo(filename).Length; 
      List<long> badPositions = new List<long>(); 
      List<byte> currentLine = new List<byte>(); 
      List<string> lines = new List<string>(); 
      bool lastReadByteWasLF = false; 
      int linesToRead = 20; 
      int linesRead = 0; 
      long lastBytePos = fileLen; 

      MemoryMappedFile mapFile = MemoryMappedFile.CreateFromFile(filename, FileMode.Open); 

      using (mapFile) 
      { 
       var view = mapFile.CreateViewAccessor(); 

       for (long i = fileLen - 1; i >= 0; i--) //iterate backwards 
       { 

        try 
        { 
         byte b = view.ReadByte(i); 
         lastBytePos = i; 

         switch (b) 
         { 
          case 13: //CR 
           if (lastReadByteWasLF) 
           { 
            { 
             //A line has been read 
             var bArray = currentLine.ToArray(); 
             if (bArray.LongLength > 1) 
             { 
              //Add line string to lines collection 
              lines.Insert(0, Encoding.UTF8.GetString(bArray, 1, bArray.Length - 1)); 

              //Clear current line list 
              currentLine.Clear(); 

              //Add CRLF to currentLine -- comment this out if you don't want CRLFs in lines 
              currentLine.Add(13); 
              currentLine.Add(10); 

              linesRead++; 
             } 
            } 
           } 
           lastReadByteWasLF = false; 

           break; 
          case 10: //LF 
           lastReadByteWasLF = true; 
           currentLine.Insert(0, b); 
           break; 
          default: 
           lastReadByteWasLF = false; 
           currentLine.Insert(0, b); 
           break; 
         } 

         if (linesToRead == linesRead) 
         { 
          break; 
         } 


        } 
        catch 
        { 
         lastReadByteWasLF = false; 
         currentLine.Insert(0, (byte) '?'); 
         badPositions.Insert(0, i); 
        } 
       } 

      } 

      if (linesToRead > linesRead) 
      { 
       //Read last line 
       { 
        var bArray = currentLine.ToArray(); 
        if (bArray.LongLength > 1) 
        { 
         //Add line string to lines collection 
         lines.Insert(0, Encoding.UTF8.GetString(bArray)); 
         linesRead++; 
        } 
       } 
      } 

      //Print results 
      lines.ForEach(o => Console.WriteLine(o)); 
      Console.ReadKey(); 
     } 
    } 
} 
3

內存映射文件可以是(是一個大小相當於或大於RAM更大的通常文件)大文件的一個問題,如果你最終映射整個文件。如果你只繪製結尾,那應該不是真正的問題。

無論如何,這裏是一個C#實現,它不使用內存映射文件,而是一個普通的FileStream。它基於ReverseStreamReader實現(代碼也包含在內)。在性能和內存消耗方面,我會好奇的看到它與其他MMF解決方案相比。

public static void OverwriteEndLines(string filePath, int linesToStrip) 
{ 
    if (filePath == null) 
     throw new ArgumentNullException("filePath"); 

    if (linesToStrip <= 0) 
     return; 

    using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite)) 
    { 
     using (ReverseStreamReader reader = new ReverseStreamReader(file)) 
     { 
      int count = 0; 
      do 
      { 
       string line = reader.ReadLine(); 
       if (line == null) // end of file 
        break; 

       count++; 
       if (count == linesToStrip) 
       { 
        // write CR LF 
        for (int i = 0; i < linesToStrip; i++) 
        { 
         file.WriteByte((byte)'\r'); 
         file.WriteByte((byte)'\n'); 
        } 

        // truncate file to current stream position 
        file.SetLength(file.Position); 
        break; 
       } 
      } 
      while (true); 
     } 
    } 
} 

// NOTE: we have not implemented all ReadXXX methods 
public class ReverseStreamReader : StreamReader 
{ 
    private bool _returnEmptyLine; 

    public ReverseStreamReader(Stream stream) 
     : base(stream) 
    { 
     BaseStream.Seek(0, SeekOrigin.End); 
    } 

    public override int Read() 
    { 
     if (BaseStream.Position == 0) 
      return -1; 

     BaseStream.Seek(-1, SeekOrigin.Current); 
     int i = BaseStream.ReadByte(); 
     BaseStream.Seek(-1, SeekOrigin.Current); 
     return i; 
    } 

    public override string ReadLine() 
    { 
     if (BaseStream.Position == 0) 
     { 
      if (_returnEmptyLine) 
      { 
       _returnEmptyLine = false; 
       return string.Empty; 
      } 
      return null; 
     } 

     int read; 
     StringBuilder sb = new StringBuilder(); 
     while((read = Read()) >= 0) 
     { 
      if (read == '\n') 
      { 
       read = Read(); 
       // supports windows & unix format 
       if ((read > 0) && (read != '\r')) 
       { 
        BaseStream.Position++; 
       } 
       else if (BaseStream.Position == 0) 
       { 
        // handle the special empty first line case 
        _returnEmptyLine = true; 
       } 
       break; 
      } 
      sb.Append((char)read); 
     } 

     // reverse string. Note this is optional if we don't really need string content 
     if (sb.Length > 1) 
     { 
      char[] array = new char[sb.Length]; 
      sb.CopyTo(0, array, 0, array.Length); 
      Array.Reverse(array); 
      return new string(array); 
     } 
     return sb.ToString(); 
    } 
} 
0

我對ReverseStreamReaders一無所知。該解決方案是[基本上]簡單:

  • 尋求到檔案結尾反向
  • 讀取線。隨着你走,計數字符。
  • 當您累計了20行時,您已完成:通過遞減20行中包含的字符數並關閉文件來設置流上的文件長度。

雖然,惡魔在細節,關於「讀反向線」。有一些複雜因素可能會讓您陷入困境:

  1. 您不能在StreamReader上查找,只能在流上查找。
  2. 文件的最後一行可以或可以不與一個CRLF對結束。
  3. 了.NET Framework的I/O類並不真正CR,LF或CRLF區分作爲行終止。他們只是在那個大會上踢球。
  4. 根據用於存儲文件的編碼,向後讀取是非常成問題的。你不知道一個特定的字節/字節表示什麼:它可能是多字節編碼序列的一部分。 Character!=這個現代時代的字節。如果您知道文件使用單字節編碼,或者如果它是UTF-8,它不包含代碼點大於0x7F的字符,則唯一安全的方法是。

我不知道有一個良好的,容易明顯之外的解決方案:通過文件順序讀取和不寫了二十行。