IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Delphi Discussion :

Lire un TStream ligne par ligne


Sujet :

Langage Delphi

  1. #1
    Membre confirmé

    Profil pro
    Inscrit en
    Mars 2002
    Messages
    1 184
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2002
    Messages : 1 184
    Points : 619
    Points
    619
    Par défaut Lire un TStream ligne par ligne
    Je dois travailler sur un fichier ASCII de taille pouvant atteindre plusieurs GO.
    Actuellement le traitement est basé sur une TStringList qui évidemment plante sur de tels volumes.

    Je me suis orienté vers un TStream.
    Je ne suis pas en version 2009 je n'ai donc pas le TSteamReader.
    Je dois lire le fichier ligne par ligne (de retour chariot en retour chariot)

    Mon code pour l'instant est le suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        GFIC := TFileStream.Create(self.FPath+self.FName, fmOpenRead or fmShareDenyWrite);
        LC_Chaine := '';
        while (GFIC.Read(LC_Char, 1) = 1) do begin          
               if (LC_Char = Chr(13)) or (LC_Char = Chr(10)) then Begin
                  // traitement
               End
               Else
                  LC_Chaine := LC_Chaine + LC_Char;
        end;
        GFIC.Free;
    Cela fonctionne mais c'est extremement lent compte tenu du nombre d'accès.
    Je cherche à savoir si il est possible de détecter le prochain retour chariot et de lire en une seule fois la chaine à charger.

    Merci d'avance de vos retours

  2. #2
    Expert confirmé
    Avatar de Ph. B.
    Homme Profil pro
    Freelance
    Inscrit en
    Avril 2002
    Messages
    1 786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 58
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Avril 2002
    Messages : 1 786
    Points : 5 918
    Points
    5 918
    Par défaut
    Bonjour,
    Citation Envoyé par Delphi-ne Voir le message
    Cela fonctionne mais c'est extremement lent compte tenu du nombre d'accès.
    Je cherche à savoir si il est possible de détecter le prochain retour chariot et de lire en une seule fois la chaine à charger.
    Au lieu de lire caractère par caractère votre flux, ce qui ne pourra qu'être très lent, lisez le par paquet de 4Ko par exemple, puis traitez en mémoire ce bloc.
    Le code sera un (tout petit) peu plus complexe, mais les performances s'en ressentiront...

  3. #3
    Membre éprouvé
    Avatar de Dr.Who
    Inscrit en
    Septembre 2009
    Messages
    980
    Détails du profil
    Informations personnelles :
    Âge : 45

    Informations forums :
    Inscription : Septembre 2009
    Messages : 980
    Points : 1 294
    Points
    1 294
    Par défaut
    API Windows FileMap !

    J'avais une source la dessus, je la retrouve et te la poste ce soir.

    En gros, c'est un pointeur qui est placé au début du fichier, et tu l'incrémente comme n'importe quel pointeur.

    il suffit ensuite de lister avec un gros PAnsiChar ou même un array of char de 4, 16, 32, 64, 128Ko par exemple

    Par contre, pourquoi les fichiers sont ils si gros ?


    une petite classes ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
     
    uses  Math;
     
     
    type
      TTextReader = class
      private
        fStream : TFileStream;
        fMem    : AnsiString;
        fCount  : integer;
        fMemBlk : integer;
        function getStr: AnsiString;
      protected
        procedure AdjustMem;
      public
        property Str      : AnsiString read getStr;
        property StrCount : integer    read fCount;
     
        procedure FirstPage;
        procedure PrevPage;
        procedure NextPage;
        procedure LastPage;
     
        function PagesCount: integer;
     
        constructor Create(const aFileName: TFileName; const aMemBlockCount: word = 10);
        destructor Destroy; override;
      end;
     
    procedure TTextReader.AdjustMem;
    var Old: integer;
    begin
      Old := fCount;
      while not (fMem[fCount] in [#13,#10]) do
        dec(fCount);
     
      if fCount <> Old then
      begin
        Old := Old - fCount;
        fMem[fCount+1] := #0;
        fStream.Position := fStream.Position - Old;
      end;
    end;
     
    constructor TTextReader.Create(const aFileName: TFileName; const aMemBlockCount: word = 10);
    begin
      inherited Create;
      fStream := TFileStream.Create(aFileName, fmOpenRead);
      fMemBlk := aMemBlockCount * 4096;
      setLength(fMem, fMemBlk);
      FirstPage;
    end;
     
    destructor TTextReader.Destroy;
    begin
      setLength(fMem, 0);
      fStream.Free;
      inherited;
    end;
     
    procedure TTextReader.FirstPage;
    begin
      fStream.Seek(0, soFromBeginning);
      fCount := fStream.Read(fMem[1], fMemBlk);
      AdjustMem;
    end;
     
    function TTextReader.getStr: AnsiString;
    begin
      result := copy(fMem, 1, fCount);
    end;
     
    procedure TTextReader.LastPage;
    begin
      fStream.Seek(-fMemBlk, soFromEnd);
      fCount := fStream.Read(fMem[1], fMemBlk);
      AdjustMem;
    end;
     
    procedure TTextReader.NextPage;
    begin
      fCount := fStream.Read(fMem[1], fMemBlk);
      AdjustMem;
    end;
     
    function TTextReader.PagesCount: integer;
    begin
      if (fStream.Size and 1) = 0 then
        result := ceil(fStream.Size / fMemBlk)
      else
        result := ceil((fStream.Size+1) / fMemBlk);
    end;
     
    procedure TTextReader.PrevPage;
    begin
      fStream.Seek(-fMemBlk, soFromCurrent);
      fCount := fStream.Read(fMem[1], fMemBlk);
      AdjustMem;
    end;

  4. #4
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 787
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 787
    Points : 13 442
    Points
    13 442
    Par défaut
    La lecture par bloc est presque obligatoire. Travailler avec un fichier mappé peut être une solution mais plus simplement dans ton cas, copie ce bloc directement dans un TStringStream et travaille sur la chaîne associée.
    LC_Chaine := LC_Chaine + LC_Char est à éviter puisqu'il va entraîner un nombre important de réallocation. A la place, trouve le caractère de fin (#13) et copie la sous-chaîne en une fois.

  5. #5
    Membre confirmé

    Profil pro
    Inscrit en
    Mars 2002
    Messages
    1 184
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2002
    Messages : 1 184
    Points : 619
    Points
    619
    Par défaut
    Merci

    Je suis parti de la solution de Andnotor.
    Le lis un bloc de 1000 caractères, je recherche dedans le caractère de fin de ligne et je repositionne le pointeur.

    Cela me permet déjà des performances nettement meilleurs sur un fichier 'moyen' de 300 MO.
    Il me reste à tester des volumes de plusieurs GO puisque c'était le but de la modification que l'on m'a demandé.

    J'avais bien pensé à la solution de Ph. B de lire plusieurs KO à la fois mais le traitement n'est pas séquentiel, il avance et recule dans le fichier (c'est dingue...) et cela aurait nettement compliqué les choses.
    Pourquoi des fichiers si gros ? Parce que l'activité du client est justement une gestion de masse.

  6. #6
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    13 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 13 612
    Points : 25 303
    Points
    25 303
    Par défaut
    Lire un fichier ligne par ligne, vous avez tous oublié ReadLn ?
    Effectivement, la TStringList ne doit pas utiliser pour une trop grande quantité, surtout en Unicode où cela double la taille d'un fichiet texte Ansi

    Sinon, j'ai déjà fait une classe qui gère un permet de lire un fichier texte de très grande taille via un TFileStream avec une faible charge mémoire : TTextFileReader écrit en 2007 sur D7 (attention si version unicode, quelque string à forcer en AnsiString)

    Deux modes d'utilisation :
    - unidirectionnel avec ReadString
    - random acces avec AutoIndexed à true et ReadLine (ton mode !)

    Pour le mode random, cela génère un fichier d'index, qui permet de se déplacer librement dans le fichier, si la phase de génération de l'index peut prendre un peu de temps, cela se compense très vite si par la suite, l'algorithme fait des retours arrière

    Je l'ai massivement utiliser pour lire des fichiers SQL de 1Go généré par TMyDump ou de MySQL Administrator
    J'avais un vieux MyDAC (2007), cette version était infame en terme de mémoire pour la restauration, cela chargeait tout le fichier 3 fois
    Je cherchais les requêtes (pouvant être sur plusieurs lignes) et une fois trouver la fin, je devais "rembobiner" pour lire la requête complète
    Certaines requêtes INSERT pouvait faire des centaines de lignes (je crois une limite de 32Ko lié au MAX_SQL_STATMENT)
    Je pense l'avoir testé avec un fichier de 10 Go, le ficheir d'index devait faire dans les 500Mo (12o par ligne )

  7. #7
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 787
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 787
    Points : 13 442
    Points
    13 442
    Par défaut
    En fonction du traitement, tu pourrais aussi envisager du multi-thread.

Discussions similaires

  1. Réponses: 9
    Dernier message: 03/07/2014, 18h08
  2. lire une base de donnee ligne par ligne
    Par thildouille dans le forum Langage
    Réponses: 9
    Dernier message: 03/06/2011, 00h50
  3. lire un fichier de string ligne par ligne
    Par bilzzbenzbilz dans le forum Entrée/Sortie
    Réponses: 4
    Dernier message: 11/02/2009, 10h44
  4. Réponses: 3
    Dernier message: 16/10/2007, 20h45

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo