Novo FastFile 3.0 – Lendo arquivos grandes – 2GB pra frente



Foram dias difíceis de testes. Não foi fácil encontrar uma solução que atendesse aos quesitos de performance e estabilidade de forma satisfatória. Depois de experimentar consagradas alternativas (tradicionais e open-source), como TGpHugeFile, TLists, Memory Mapped Files, Threads, vários descendentes de TFileStream,  alguns banco de dados, etc; todos frustraram as expectativas para processamento superior a 15 milhões de linhas dinamicamente em memória; por questões óbvias de falta dela tive que procurar uma outra solução que em nenhum lugar aparentemente encontrasse – teria que “criar do zero” e seguir com ela – a fim de carregar grandes arquivos de 2GB pra frente sem travar a aplicação por falta de memória ou algo parecido.

Qual o problema de ler um arquivo imensamente grande em Delphi?

Se você não ser precavido, a resposta é todos eles. Os problemas são primeiramente a falta de memória, depois o travamento da aplicação, e terceiro seria o Windows reclamando com também travamento de telas e processos – não adianta “acelerar o conserto” depois – já que para retomar à estabilidade de antes é bem provável que se leve um tempo considerável – e um prejuízo de sua aplicação que falhou a operação de leitura, além de perder tempo com ela também. E aí, como resolver então?

Já foi dito anteriormente, em outras edições, nas versões anteriores deste nosso projeto FastFile que uma solução baseada em listas de ponteiros (TLists) são excelentes – e é verdade – só que não foi dito é até quando que será excelente – ou melhor – até quanto de comprimento de um arquivo nosso objeto de TList vai armazenar as linhas deste arquivo sem estourar a memória – e isso é um fato. Testamos na edição passada nosso FastFile lendo um arquivo de 1,2 GB em 30 segundos. Funcionou e funciona até agora nesta versão com sucesso. Mas e se o arquivo for maior que 1,2 GB?
Para isso nossa outra solução teve que ser buscada – e pacientemente. Como disse anteriormente, foram muitos os caminhos trilhados – e todos falharam por não conseguir lograr êxito nesta missão. A missão a qual me refiro é poder ler mais de 15 milhões de uma forma sem travar – na memória é que não vai poder mais – portanto até este limite vamos considerar que é possível. A partir daí que vamos buscar outra implementação.
O valor de 15 milhões é simbólico – poderia ser um pouco mais que isso. Mas este valor é bem “seguro” para os nossos testes. Então vamos empregar outra abordagem – os arquivos até 1,5 GB terão a abordagem da nossa classe TLineReaderList baseada em TList (já discutido na edição anterior) enquanto que os arquivos superior a 1,5 GB terão a abordagem em um outro componente adotado – e é essa a novidade do nosso artigo e a solução em definitiva – a única que não fracassou em nossa solução de leitura a arquivos grandes – a classe open-source SynBigTable !

Processando a leitura das linhas com SynBigTable

Esta classe é incrivelmente rápida; sua leitura é eficiente e invejável, lendo e salvando em um arquivo externo como o buffer da operação de leitura e armazenamento temporário das linhas de um arquivo superior a 1,5GB. A operação de leitura poderá ser repetida quantas vezes forem necessárias – e com louvável resultado de processamento, eficiente e razoável – levando em média dois minutos. E esse será o tempo de espera – mas o que são dois minutos para arquivos com mais de 2GB (nos testes, para ser mais exato – 2,2GB)?
Também com ela foi utilizada a unit FastMM4 como gerenciador eficiente de memória. Os resultados obtidos através dela foram impressionantes.
Com a classe TSynBigTableRecord é perfeita para manipulação de registros, aos milhões de uma vez, com a rapidez em poucos segundos, sempre gravando em um arquivo temporário; de acordo com os desenvolvedores é ideal para um registro de log, e a nossa ideia a ser implementada aqui é utilizar esta classe como estrutura de apoio a nossa leitura dos arquivos “gigantes” sem comprometer a performance ou a memória, pois já está tudo bem implementada com ela sem problema algum. Basta definir primeiramente o nome do arquivo temporário a ser criado – e depois criar os campos (sim, funciona como um banco de dados local baseado em arquivos, mas sem SQL) e depois popular os campos e salvando, até terminar. E esta operação será feita de forma bem rápida.
O processo básico de funcionamento com a classe TSynBigTableRecord é o seguinte:

  1. Criar um arquivo -  T := TSynBigTableRecord.Create(‘meuBD.extensao’,'TableName');

  2. Definir os campos - T.AddField('Line',tftWinAnsi,[tfoIndex]);

  3. Mais um campo - T.AddField('Number',tftInt32,[tfoIndex]);

  4. Salvar as alterações - T.AddFieldUpdate;

  5. Utilizar variáveis do tipo TSynTableFieldProperties para melhor performance de memória;

  6. Definir portanto duas variáveis para o nosso exemplo, sendo: fieldLine, fieldNumber: TSynTableFieldProperties;

  7. Assim, ficando – fieldLine   := T.Table['Line'];

  8. Para o segundo campo, ficando – fieldNumber := T.Table['Number'];

  9. Definir uma variável do tipo TSynTableData, para acessar os campos em um conjunto de registros;

  10. Inicializar a variável definida acima – data.Init(T.Table);

  11. Popular os campos recém-criados, como no nosso exemplo: data.SetFieldValue(fieldLine, strList.Strings[i]);  //more faster than:  data.Field['Line'] := strList.Strings[i];

  12.  E também com o segundo campo –  data.SetFieldValue(fieldNumber, lineCounter);       //more faster than:  data.Field['Number'] := lineCounter;

  13.  Comando para criar os dados nos campos:  T.RecordAdd(data);

  14. E finalmente, para salvar (como um “commit”) – T.UpdateToFile;

  15. Para terminar, logicamente como em toda variável destruí-la como em T.Free (usamos FreeAndNil(T)).

Na prática, o código-fonte ficaria assim:

 

var
T: TSynBigTableRecord;
                      data: TSynTableData;
                      fieldLine, fieldNumber: TSynTableFieldProperties;
                      rec: TSynTableData;
{ … } T := TSynBigTableRecord.Create(FN,'TableName'); DeleteTempDataBaseFile;
// T.Clear;
T.AddField('Line',tftWinAnsi,[tfoIndex]);
T.AddField('Number',tftInt32,[tfoIndex]);
T.AddFieldUpdate;
 
fieldLine   := T.Table['Line'];
fieldNumber := T.Table['Number'];
data.Init(T.Table); { ... } data.SetFieldValue(fieldLine, strList.Strings[i]);
 //more faster than:  data.Field['Line'] := strList.Strings[i];
data.SetFieldValue(fieldNumber, lineCounter);
 //more faster than:  data.Field['Number'] := lineCounter;
T.RecordAdd(data);
{ … }

if T.CurrentInMemoryDataSize>400 shl 20 then // write on disk every 400 MB
begin
     T.Pack;
      T.UpdateToFile;
 end;

 

O processo básico de leitura com TSynBigTableRecord é o seguinte:

  1. Criar arquivo temporário;

  2. Processar leitura linha a linha;

  3. Alocar memória pra elas; 

  4. Gravar o fluxo de dados em arquivo a cada 400MB;

  5. Finalizar a operação e liberar a memória alocada;

  6. Remover arquivo temporário.

Porque TSynBigTableRecord?

Porque é a melhor escolha para leitura linha a linha de arquivos grandes. Outras demais tradicionais alternativas e outras desenvolvidas por terceiros frustraram todas as minhas expectativas (como disse no início deste artigo) pelo fato de estourar a memória e de travar a aplicação.
Qual o tempo gasto para ler um arquivo de 2GB?
Depende da memória utilizada para outros processos, quantos programas estão abertos e consumindo memória razoável, etc – mas com meus testes com 3GB de RAM obtive para arquivos de 2.2GB um tempo médio de 2 minutos, podendo variar até para 2,30 minutos.

Posso ler arquivos com mais de 2GB?

Sim, claro. O tempo vai subir um pouco; uma média de 1 minuto por GB, mas sem problemas desde que a memória suporte. Aparentemente nossa solução contempla facilmente até 4GB de arquivos (embora não testado – testado até com 2.2GB) – mas provavelmente não haverá problema para estes limites de até 4GB. Além deste tamanho deve ser realizados testes para observar se também será possível sua leitura.
Assim sendo, realizar o processamento de um arquivo grande utilizando nossas consagradas soluções como a nossa TListView virtual utilizando a nossa estrutura TLineReaderList para arquivos de até 1.5GB e a classe TSynBigTableRecord para arquivos maiores que 1.5GB será a nossa solução em definitivo.

 

Fator

Arquivos até 1.5GB

Arquivos maiores que 1.5GB

Velocidade

Até 30 segundos

2 minutos e meio

Estrutura

TLineReaderList

TSynBigTableRecord

Memória

Full

Utiliza a memória e transfere dela para um arquivo temporário

Dependência Externa

Nenhuma

Um arquivo temporário como o buffer dos registros, disponível apenas durante a execução do aplicativo

Eficiência

Sim

Sim

Praticidade

Ótima; pode-se repetir a operação quantas vezes forem necessárias e desde que a memória suporte

Ótima; pode-se repetir a operação quantas vezes forem necessárias e desde que a memória suporte

 

Esta classe é incrivelmente rápida; sua leitura é eficiente e invejável, lendo e salvando em um arquivo externo como o buffer da operação de leitura e armazenamento temporário das linhas de um arquivo superior a 1.5GB. A operação de leitura poderá ser repetida quantas vezes forem necessárias – e com louvável resultado de processamento, eficiente e razoável – levando em média dois minutos. E esse será o tempo de espera – mas o que são dois minutos para arquivos com mais de 2GB (nos testes, para ser mais exato – 2.2GB)?

Os requisitos básicos para o funcionamento (vamos abordar sobre a classe TSynBigTableRecord) desse processo é a permissão de gravação no diretório do aplicativo, espaço em disco e disponibilidade de memória. Basicamente isso é tudo, por enquanto. Atendendo a estes três itens fundamentais a probabilidade de se concluir a operação com sucesso será alta. Claro que se deve observar é o tempo de espera (abordado no item “velocidade” da tabela descrita acima) – pois  isso é tudo dependente de memória – portanto quando disse “dois minutos” é apenas uma média – e isso pode variar, dependendo da memória disponível e a quantidade de programas abertos no momento, se há algum aplicativo consumindo consideravelmente espaço em memória, etc – o tempo poderá demorar mais que isso. Para isso espere alguns segundos e refaça a operação, feche quantos programas puder e veja se não há algum programa “pesado” neste instante.



Figura 01 – Mensagem de confirmação de sucesso da operação de leitura.



Figura 02 – Tela do aplicativo FastFile onde mostra que um arquivo de 2.2GB foi carregado com sucesso.


Figura 03 – Outra tela do aplicativo FastFile, desta vez exibindo as últimas linhas de um arquivo de 2.2GB carregado com sucesso.

 


Figura 04 – Exibição (primeiro arquivo a direita) do arquivo FASTFILE (sem extensão), que é um arquivo temporário utilizado no buffer dos registros das linhas processadas pelo TSynBigTableRecord para arquivos maiores que 2GB.


Figura 05 – Tela do aplicativo FastFile onde mostra que um arquivo de 1.2GB foi carregado com sucesso; neste caso foi utilizada a estrutura baseada em TList, onde por ser bem rápido seu acesso o tempo estimado para abrir o arquivo foi de 24 segundos.

 


Figura 06 – Mensagem de confirmação da leitura – para o arquivo de 1.2GB levando 24 segundos. Para até 1.5GB geralmente tende a ser mais rápido.
 

Algumas considerações a serem observadas pela classe SynBigTable:

  1. Ela utiliza internamente uma estrutura chamada TFileBufferReader, que é baseada em arquivos mapeados em memória (Memory Mapped Files) para arquivos até 2GB – no comentário deste fonte é informado que o Windows tem problemas para alocar memória para maior que 2GB, pelo menos na versão de 32 bits. Ainda informa que as vezes o Windows também pode falhar para alocar para mais que 512 MB para um mapa de memória (memory map). Neste caso a leitura direta será realizada, sendo mais lenta porém sem limites de tamanho de arquivo. É uma importante observação, escrita na definição da classe TFileBufferReader na unit SynCommons;

  2. Os dados podem ser acessados por um CD/DVD, onde ficarão em memória. Poderá inclusive manipular estes dados, como incluir novos, editar, etc; só que neste caso não serão salvos;

  3. Com poucos KB será criado um arquivo que será a persistência destes dados, para inúmeros registros;

  4. A classe TSynBigTableRecord permitirá gravar um milhão de registros com tempobem rápido – inferior a 900ms (utilizando índices e métodos rápidos de pesquisa);

  5. Pode-se gravar também campos blob, figuras, etc;

  6. Alternativa fácil de compartilhamento de dados entre Delphi 7 e Delphi XE;

  7. Pode ser utilizado para implementar um log ou trilha de auditoria em sua aplicação;

  8. Não é indicado para substituir um banco de dados existente;

  9. Não suporta SQL;

  10. Não interage com componentes DBExpress bem como demais componentes de acesso a dados;

  11. Para um banco de dados com SQL é recomendado um framework também desenvolvido por eles chamado SQLite3;

  12. Os dados são armazenados em um arquivo único;

  13. A recuperação é muito rápida neste arquivo;

  14. Os dados são acrescentados no final deste arquivo, usando um cache para a adição imediata;

  15. Enquanto o método UpdateToFile não é chamado, os dados permanecem na memória;

  16. A propriedade CurrentInMemoryDataSize contém o tamanho da memória preenchida com estes dados (em bytes) – enquanto eles não forem salvos. Com esta propriedade é possível definir um “ritmo” de salvamento destes dados para o arquivo, por exemplo: Em cada 200MB, transferir os dados para o arquivo, salvando-o;

  17. Utiliza funções desenvolvidas por eles como, por exemplo, a “JSONEncodeArray” – para conversão dos dados para o formato JSON;

  18. Utiliza também o tipo criado por eles chamado “RawUTF8” – um tipo de string UTF-8 armazenado em um AnsiString (de acordo com os comentários do código-fonte: “Usar esse tipo em vez de System.UTF8String,  pois houve mudanças entre o compilador Delphi 2009 e versões anteriores: a nossa implementação é consistente e compatível com todas as versões do compilador Delphi”) Declarado na unit SynCommons.

  19. E por fim, também utiliza o tipo criado por eles para o formato Unicode, chamado RawUnicode, que segundo eles é mais rápido que o tipo WideString (unit SynCommons).

Segue abaixo o código-fonte da procedure “ProcessFile” – a mais importante do FastFile, onde toda a lógica de implementação da leitura dos arquivos é realizada, utilizando as classes TLineReaderList e a SynBigTable:

 

procedure TfrmMain.ProcessFile(const StrFilename: String);
var
  StrmInput: TFileStream;
  StrmOutput: TMemoryStream;
  FileNumber: Integer;
  PartFileSize: integer;
  OriginalFileSize: Int64;
  TotalPartsToDivide: Integer;
  i: integer;
  strList: TStringList;
  lcur_temp: TCursor;
begin
  if (StrFilename = '') then Exit;
  if not (SysUtils.FileExists(StrFilename)) then Exit;
  tempFolder := Self.ExtractName(SysUtils.ExtractFileName(StrFilename));
  PathDirectory    := (ExtractFilePath(StrFilename) + tempFolder);
  gpContainer.Caption    := 'File Contents';

  {if FileExists(Self.GetLocalDirectory + cTempDataBaseFile) then
  begin
    DeleteFile(Self.GetLocalDirectory + cTempDataBaseFile);
  end;
  }

  DestroyInMemoryObjects;
  DestroyTLineReaderListObjects;
  TrimAppMemorySize;

  FLineReaderList := TLineReaderList.Create;

  GetTempDataBaseFile;

  T := TSynBigTableRecord.Create(FN,'TableName');

  DeleteTempDataBaseFile;
 // T.Clear;
  T.AddField('Line',tftWinAnsi,[tfoIndex]);
  T.AddField('Number',tftInt32,[tfoIndex]);
  T.AddFieldUpdate;
 
  fieldLine   := T.Table['Line'];
  fieldNumber := T.Table['Number'];
  data.Init(T.Table);

  try
    if SysUtils.DirectoryExists(PathDirectory) then SysUtils.RemoveDir(PathDirectory);
    SysUtils.CreateDir(PathDirectory);
  except
    on E:Exception do
      Raise Exception.Create(E.Message);
  end;

  //FileSize := Round(Biblioteca.GetFileSize(StrFilename) / NumberOfParts);
  try
    OriginalFileSize   := Self.GetFileSize(StrFilename);
    PartFileSize       := NumberOfPartsToDivide(OriginalFileSize);
    TotalPartsToDivide := System.Round(OriginalFileSize/PartFileSize);

  except
    on E:Exception do
      Raise Exception.Create(E.Message);
  end;

  if (Self.RoundUpWithDecimais(OriginalFileSize/PartFileSize, 2)) > (TotalPartsToDivide) then
    Inc(TotalPartsToDivide);

  ProgressBar.Max    := TotalPartsToDivide;

  lblOriginalFileSize.Caption      := 'Original FileSize: ' + FormatFloat('#,', Self.RoundUpWithDecimais(OriginalFileSize/1024, 2)) + ' KB';
  lblPartFileSize.Caption          := 'Part FileSize: '     + FormatFloat('#,', Self.RoundUpWithDecimais(PartFileSize/1024, 2)) + ' KB';
  lblNumberOfParts.Caption         := 'Number of Parts: '   + FormatFloat('#,', TotalPartsToDivide);

  FileNumber           := 1;
  ProgressBar.Position := 0;
  Tag                  := 0;
  lineCounter          := 0;
  memoLog.Clear;
  stLines.Caption      := '';

  bFileIsUpTo1_5_GB := (OriginalFileSize > 1500000000);

  Self.ActionComponent([ListView1, ScrollBar1], acNotVisible);
  strList := TStringList.Create;

  StrmInput := TFileStream.Create(StrFilename,fmOpenRead or fmShareDenyNone);

  Self.ActionComponent([btnCancel], acEnabled);
  Self.ActionComponent([btnReadToVirtualListView, btnHelp], acNotEnabled);
  lcur_temp := frmMain.f_set_cursor(crHourGlass);

  try
    while (StrmInput.Position < StrmInput.Size) and (Tag = 0) and (ProgressBar.Position < FileNumber) do

begin
     // SequentialFile := ChangeFileExt((ExtractFilePath(StrFilename) +
     //   IncludeTrailingPathDelimiter(tempFolder) + ExtractFileName(StrFilename)),'.'+Format('%.03d',[FileNumber]));

      //StrmOutput := TFileStream.Create(SequentialFile ,fmCreate);// ==> Don´t use: it´s very slow !!

      StrmOutput := TMemoryStream.Create;
      try
        if StrmInput.Size - StrmInput.Position < PartFileSize then
          PartFileSize := StrmInput.Size - StrmInput.Position;

        StrmOutput.CopyFrom(StrmInput,PartFileSize);
         // strList.Clear; ==> Don´t use: it´s very slow !!
        strList.Text := Self.MemoryStreamToString(StrmOutput);

        for i := 0 to strList.Count - 1 do
        begin
          if (bFileIsUpTo1_5_GB) then
          begin
            data.SetFieldValue(fieldLine, strList.Strings[i]);  //more faster than:  data.Field['Line'] := strList.Strings[i];
            data.SetFieldValue(fieldNumber, lineCounter);       //more faster than:  data.Field['Number'] := lineCounter;
            T.RecordAdd(data);
          end
          else
            FLineReaderList.Add(TLineReader.Create(strList.Strings[i], lineCounter));

          Inc(lineCounter);
        end;

        ProgressBar.Position := ProgressBar.Position + 1;  

        Application.ProcessMessages;
      finally
        FreeAndNil(StrmOutput);
      end;
      Inc(FileNumber);

     if (bFileIsUpTo1_5_GB) then
       if T.CurrentInMemoryDataSize>400 shl 20 then // write on disk every 400 MB
       begin
         T.Pack;
         T.UpdateToFile;
       end;
    end;
  finally

    FreeAndNil(StrmInput);
    FreeAndNil(strList);
    Self.ActionComponent([btnCancel], acNotEnabled);
    Self.ActionComponent([btnReadToVirtualListView, btnHelp], acEnabled);
    frmMain.f_set_cursor(lcur_temp);
  end;
end;

  
Acima: Transcrição do fonte da função “ProcessFile”.

 

Testando Performance

Foram realizados alguns testes para medir a velocidade de execução de inserção de 1.000.000 de itens dinamicamente, a fim de certificar o componente/estrutura com o melhor tempo de resposta. A ideia é aproveitar para novos releases do projeto e outros temas, bem como ilustrar nossa implementação visto que as estruturas utilizadas na nossa construção também fazem parte destes testes.
É importante a medição deste teste, pois podemos saber o componente/estrutura que melhor se adapta a cada situação. Por exemplo, se precisarmos armazenar certo número de itens em memória, se precisamos persistir estes dados para posterior recuperação, enfim, negocialmente qual a melhor proposta viável. Depende sempre do que será implementado para cada situação.

 

Objetivo do teste:
Contar de zero a um  milhão e popular um campo com este contador. Simples.

Foram testados os seguintes componentes e estruturas:

  1. TRxMemoryData (componente do pacote RXLib);

  2. THVDataSet (componente baseado em TMemoryStream);

  3. TClientDataSet (do Delphi mesmo);

  4. TUserList (estrutura baseada em TList);

  5. TSynBigTableRecord (classe do framework SynBigTable).

 



Figura 07 – Tela do nosso aplicativo de performance.
 

 

 
Figura 08 – Mensagem de confirmação informando o vencedor do teste, a estrutura TUserList. Com média de 1 segundo, um milhão de itens foram gerados.

 Segue abaixo a relação dos tempos estimados de cada item testado. Levando em consideração a geração de 1.000.000 de itens dinamicamente. Computador com 4GB de memória RAM e processador 2,50 GHZ. São 5 objetos que foram testados; cada um tem uma ligeira diferença de velocidade. Particularmente acreditava que o TRxMemoryData (pacote RXLIB) fosse um dos mais rápidos devido a sua implementação toda em memória, conforme é informado em seus comentários. Mas frustrou a expectativa por ser demasiadamente lento para este propósito.

 

Tabela comparativa dos Objetos

 

Componente/Estrutura

Velocidade

TRxMemoryData

7 minutos e 10 segundos

THVDataSet

5 segundos

TClientDataSet

5 minutos e 58 segundos

TUserList

80 milésimos de segundo

TSynBigTableRecord

495 milésimos de segundo

 


Figura 09 – Tela inicial do nosso aplicativo de performance.

 

 


Figura 10 – Tela do nosso aplicativo de performance com o teste do clientdataset executado.

 


Figura 11 – Mensagem de confirmação do teste do clientdataset, levando em média quase 6 minutos.

 


Figura 12 – Mensagem de confirmação do teste do TUserlist, levando em média quase 1 segundo. Impressionante a sua velocidade nesta estrutura baseada em TList – por essa razão (ser baseada em uma lista de ponteiros) a velocidade sempre será a mais rápida de todas, armazenando tudo em memória. A desvantagem é que a memória não é ilimitada e por isso esta estrutura poderá estourar a memória – dependendo da quantidade de itens gerados. Mas facilmente chegará a vinte milhões de itens na média de 1 segundo, e assim por diante.

 

Portanto, baseado nos testes podemos observar que o objeto mais rápido de todos foi a nossa estrutura TUserList, a campeã em velocidade, estimando cerca de 1 segundo no máximo para a geração de milhões de registros. Essa estrutura foi a utilizada no nosso aplicativo FastFile para a leitura de arquivos até 1.5GB, batendo na casa de 30 segundos no máximo (se não menos que isso) para sua leitura e exibição destas linhas em um objeto de TListView no modo virtual, sendo ela como a estrutura de suporte para a exibição destes dados.
E se um arquivo a ser lido for superior ao tamanho de 1.5GB a estrutura utilizada para renderizar o objeto TListView virtual será a classe TSynBigTableRecord, que é a segunda colocada em velocidade nos nossos testes – levando alguns segundos a mais, podendo logicamente levar um minuto a mais, dependendo da memória disponível no computador onde está sendo executado o aplicativo no momento, bem como acesso ao disco rígido, aplicativos consumindo memória no momento, etc;
O nosso aplicativo FastFile, como vimos, utiliza os objetos campeões de velocidade. São os mais rápidos encontrados e disponíveis. Enquanto um foi desenvolvido especialmente para este projeto (TUserList) – o outro objeto (TSynBigTableRecord) foi apenas utilizado na nossa implementação; ele é componente de terceiros e open-source.


Conclusão

Este aplicativo foi construído pelo simples fato de que inúmeros desenvolvedores ainda até hoje não dispõe de uma alternativa viável, útil e eficiente de como abrir um arquivo bem grande (em GBs de tamanho) para sua leitura. Muitas classes encontradas tradicionalmente bem como as de terceiros não são tão boas assim – para carregar até uns 300MB a performance quase sempre é comprometida, e sem contar o tamanho além disso. Muito provável a falta de memória é apresentada como exceção pelo Delphi e o travamento da aplicação – e o usuário é frustrado por não abrir este arquivo pesado – e isso é bem comum nos dias de hoje; não temos uma solução de sucesso para esta finalidade.
As outras versões anteriores do nosso FastFile não permitiam abrir arquivos maiores de que 1.5GB de tamanho – ocasionando erro de falta de memória. Até este tamanho era possível perfeitamente – o problema era depois deste valor que sempre apresentava erros de “OutOfMemory”.
Nesta atual versão implementamos um controle eficiente de como abrir arquivos maiores que 1.5GB sem problemas, sem mais limites de tamanho – o que define o limite será a capacidade da memória RAM disponível para tal – abrindo arquivos “gigantes” agora requer um pouco mais de tempo (+/- 2 minutos) para esta operação, porém finalizando sempre com sucesso, associando com a classe TSynBigTableRecord de SynBigTable, onde por sua vez registra em um arquivo temporário todos os dados contidos na memória – então temos agora maior segurança na nossa implementação – estouro de memória será bem mais difícil – pouca memória, pouco espaço em disco, falta de permissão de gravação em disco, muitos programas abertos simultaneamente, etc; são alguns fatores que podem prejudicar o processamento de leitura destes arquivos grandes.
Continuamos apostando na solução de TListView virtual por ser muito prático o seu carregamento dos dados, visto que na verdade este objeto não armazenada nada diretamente, pois não é proprietário dos seus dados, e sim os nossos objetos de apoio definidos e mencionados anteriormente. Eles que manipularão a visualização linha por linha através do número de cada uma delas – e será sempre um procedimento rápido visto que já foi registrado na memória antes.
Portanto, implementamos uma alternativa eficiente, profissional e viável de como abrir um arquivo extremamente grande para sua leitura e exibir seus dados neste objeto de TListView virtual. Por enquanto, é possível apenas visualizar estes dados, não sendo possível alterá-los nem removê-los. O aplicativo apenas contempla a função de carregamento do arquivo em si, a qual é considerada a mais complexa aqui na nossa implementação. Outra observação é que apenas arquivos-texto comuns com caracteres de controle do código ASCII de “início da posição do cursor/nova linha” (Carriage Return + Line Feed = CR+LF) podem ser processados pelo aplicativo. Não é suportado para arquivos binários ou demais extensões oriundas de outros aplicativos.
O aplicativo é muito prático para esta funcionalidade, de forma elegante, rápida e “esperta” o usuário acompanha o progresso da operação de leitura do arquivo (através de um objeto do tipo TProgressBar) – onde a nossa solução poderá sempre ser repetida quantas vezes forem necessárias.
Com isso, trago a terceira versão deste aplicativo promissor para a leitura eficiente e segura em arquivos grandes. Também poderá ser abrir arquivos não tão grandes e pequenos. Mas a ênfase é para os grandes. Com isso encerro esta edição e desejo um forte abraço a todos. Bons estudos e até a próxima!

 

 

 

Sobre o Autor

Hamden Vogel
Analista de Sistemas pós-graduado em Engenharia de Software pela UPIS e Programador Delphi com larga experiência desde 2000, tem desenvolvido e vendido softwares em Delphi para a África e Estados Unidos, além do mercado nacional. Colaborou com dicas e componentes para sites especializados em Delphi. Também desenvolve em outras linguagens como C/C++, ASP, PHP e .NET.


E-mail: suporte@theclub.com.br

The Club - O Maior Clube de programadores do Brasil