Как удалить определенную строку из текстового файла в Делфи
у меня есть текстовый файл с информацией о пользователе, хранящейся в нем строка за строкой. Каждая строка имеет формат: UserID#UserEmail#UserPassword
с разделителем"#".
Я попытался использовать эту кодировку для выполнения задачи:
var sl:TStringList;
begin
sl:=TStringList.Create;
sl.LoadFromFile('filename');
sl.Delete(Index);
sl.SaveToFile('filename');
sl.free;
end;
но я не уверен, что поместить в пространство "индекс".
есть ли способ получить идентификатор пользователя в качестве входных данных, а затем удалить строку текста из текстового файла с этим идентификатором пользователя? Любая помощь будет оценена.
6 ответов
вы можете установить NameValueSeparator
to #
затем используйте 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;
как Ариох там говорит, Если вы сохраните с тем же именем файла, вы рискуете потерять свои данные при этом сэкономить не удастся, так что вы можете сделать что-то вроде
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
^
отметим, что E
, F
и G
были осмотрены уже, так что теперь вы действительно рассмотрите следующий пункт,C
и вы не пропустите ни. Также, если вы идите вверх, используя 0 to Count - 1
и удалить Count
станет одним меньше, и в конце вы попытаетесь получить доступ за границу списка. Это не может произойти, если вы работаете в обратном направлении, используя Count - 1 downto 0
.
используя + '#'
если добавить '#'
и тест для Pos() = 1
, вы будете уверены, чтобы поймать всю UserID
до разделителя, а не строки с идентификатором пользователя, который только содержит the UserID
вы ищете. IOW, if UserID
is 'velthuis'
, вы не хотите, чтобы удалить строки 'rudyvelthuis#rvelthuis01#password'
или 'velthuisresidence#vr#password2'
, а вы хотите удалить 'velthuis#bla#pw3'
.
Е. Г. при поиске имени пользователя, вы ищите '#' + UserName + '#'
по той же причине.
есть единственный способ "удалить строку из текстового файла" - то есть создать новый файл с измененным содержимым, чтобы переписать его.
поэтому вам лучше просто сделать это явно.
и не забывайте о защите от ошибок. Ваш текущий код может просто уничтожить файл и утечку памяти, если произойдет какая-либо ошибка...
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: см., если проверка имени пользователя должна быть чувствительной к регистру или случай-игнорирование:
- http://www.freepascal.org/docs-html/rtl/strutils/ansistartsstr.html
- http://www.freepascal.org/docs-html/rtl/strutils/ansistartstext.html
примечание 2: в Delphi 7 SizeOf( s[1] )
всегда равно единице, потому что string
- это псевдоним для AnsiString
. Но в более новой версии Delphi это не так. Это может показаться утомительным и избыточным, но это может сэкономить много головной боли в будущем. Еще лучше бы быть иметь временное AnsiString
введите переменную типа a := AnsiString( s + ^m^J ); fs.WriteBuffer(a[1],Length(a));
до сих пор все предлагали использовать для For..Тогда цикл, но могу ли я предложить повторить..В то время как.
традиционный 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;
объяснение
- он загружает содержимое файла в строку.
- строка отправляется в TRegEx.Заменить
- регулярное выражение ищет имя пользователя, за которым следует знак хэша, затем любой символ, а затем CRLF. Он заменяет его пустой строкой.
- полученная строка затем записывается в оригинал файл
Это просто для удовольствия, потому что я увидел длинный код, где я думал, что это будет возможно с одной строки кода.