2008-11-21 2 views
1

S'il vous plaît ne hésitez pas à me corriger si je me trompe à tout moment ...Quelle est la manière la plus efficace d'implémenter ReadLine() sur un flux binaire?

Je suis en train de lire un fichier en utilisant le fichier .NET CSV (valeurs séparées par des virgules) classes E/S. Maintenant le problème est, ce fichier CSV peut contenir des champs avec des retours chariot doux (c.-à-d. Des marqueurs \ r ou \ n plutôt que le standard utilisé dans les fichiers texte pour terminer une ligne) dans certains champs et le mode texte standard La classe d'E/S StreamReader ne respecte pas la convention standard et traite les retours chariot comme des retours chariot, compromettant ainsi l'intégrité du fichier CSV.

Maintenant, l'utilisation de la classe BinaryReader semble être la seule option restante, mais BinaryReader n'a pas de fonction ReadLine(), d'où la nécessité d'implémenter une ReadLine() par moi-même. Mon approche actuelle lit un caractère du flux à la fois et remplit un StringBuilder jusqu'à ce qu'un \ r \ n soit obtenu (en ignorant tous les autres caractères, y compris solitaire \ r ou \ n), puis retourne une représentation sous forme de chaîne de StringBuilder (en utilisant ToString()).

Mais je me demande: est-ce le moyen le plus efficace d'implémenter la fonction ReadLine()? S'il te plaît, éclaire-moi.

+1

Lorsque vous dites «la convention standard», vous devez vous rendre compte que ce n'est pas particulièrement standard. Sous Unix, "\ n" est le terminateur de ligne normal, seul. –

+0

Avez-vous vraiment un problème de perf ou est-ce un cas typique d'optimisation prématurée? ;). Je ne t'ai pas vu mentionner le problème de perf –

+0

@Jon - Oui je sais, merci. Je voulais dire standard sur la fenêtre/dos. –

Répondre

6

C'est probablement le cas. En termes d'ordre, il passe une fois par chaque caractère, donc ce serait O (n) (où n est la longueur du flux) donc ce n'est pas un problème. Pour lire un seul caractère, un BinaryReader est votre meilleur pari.

Ce que je ferais est de faire une classe

public class LineReader : IDisposable 
{ 
    private Stream stream; 
    private BinaryReader reader; 

    public LineReader(Stream stream) { reader = new BinaryReader(stream); } 

    public string ReadLine() 
    { 
     StringBuilder result = new StringBuilder(); 
     char lastChar = reader.ReadChar(); 
     // an EndOfStreamException here would propogate to the caller 

     try 
     { 
      char newChar = reader.ReadChar(); 
      if (lastChar == '\r' && newChar == '\n') 
       return result.ToString(); 

      result.Append(lastChar); 
      lastChar = newChar; 
     } 
     catch (EndOfStreamException) 
     { 
      result.Append(lastChar); 
      return result.ToString(); 
     } 
    } 

    public void Dispose() 
    { 
     reader.Close(); 
    } 
} 

Ou quelque chose comme ça.

(ATTENTION:. Le code n'a pas été testé et est fourni sans ce programme s'avère défectueux ou détruire la planète doit-garantie d'aucune sorte, expresse ou implicite, vous assumez le coût de tous les services, réparations ou correction.)

+0

Wow! C'était plutôt rapide. Merci pour votre réponse, je voudrais voter, mais je n'ai pas encore assez de réputation :) –

+0

Cela ne devrait-il pas se passer en boucle? Je ne vois pas comment vous lirez plus de 1 ou 2 caractères. – Eric

+0

Je pense que vous avez raison. Je ne peux pas vraiment l'éditer à partir de mobile, et n'aura pas accès à un bureau pendant un moment. Pourriez-vous suggérer une amélioration? – configurator

0

Que diriez-vous simplement de prétraiter le fichier?

Remplacez les retours chariot par quelque chose d'unique. Pour l'anecdote, les fichiers CSV avec des sauts de ligne dans les données, c'est un mauvais design.

+0

Je pense que les sauts de ligne singuliers dans les données CSV peuvent ne pas être une mauvaise idée aussi longtemps que vous êtes sur windows/dos. Cette conception a été autour pendant assez longtemps. C'est comme ça que ça se passe dans Excel par exemple si vous avez un saut de ligne dans une cellule. (Appuyez sur Alt + Entrée pour introduire un saut de ligne dans une cellule) –

0

Vous pouvez lire un morceau plus volumineux à la fois, le désencoder en une chaîne à l'aide de Encoder.GetString, puis le diviser en lignes à l'aide de string.Split ("\ r \ n") ou même en sélectionnant la tête de la chaîne string.Substring (0, string.IndexOf ("\ r \ n")) et laissant le reste pour le traitement de la ligne suivante. N'oubliez pas d'ajouter l'opération de lecture suivante à votre dernière ligne de la lecture précédente.

+0

Le flux sous-jacent met déjà en mémoire tampon les lectures en morceaux plus gros, n'est-ce pas? – configurator

+0

@config: oui, c'est le cas. – MusiGenesis

+0

J'étais plus inquiet à propos de l'ajout de chaînes de longueur = 1 à un "StringBuffer" (pourrait-il signifier StringBuilder), et des affectations de tas fréquents. Il vaut mieux faire moins d'opérations sur des cordes plus grandes. – Guge

0

Votre approche semble correcte. Une façon d'améliorer l'efficacité de votre méthode pourrait consister à stocker chaque ligne au fur et à mesure que vous la construisez dans une chaîne régulière (c'est-à-dire pas un StringBuilder), puis d'ajouter la chaîne entière à votre StringBuilder. Voir this article pour plus d'explications - StringBuilder n'est pas automatiquement le meilleur choix ici.

Cependant, cela aura probablement peu d'importance.

+0

Ce n'est pas tout à fait vrai: utiliser String.Join sur une chaîne [] serait plus rapide. Mais qu'en est-il de la construction de la chaîne []? Vous auriez besoin d'une List <> ou d'une LinkedList <>, qui prendrait plus de temps à construire que d'utiliser un StringBuilder. – configurator

+0

Je viens de lire l'article et je suis d'accord avec "configurateur". Le premier ordre du jour ici est de peigner le flux entrant pour \ r \ n un char à la fois et de construire une chaîne à partir des caractères rejetés. StringBuilder surclasse String dans ce cas. –

+0

@MusiGenesis merci pour le lien vers l'article si. Vous m'avez en effet éclairé :) –

1

Pour cela, vous pouvez utiliser une connexion ODBC/OleDB. Si vous pointez la source de données d'une connexion oledb vers un répertoire contenant des fichiers csv, vous pouvez l'interroger comme si chaque fichier CSV était une table.
vérifier http://www.connectionstrings.com/?carrier=textfile>connectionstrings.com pour la chaîne de connexion correcte

+0

Hmm, une solution intéressante! –

0

Voici une alternative plus rapide avec le support d'encodage. Il étend BinaryReader, donc vous pouvez l'utiliser pour faire les deux, lire des morceaux binaires et aussi exécuter StreamReader comme ReadLine directement sur un flux binaire.

public class LineReader : BinaryReader 
{ 
    private Encoding _encoding; 
    private Decoder _decoder; 

    const int bufferSize = 1024; 
    private char[] _LineBuffer = new char[bufferSize]; 

    public LineReader(Stream stream, int bufferSize, Encoding encoding) 
     : base(stream, encoding) 
    { 
     this._encoding = encoding; 
     this._decoder = encoding.GetDecoder(); 
    } 

    public string ReadLine() 
    { 
     int pos = 0; 

     char[] buf = new char[2]; 

     StringBuilder stringBuffer = null; 
     bool lineEndFound = false; 

     while(base.Read(buf, 0, 2) > 0) 
     { 
      if (buf[1] == '\r') 
      { 
       // grab buf[0] 
       this._LineBuffer[pos++] = buf[0]; 
       // get the '\n' 
       char ch = base.ReadChar(); 
       Debug.Assert(ch == '\n'); 

       lineEndFound = true; 
      } 
      else if (buf[0] == '\r') 
      { 
       lineEndFound = true; 
      }      
      else 
      { 
       this._LineBuffer[pos] = buf[0]; 
       this._LineBuffer[pos+1] = buf[1]; 
       pos += 2; 

       if (pos >= bufferSize) 
       { 
        stringBuffer = new StringBuilder(bufferSize + 80); 
        stringBuffer.Append(this._LineBuffer, 0, bufferSize); 
        pos = 0; 
       } 
      } 

      if (lineEndFound) 
      { 
       if (stringBuffer == null) 
       { 
        if (pos > 0) 
         return new string(this._LineBuffer, 0, pos); 
        else 
         return string.Empty; 
       } 
       else 
       { 
        if (pos > 0) 
         stringBuffer.Append(this._LineBuffer, 0, pos); 
        return stringBuffer.ToString(); 
       } 
      } 
     } 

     if (stringBuffer != null) 
     { 
      if (pos > 0) 
       stringBuffer.Append(this._LineBuffer, 0, pos); 
      return stringBuffer.ToString(); 
     } 
     else 
     { 
      if (pos > 0) 
       return new string(this._LineBuffer, 0, pos); 
      else 
       return null; 
     } 
    } 

} 
1

Voici une méthode d'extension pour la classe BinaryReader:

using System.IO; 
using System.Text; 

public static class BinaryReaderExtension 
{ 
    public static string ReadLine(this BinaryReader reader) 
    { 
     if (reader.IsEndOfStream()) 
      return null; 

     StringBuilder result = new StringBuilder(); 
     char character; 
     while(!reader.IsEndOfStream() && (character = reader.ReadChar()) != '\n') 
      if (character != '\r' && character != '\n') 
       result.Append(character); 

     return result.ToString(); 
    } 

    public static bool IsEndOfStream(this BinaryReader reader) 
    { 
     return reader.BaseStream.Position == reader.BaseStream.Length; 
    } 
} 

Je n'ai pas testé dans toutes les conditions, mais ce code a fonctionné pour moi.

Questions connexes