2010-12-09 11 views
0

J'ai un fichier de 6 Go et les 20 dernières lignes sont mauvaises. Je voudrais utiliser un fichier mappé en mémoire avec .NET 4 pour lire les dernières lignes et les afficher dans console.writelines, et plus tard aller les 20 dernières lignes et les remplacer par String.Empty. Quel est un moyen cool de le faire en utilisant un fichier/flux mappé en mémoire avec un exemple C#?Fichier mappé en mémoire pour lire la fin du fichier?

Merci.

+2

Vous connaissez un moyen ordinaire et vous en cherchez maintenant un cool? – khachik

+0

Je ne connais aucun moyen en ce moment. J'espérais par défaut de «cool». À l'heure actuelle, je lis le fichier en utilisant un flux old-school sur l'objet File et readline jusqu'à la fin et juste montrer la fin, je ne suis même pas sur la partie supprimer. – Snowy

Répondre

1

D'après la question, il semble que vous ayez besoin d'un fichier Memory Mapped. Cependant, il existe un moyen de le faire sans utiliser un fichier mappé en mémoire.

Ouvrez le fichier normalement, puis déplacez le pointeur de fichier jusqu'à la fin du fichier. Une fois que vous êtes à la fin, lisez le fichier à l'envers (décrémentez le pointeur de fichier après chaque lecture) jusqu'à ce que vous obteniez le nombre de caractères désiré.

La manière cool ... charger les caractères dans un tableau à l'envers aussi bien, alors vous n'avez pas à les inverser une fois que vous avez fini de lire.

Effectuez le correctif de la baie, puis réécrivez-les. Fermez, Rincez, Complétez!

+0

Pourquoi à l'envers? Que se passe-t-il quand vous faites cela? – Amy

+0

La lecture d'un fichier vers l'arrière n'est pas différente de l'avant. Alternativement, il pourrait sauvegarder le pointeur après s'être déplacé à la fin et lire en avant mais ce n'est juste pas aussi frais :) –

+0

Voir mon message ci-dessous: lire le texte en arrière est problématique en raison des problèmes d'encodage. –

0

La solution comporte deux parties. Pour la première partie, vous devez lire la carte mémoire en arrière pour saisir les lignes, jusqu'à ce que vous ayez lu le nombre de lignes que vous voulez (20 dans ce cas). Pour la deuxième partie, vous voulez tronquer le fichier par les vingt dernières lignes (en les affectant à string.Empty). Je ne suis pas sûr si vous pouvez le faire avec une carte mémoire. Vous devrez peut-être faire une copie du fichier quelque part et écraser l'original avec les données source sauf les xxx derniers octets (qui représentent les vingt dernières lignes)

Le code ci-dessous va extraire les vingt dernières lignes et l'afficher .

Vous obtiendrez également la position (lastBytePos variable) où commencent les vingt dernières lignes. Vous pouvez utiliser cette information pour savoir où tronquer le fichier.

MISE À JOUR: tronquer le fichier (appel FileStream.SetLength lastBytePos)

Je ne savais pas ce que vous vouliez dire par les 20 dernières lignes sont mauvais. Dans le cas où le disque est physiquement corrompu et que les données ne peuvent pas être lues, j'ai ajouté une liste badPositions qui contient les positions où la carte mémoire a eu des problèmes de lecture des données.

Je n'ai pas de fichier + 2GB à tester, mais cela devrait fonctionner (croiser les doigts).

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

mémoire mappée Les fichiers peuvent être un problème pour les gros fichiers (généralement des fichiers qui sont d'une taille équivalente ou supérieure à la RAM), dans le cas où vous associez éventuellement le fichier entier. Si vous mappez seulement la fin, cela ne devrait pas être un vrai problème.

De toute façon, voici une implémentation C# qui n'utilise pas de fichier mappé mémoire, mais un FileStream standard. Il est basé sur une implémentation ReverseStreamReader (code également inclus). Je serais curieux de le voir par rapport à d'autres solutions MMF en termes de performances et de consommation de mémoire.

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

Je ne sais rien de ReverseStreamReaders. La solution est [essentiellement] simple:

  • cherchent à mettre fin de fichier
  • lignes de lecture en sens inverse. Compter les personnages au fur et à mesure.
  • Lorsque vous avez accumulé 20 lignes, vous avez terminé: définissez la longueur du fichier sur le flux, en décrémentant le nombre de caractères contenus dans les 20 lignes et fermez le fichier.

Le diable est dans les détails, cependant, en ce qui concerne "lire les lignes dans la partie inverse". Il existe des facteurs de complication susceptibles de vous causer des problèmes:

  1. Vous ne pouvez pas rechercher sur un StreamReader, uniquement sur un flux.
  2. La dernière ligne du fichier peut être terminée ou non avec une paire CRLF.
  3. Les classes d'E/S du framework .Net ne font pas vraiment la différence entre CR, LF ou CRLF en tant que terminaisons de ligne. Ils ont simplement insisté sur cette convention.
  4. Selon le codage utilisé pour stocker le fichier, la lecture vers l'arrière est très problématique. Vous ne savez pas ce que représente un octet/octet particulier: il se peut qu'il fasse partie d'une séquence d'encodage multi-octets. Caractère! = Byte dans cet âge moderne. La seule façon de vous protéger est de savoir si le fichier utilise un codage à un octet ou, s'il s'agit d'un codage UTF-8, qu'il ne contient aucun caractère dont le code est supérieur à 0x7F.

Je ne suis pas sûr qu'il ya une bonne, facile solution en dehors de l'évidence: lecture séquentielle dans le fichier et ne pas écrire les vingt dernières lignes.

Questions connexes