簡體   English   中英

如何從Delphi中的文本文件中刪除特定行

[英]How to delete a specific line from a text file in Delphi

我有一個文本文件,其中逐行存儲了用戶信息。 每行的格式為: UserID#UserEmail#UserPassword其中'#'為分隔符。

我試圖使用此編碼來執行任務:

var sl:TStringList;
begin
  sl:=TStringList.Create;
  sl.LoadFromFile('filename');
  sl.Delete(Index);
  sl.SaveToFile('filename');
  sl.free;
end;

但我不確定在“索引”空間放什么。

有什么方法可以接收用戶ID作為輸入,然后從具有此用戶ID的文本文件中刪除文本行? 任何幫助,將不勝感激。

您可以將NameValueSeparator設置為#然后使用IndexOfName查找用戶,只要用戶名是文件中的第一個值即可。

sl.NameValueSeparator := '#';
Index := sl.IndexOfName('455115')

所以在你的例子中,就像這樣

var sl:TStringList;
begin
  sl:=TStringList.Create;
  sl.LoadFromFile('filename');
  sl.NameValueSeparator := '#';
  Index := sl.IndexOfName('455115')
  if (Index  <> -1) then
  begin
      sl.Delete(Index);
      sl.SaveToFile('filename');
  end;
  sl.free;
end;

對於大型文件,這可能會很慢,因為IndexOfName會循環訪問TStringList中的每一行,並依次檢查每個字符串,直到找到匹配項為止。

免責聲明:測試/使用Delphi 2007,Delphi 7可能不同。

我不明白為什么這么多人如此努力。 這很簡單:

function ShouldDeleteLine(const UserID, Line: string): Boolean;
begin    
  // Remember: Pos(Needle, Haystack)
  Result := Pos(UserID + '#', Line) = 1; // always 1-based!
end;

procedure DeleteLinesWithUserID(const FileName, UserID: string);
var
  SL: TStringList;
  I: Integer;
begin
  if not FileExists(FileName) then
    Exit;

  SL := TStringList.Create;
  try
    SL.LoadFromFile(FileName); // Add exception handling for the 
                               // case the file does not load properly.

    // Always work backward when deleting items, otherwise your index
    // may be off if you really delete.
    for I := SL.Count - 1 downto 0 do
      if ShouldDeleteLine(SL[I], UserID) then
      begin
        SL.Delete(I);
        // if UserID is unique, you can uncomment the following line.
        // Break;
      end;
    SL.SaveToFile(FileName);
  finally
    SL.Free;
  end;
end;

正如Arioch所說,如果你保存到相同的文件名,你可能會在保存失敗時丟失數據,所以你可以做類似的事情

SL.SaveToFile(FileName + '.dup');
if FileExists(FileName + '.old') then
  DeleteFile(FileName + '.old');
RenameFile(FileName, FileName + '.old');
RenameFile(FileName + '.dup', FileName);

這使原始文件的備份保持為FileName + '.old'

說明

向后工作

為什么要倒退? 因為如果你有以下物品

A B C D E F G
      ^

並且您在^處刪除該項目,然后以下項目將向下移動:

A B C E F G
      ^

如果你向前迭代,你現在將指向

A B C E F G
        ^

從未檢查過E 如果你倒退,那么你會指出:

A B C E F G
    ^

請注意, EFG已經被檢查過了,所以現在你確實會檢查下一個項目C ,你不會錯過任何一個。 此外,如果向上使用0 to Count - 1 ,並且刪除, Count將減少一個,最后,您將嘗試訪問列表的邊界。 如果您使用Count - 1 downto 0向后工作,則不會發生這種情況。

使用+ '#'

如果附加'#'並測試Pos() = 1 ,您將確保將整個UserID捕獲到分隔符,而不是具有僅包含您要查找的UserID的用戶ID的行。 IOW,如果UserID'velthuis' ,你不想刪除像'rudyvelthuis#rvelthuis01#password''velthuisresidence#vr#password2' ,但你確實要刪除'velthuis#bla#pw3'

例如,在查找用戶名時,出於同樣的原因,您會查找'#' + UserName + '#'

有唯一的方法來實際“從文本文件中刪除一行” - 即創建一個具有更改內容的新文件,以REWRITE它。

所以你最好明確地做。

你不要忘記防止錯誤。 如果發生任何錯誤,您當前的代碼可能只會破壞文件並泄漏內存...

var sl: TStringList;
    s, prefix: string;
    i: integer; okay: Boolean;
    fs: TStream;

