2008-11-05 7 views
19

J'ai un fichier texte qui contient plusieurs 'enregistrements' à l'intérieur. Chaque enregistrement contient un nom et une collection de nombres en tant que données. J'essaie de construire une classe qui lira le fichier, présentera uniquement les noms de tous les enregistrements, puis permettra à l'utilisateur de sélectionner les données d'enregistrement qu'il/elle veut..NET C# - Accès aléatoire dans les fichiers texte - pas facile?

La première fois que je parcours le fichier, je ne lis que les noms d'en-tête, mais je peux garder une trace de la 'position' dans le fichier où se trouve l'en-tête. J'ai besoin d'un accès aléatoire au fichier texte pour rechercher le début de chaque enregistrement après qu'un utilisateur le demande.

Je dois le faire de cette façon car le fichier est trop volumineux pour être lu complètement en mémoire (1 Go +) avec les autres demandes de mémoire de l'application.

J'ai essayé d'utiliser la classe .NET StreamReader pour ce faire (qui fournit une fonctionnalité 'ReadLine' très facile à utiliser, mais il n'y a aucun moyen de capturer la position réelle du fichier (la position dans la propriété BaseStream est biaisé en raison de la mémoire tampon de la classe utilise).

est-

Répondre

5

il pas facile de le faire dans .NET? vous pouvez utiliser un System.IO.FileStream au lieu de StreamReader. Si vous savez exactement, quel fichier contient (le codage par exemple), vous pouvez faire toutes les opérations comme avec StreamReader

0

Etes-vous sûr que le fichier est "trop ​​grande"? Avez-vous essayé de cette façon et a-t-il causé un problème?

Si vous allouez une grande quantité de mémoire et que vous ne l'utilisez pas pour l'instant, Windows l'échangera sur le disque. Par conséquent, en accédant à partir de "mémoire", vous aurez accompli ce que vous voulez - un accès aléatoire au fichier sur le disque.

+1

Si la taille du fichier est supérieure à 1 Go et que vous exécutez le système sur 32 bits, l'espace d'adressage sera probablement insuffisant, même si Windows échange son cœur. –

6

FileStream a la méthode seek().

+0

Ce n'est pas utile quand on ne sait pas où chercher. –

+0

Peut-être que nous utilisons différentes définitions d'accès aléatoire. Je (ainsi que Jason apparemment) le prend pour signifier un fichier d'enregistrements avec une taille spécifique en octets, ainsi le début d'un enregistrement est (recnum - 1) * recsize – Powerlord

+0

Plus important encore, le PO suggère qu'ils peuvent enregistrer le flux indices au début des enregistrements individuels, sachant où chercher est un problème résolu dans ce cas. –

2

Le codage est-il de taille fixe (par exemple ASCII ou UCS-2)? Si c'est le cas, vous pouvez garder une trace de l'index des caractères (basé sur le nombre de caractères que vous avez vu) et trouver l'index binaire basé sur cela.

Sinon, non - vous auriez essentiellement besoin d'écrire votre propre implémentation StreamReader qui vous permet de jeter un coup d'œil à l'index binaire. C'est une honte que StreamReader ne l'implémente pas, je suis d'accord.

0

Cette question précise a été posée en 2006 ici: http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic40275.aspx

Résumé:

« Le problème est que les données tampons StreamReader, la valeur renvoyée dans propriété BaseStream.Position est toujours en avance sur la ligne traitée réelle. "

Toutefois, « si le fichier est codé dans un codage de texte qui est de largeur fixe, vous pouvez garder une trace de la quantité de texte a été lu et multiplier par la largeur »

et sinon, vous pouvez utilisez simplement le FileStream et lisez un caractère à la fois, puis le BaseStream.Position propriété doit être correcte

5

Si vous êtes flexible avec la façon dont le fichier de données est écrit et ne pas l'esprit qu'il soit un peu moins de texte éditeur WYSIWYG, vous pouvez écrire vos dossiers avec un BinaryWriter:

using (BinaryWriter writer = 
    new BinaryWriter(File.Open("data.txt", FileMode.Create))) 
{ 
    writer.Write("one,1,1,1,1"); 
    writer.Write("two,2,2,2,2"); 
    writer.Write("three,3,3,3,3"); 
} 

Ensuite, la lecture d'abord chaque enregistrement est simple parce que vous pouvez utiliser la méthode de ReadString de BinaryReader:

using (BinaryReader reader = new BinaryReader(File.OpenRead("data.txt"))) 
{ 
    string line = null; 
    long position = reader.BaseStream.Position; 
    while (reader.PeekChar() > -1) 
    { 
     line = reader.ReadString(); 

     //parse the name out of the line here... 

     Console.WriteLine("{0},{1}", position, line); 
     position = reader.BaseStream.Position; 
    } 
} 

le BinaryReader ne tamponne donc vous obtenez la bonne position pour stocker et utiliser plus tard. Le seul problème est d'analyser le nom hors de la ligne, ce qui peut être le cas avec StreamReader.

11

Il y a quelques bonnes réponses fournies, mais je n'ai pas trouvé de code source qui fonctionnerait dans mon cas très simpliste. Voilà, avec l'espoir que cela sauvera quelqu'un d'autre l'heure que j'ai passée à chercher. Le "cas très simpliste" auquel je me réfère est: le codage du texte est de largeur fixe, et les caractères de fin de ligne sont les mêmes dans tout le fichier. Ce code fonctionne bien dans mon cas (où je suis en train d'analyser un fichier journal, et je dois parfois chercher en avant dans le fichier, puis revenir.) J'ai implémenté juste assez pour faire ce que je devais faire (ex: un seul constructeur , et seulement passer outre ReadLine()), donc plus probable que vous aurez besoin d'ajouter du code ... mais je pense que c'est un point de départ raisonnable

public class PositionableStreamReader : StreamReader 
{ 
    public PositionableStreamReader(string path) 
     :base(path) 
     {} 

    private int myLineEndingCharacterLength = Environment.NewLine.Length; 
    public int LineEndingCharacterLength 
    { 
     get { return myLineEndingCharacterLength; } 
     set { myLineEndingCharacterLength = value; } 
    } 

    public override string ReadLine() 
    { 
     string line = base.ReadLine(); 
     if (null != line) 
      myStreamPosition += line.Length + myLineEndingCharacterLength; 
     return line; 
    } 

    private long myStreamPosition = 0; 
    public long Position 
    { 
     get { return myStreamPosition; } 
     set 
     { 
      myStreamPosition = value; 
      this.BaseStream.Position = value; 
      this.DiscardBufferedData(); 
     } 
    } 
} 

Voici un exemple de la façon d'utiliser le PositionableStreamReader.

PositionableStreamReader sr = new PositionableStreamReader("somepath.txt"); 

// read some lines 
while (something) 
    sr.ReadLine(); 

// bookmark the current position 
long streamPosition = sr.Position; 

// read some lines 
while (something) 
    sr.ReadLine(); 

// go back to the bookmarked position 
sr.Position = streamPosition; 

// read some lines 
while (something) 
    sr.ReadLine(); 
+0

Merci! Sauvé ma peau! – Armbrat

1

deux ou trois éléments qui peuvent intéresser.

1) Si le lin es sont un ensemble fixe de caractères, ce qui n'est pas nécessairement une information utile si le jeu de caractères a des tailles variables (comme UTF-8). Alors vérifiez votre jeu de caractères.

2) Vous pouvez connaître la position exacte du curseur de fichier de StreamReader en utilisant la valeur de BaseStream.Position SI vous flush() les tampons premier (qui forcera la position actuelle d'être là où la prochaine lecture commencera - un octet après le dernier octet lu).

3) Si vous savez à l'avance que la longueur exacte de chaque enregistrement sera le même nombre de caractères, et que le jeu de caractères utilise des caractères à largeur fixe (chaque ligne a le même nombre d'octets) utilisez FileStream avec une taille de buffer fixe pour correspondre à la taille d'une ligne et la position du curseur à la fin de chaque lecture sera, forcément, le début de la ligne suivante. 4) Y at-il une raison particulière pour laquelle, si les lignes ont la même longueur (en supposant ici en octets), n'utilisez pas simplement des numéros de ligne et calculez le décalage d'octet dans le fichier basé sur la ligne x nombre?

Questions connexes