Não apaga todas as linhas desejadas do Memo / processo demora muito
Bom dia, preciso identificar todas as linhas de um Memo que contenha um determinado texto e apagar estas linhas. Porém estou enfrentando dois problemas que não consegui resolver. O primeiro é que ele não apaga todas as linhas que contem o texto indicado preciso rodar umas 3x para que ele realmente apague todas as linhas. O outro problema é que ele demora demais para concluir o processo, ele apaga 1 linha a cada 2~3 segundos mais ou menos.
Dentro do Memo é carregado um arquivo txt com mais ou menos 50k de linhas. O problema acontece com ou sem Thread.
Fiz da seguinte forma.
Dentro do Memo é carregado um arquivo txt com mais ou menos 50k de linhas. O problema acontece com ou sem Thread.
Fiz da seguinte forma.
procedure TFrm_SAD.c870;
begin
TThread.CreateAnonymousThread(
procedure
var i: integer;
begin
for i := 0 to MemoSped.Lines.Count do begin
if (MemoSped.Lines.Strings[i].Contains('|C870|') = True) then begin
MemoSped.Lines.Delete(i);
end;
end;
end
).Start;
end;
Paulo
Curtidas 0
Respostas
Emerson Nascimento
03/02/2021
faça a verificação de trás pra frente, assim você resolve numa única passagem;
quando um resultado for booleano e você precisar utilizar numa condição, não precisa comparar com True ou False. basta avaliar o resultado;
o fato de usar thread no caso acima só traz alguma vantagem se você quiser liberar o programa para uso, porque não há nenhum ganho de performance.
para obter ganho de performance o ideal seria dividir o conteúdo do memo e passar para threads distintas. assim haveria ganho real de performance.
quando um resultado for booleano e você precisar utilizar numa condição, não precisa comparar com True ou False. basta avaliar o resultado;
procedure TFrm_SAD.c870;
begin
TThread.CreateAnonymousThread(
procedure
var i: integer;
begin
for i := MemoSped.Lines.Count downto 0 do
begin
if MemoSped.Lines.Strings[i].Contains('|C870|') then
MemoSped.Lines.Delete(i);
end;
end
).Start;
end;para obter ganho de performance o ideal seria dividir o conteúdo do memo e passar para threads distintas. assim haveria ganho real de performance.
GOSTEI 0
Emerson Nascimento
03/02/2021
outra coisa... no lugar de um TMemo, use um TStrings ou TStringList criado em tempo de execução. acredito que ficará bem mais rápido.
GOSTEI 0
Paulo
03/02/2021
outra coisa... no lugar de um TMemo, use um TStrings ou TStringList criado em tempo de execução. acredito que ficará bem mais rápido.
Só o fato de eu alterar o for de
for i := 0 to MemoSped.Lines.Count do begin
para
for i := MemoSped.Lines.Count downto 0 do begin
Resolveu os dois problemas, deu um ganho considerável de performance e limpou todos os registros. Mas eu preferi testar a dica de usar o TStringList e realmente o desempenho foi muito superior. Usei a Thread apenas com a intenção de que o programa não fique "Não respondendo" como os txt que estou abrindo tem muitas linhas.
Segue o codigo.
procedure TFrm_SAD.C870;
begin
TThread.CreateAnonymousThread(
procedure
var i : integer; Sped: TStrings;
begin
Sped:= TStringList.Create;
for i := 0 to MemoSped.Lines.Count do begin
Sped.Add(MemoSped.Lines.Strings[i]);
end;
for i:= Sped.Count-1 downto 0 do begin
if (Sped.Strings[i].Contains('|C870|') = True) then begin
Sped.Delete(i);
end;
end;
MemoSped.Clear;
MemoSped.Lines:=Sped;
FreeAndNil(Sped);
end
).Start;
end;Muito Obrigado pela ajuda.
GOSTEI 0
Emerson Nascimento
03/02/2021
faça assim:
ou assim:
procedure TFrm_SAD.C870;
begin
TThread.CreateAnonymousThread(
procedure
var i : integer; Sped: TStrings;
begin
Sped := TStringList.Create; // cria o stringlist
// Sped.LoadFromFile( arquivoSped ); //carrega o arquivo diretamente para o Sped ou
Sped.AddStrings(MemoSped.Lines); // carrega todas as linhas do MemoSped para o Sped
for i := Sped.Count-1 downto 0 do
if Sped.Strings[i].Contains('|C870|') then
Sped.Delete(i);
MemoSped.Clear;
MemoSped.Lines.AddStrings( Sped );
FreeAndNil(Sped);
end
).Start;
end;
ou assim:
procedure TFrm_SAD.C870;
begin
TThread.CreateAnonymousThread(
procedure
var i : integer; Sped: TStrings;
begin
Sped:= TStringList.Create;
for i := 0 to MemoSped.Lines.Count-1 do
if not MemoSped.Lines[i].Contains('|C870|') then // inclui apenas as linhas válidas
Sped.Add(MemoSped.Lines.Strings[i]);
MemoSped.Clear;
MemoSped.Lines.AddStrings(Sped);
FreeAndNil(Sped);
end
).Start;
end;GOSTEI 0
Emerson Nascimento
03/02/2021
Você pode adicionar um progress pra indicar pro usuário o quanto do procedimento foi executado.
GOSTEI 0
Paulo
03/02/2021
faça assim:
ou assim:
procedure TFrm_SAD.C870;
begin
TThread.CreateAnonymousThread(
procedure
var i : integer; Sped: TStrings;
begin
Sped := TStringList.Create; // cria o stringlist
// Sped.LoadFromFile( arquivoSped ); //carrega o arquivo diretamente para o Sped ou
Sped.AddStrings(MemoSped.Lines); // carrega todas as linhas do MemoSped para o Sped
for i := Sped.Count-1 downto 0 do
if Sped.Strings[i].Contains('|C870|') then
Sped.Delete(i);
MemoSped.Clear;
MemoSped.Lines.AddStrings( Sped );
FreeAndNil(Sped);
end
).Start;
end;
ou assim:
procedure TFrm_SAD.C870;
begin
TThread.CreateAnonymousThread(
procedure
var i : integer; Sped: TStrings;
begin
Sped:= TStringList.Create;
for i := 0 to MemoSped.Lines.Count-1 do
if not MemoSped.Lines[i].Contains('|C870|') then // inclui apenas as linhas válidas
Sped.Add(MemoSped.Lines.Strings[i]);
MemoSped.Clear;
MemoSped.Lines.AddStrings(Sped);
FreeAndNil(Sped);
end
).Start;
end;Eu tinha feito de um jeito parecido do primeiro exemplo que você passou, mas dava erro, a TStringList ficava com uma linha só por isso fui adicionando uma por uma com o for. Mas desse jeito que você passou deu certo usei ele e retirei o for.
Você pode adicionar um progress pra indicar pro usuário o quanto do procedimento foi executado.
achei interessante, como que faz? não tenho ideia desse.
GOSTEI 0
Emerson Nascimento
03/02/2021
há várias formas de fazer. vou postar uma.
segue o forumulário que apresentará a barra de progresso. como não tem como publicar uma imagem neste fórum, vou colar aqui o conteúdo do formulário de progresso:
salve o conteúdo acima como UnitProgressWindow.dfm
salve o conteúdo acima como UnitProgressWindow.pas
salve os dois arquivos na mesma pasta.
agora pra usar:
adicione a unit UnitProgressWindow na cláusula uses de onde você quer usar a barra de progresso.
um exemplo de uso naquela função da qual trata este tópico:
você também pode chamar a partir da thread, como fez antes, porém o botão ficará liberado para executar varias vezes, com processos sobrepostos.
acredito que, para esse caso, a barra de progresso baste.
segue o forumulário que apresentará a barra de progresso. como não tem como publicar uma imagem neste fórum, vou colar aqui o conteúdo do formulário de progresso:
object FormProgresso: TFormProgresso
Left = 0
Top = 0
BorderIcons = [biMinimize]
BorderStyle = bsSizeToolWin
Caption = 'frmSegundoPlano'
ClientHeight = 92
ClientWidth = 521
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
Position = poDesktopCenter
OnClose = FormClose
OnShow = FormShow
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 8
Top = 16
Width = 31
Height = 13
Caption = 'Label1'
end
object ProgressBar1: TProgressBar
Left = 8
Top = 35
Width = 505
Height = 17
TabOrder = 0
end
object ButtonCancelar: TButton
Left = 438
Top = 58
Width = 75
Height = 25
Caption = 'Cancelar'
TabOrder = 1
OnClick = ButtonCancelarClick
end
endunit UnitProgressWindow;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;
type
TFormProgresso = class(TForm)
ProgressBar1: TProgressBar;
ButtonCancelar: TButton;
Label1: TLabel;
procedure ButtonCancelarClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormShow(Sender: TObject);
private
{ Private declarations }
FPointerTaskWindow: Pointer;
FCaption: string;
FMaxValue: integer;
Rotina: TProc;
FCanceled: boolean;
public
{ Public declarations }
procedure IncProgress(ProgMessage: string = ''; Increment: integer = 1);
property Canceled: boolean read FCanceled;
end;
function CreateProgress(Caption: string; MaxValue: integer; CancelButton: boolean = False): TFormProgresso;
implementation
{$R *.dfm}
function CreateProgress(Caption: string; MaxValue: integer; CancelButton: boolean): TFormProgresso;
var
Form: TFormProgresso;
begin
// cria a tela de progresso e configura os objetos
// com base nos parâmetros recebidos
Form := TFormProgresso.Create(nil);
Form.FCanceled := False;
Form.FCaption := Caption;
Form.FMaxValue := MaxValue;
Form.ButtonCancelar.Visible := CancelButton;
// desabilita todas as telas do programa, exceto a tela de progresso
Form.FPointerTaskWindow := DisableTaskWindows(Form.Handle);
Form.Show;
Form.Repaint;
Result := Form;
end;
{ TFormProgresso }
procedure TFormProgresso.ButtonCancelarClick(Sender: TObject);
begin
// se o botão 'Cancelar' foi pressionado,
// pergunta se o usuário deseja realmente cancelar
FCanceled := (MessageDlg('Cancelar '+FCaption+'?', mtConfirmation, [mbYes, mbNo], 0, mbNo) = mrYes);
end;
procedure TFormProgresso.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// habilita todas as telas do programa
EnableTaskWindows(FPointerTaskWindow);
end;
procedure TFormProgresso.FormShow(Sender: TObject);
begin
// altera o caption do formulário para o
// conteúdo recebido pela função CreateProgress
Self.Caption := FCaption;
// limpa o caption da label1
Label1.Caption := '';
// configura a barra de progresso
ProgressBar1.Position := 0;
ProgressBar1.Max := FMaxValue;
end;
procedure TFormProgresso.IncProgress(ProgMessage: string; Increment: integer);
begin
// incrementa a barra de progresso
// se for passada uma mensagem no primeiro parâmetro,
// ela será exibida logo acima da barra de progressso
// apresenta a mensagem, caso tenha sido recebida
if ProgMessage <> null then
Label1.Caption := ProgMessage;
// se for passado um valor no segundo parâmetro, esse
// será o incremento da barra; se nada for passado,
// o incremento será de uma unidade
// incrementa a barra
ProgressBar1.Position := ProgressBar1.Position + Increment;
// redesenha a barra no formulário
ProgressBar1.Repaint;
// aguarda 5 milissegundos para
// montagem visual da barra de progresso
// esse valor pode ser alterado
Sleep(5);
// permite ao windows processar os eventos e mernsagens do formulário
Application.ProcessMessages;
end;
end.salve os dois arquivos na mesma pasta.
agora pra usar:
adicione a unit UnitProgressWindow na cláusula uses de onde você quer usar a barra de progresso.
um exemplo de uso naquela função da qual trata este tópico:
var
i: integer;
Sped: TStringList;
FormProgress: UnitProgressWindow.TFormProgresso;
begin
// cria a tela de progresso (um formulário com label e progressbar)
FormProgress := UnitProgressWindow.CreateProgress(
'Lendo arquivo SPED', // caption da tela de progresso
MemoSped.Lines.Count-1, // número total de iterações
True // permite cancelar o processo
);
Sped := TStringList.Create;
for i := 0 to MemoSped.Lines.Count-1 do
begin
// incrementa o progresso sem apresentar mensagens
// FormProgress.IncProgress;
// incrementa o progresso mostrando o número da linha atual
FormProgress.IncProgress('Tratando da linha ' + IntToStr(i+1));
// inclui apenas as linhas válidas
if not MemoSped.Lines[i].Contains('|C870|') then
Sped.Add(MemoSped.Lines.Strings[i]);
// verifica se o usuário clicou no botão 'cancelar' na tela de processo
// só faz sentido avaliar o conteúdo se foi passado True para o
// terceiro parâmetro de UnitProgressWindow.CreateProgress()
if FormProgress.Canceled then
break;
// permite ao windows processar os eventos e mensagens do formulário
Application.ProcessMessages;
end;
MemoSped.Clear;
MemoSped.Lines.AddStrings(Sped);
FreeAndNil(Sped);
// fecha a tela de progresso e libera a memória
FormProgress.Close;
FreeAndNil(FormProgress);
end;
você também pode chamar a partir da thread, como fez antes, porém o botão ficará liberado para executar varias vezes, com processos sobrepostos.
acredito que, para esse caso, a barra de progresso baste.
GOSTEI 0