begin
  prefix := 'UserName' + '#';
  okay := false;

  fs := nil;
  sl:=TStringList.Create;
  Try   /// !!!!
    sl.LoadFromFile('filename');
    fs := TFileStream.Create( 'filename~new', fmCreate or fmShareExclusive );

    for i := 0 to Prev(sl.Count) do begin
      s := sl[ i ];

      if AnsiStartsStr( prefix, Trim(s) ) then
         continue;  // skip the line - it was our haunted user

      s := s + ^M^J;  // add end-of-line marker for saving to file

      fs.WriteBuffer( s[1], length(s)*SizeOf(s[1]) );  
    end; 
  finally 
    fs.Free;
    sl.Free;
  end;

  // here - and only here - we are sure we successfully rewritten 
  // the fixed file and only no are able to safely delete old file
  if RenameFile( 'filename' , 'filename~old') then
     if RenameFile( 'filename~new' , 'filename') then begin
        okay := true; 
        DeleteFile( 'filename~old' ); 
     end;

  if not okay then ShowMessage(' ERROR!!! ');
end;

注1:查看用戶名檢查是區分大小寫還是忽略大小寫:

注2:在Delphi 7中, SizeOf( s[1] )總是等於1,因為stringAnsiString的別名。 但在較新的Delphi版本中卻沒有。 這可能看起來很乏味和多余 - 但它可能會在未來挽救很多頭痛。 更好的是擁有一個臨時的AnsiString類型變量,如a := AnsiString( s + ^m^J ); fs.WriteBuffer(a[1],Length(a)); a := AnsiString( s + ^m^J ); fs.WriteBuffer(a[1],Length(a));

到目前為止,每個人都建議使用For..Then循環,但是我可以建議重復...而不是

傳統的For..Loop是一個不錯的選擇,但如果你有很長的用戶名列表(它們通常是唯一的),效率可能會很低。 一旦找到並刪除, For循環將繼續,直到列表結束。 如果你有一個小的列表,但如果你有500,000個用戶名,而你想要的那個位於10,000,那么沒有理由繼續超過這一點。

因此,試試這個。

    Function DeleteUser(Const TheFile: String; Const TheUserName: String): Boolean;
    Var
      CurrentLine: Integer;
      MyLines: TStringlist;
      Found: Boolean;
      Eof: Integer;

    Begin

      MyLines := TStringlist.Create;
      MyLines.LoadFromFile(TheFile);

      CurrentLine := 0;
      Eof := Mylines.count - 1; 

      Found := false;

      Repeat 

        If Pos(UpperCase(TheUserName), UpperCase(MyLines.Strings[CurrentLine])) = 1 Then
        Begin

         MyLines.Delete(CurrentLine);
          Found := True;

        End;

        Inc(CurrentLine);

      Until (Found) Or (CurrentLine = Eof); // Jump out when found or End of File

      MyLines.SaveToFile(TheFile);
      MyLines.Free;

      result := Found;
    End;

一旦被調用,該函數返回True或False,表示用戶名已被刪除。

If Not DeleteUsername(TheFile,TheUsername) then
ShowMessage('User was not found, what were you thinking!');

只是為了好玩,這是一個緊湊的解決方案,我喜歡它的可讀性。

const fn = 'myfile.txt';

procedure DeleteUser(id: integer);
var s:string; a:TStringDynArray;
begin
  for s in TFile.ReadAllLines(fn) do
    if not s.StartsWith(id.ToString + '#') then
      a := a + [s];

  TFile.WriteAllLines(fn, a);
end;

顯然,這不是最有效的解決方案。 通過不向數組附加單個項目或通過緩存搜索字符串,可以更快地運行。

要搜索其他字段,您可以使用s.split(['#'])[0]查找用戶名, s.split(['#'])[1]用於發送電子郵件等。

對於那些喜歡單行的人。 這也有效:

const fn = 'users.txt';

procedure DeleteUserRegExp(id: string);
begin
  TFile.WriteAllText(fn,TRegEx.Replace(TFile.ReadAllText(fn),''+id+'\#.*\r\n',''))
end;

說明

  1. 它將文件的內容加載到字符串中。
  2. 該字符串被發送到TRegEx.Replace
  3. 正則表達式搜索用戶名,后跟哈希符號,然后搜索任何字符,然后搜索CRLF。 它用空字符串替換它。
  4. 然后將生成的字符串寫入原始文件

這只是為了好玩,因為我看到了很長的代碼,我認為這可以用一行代碼實現。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM