Удалить строку из текстового файла delphi

Обновлено: 05.07.2024

Когда речь идет о работе программы с текстовым файлом, подразумеваются процедуры ввода данных из файла в программу или, наоборот, запись этих данных в файл программой. Для текстового файла допустима простая работа с файлом без особых дополнительных механизмов, которые применяются для работы со специализированными файлами, такими как при загрузке данных из Excel или работе программы с базой данных. Разумеется, Delphi располагает возможностями работать с файлами с использованием компонентов. Но в данной статье рассматривается механизм прямой работы с текстовым файлом без использования дополнительных компонентов.

Итак, в общем виде, работа с файлом заключается в следующих этапах:

1. подключение к файлу – связь с внешним файлом, указание режима подключения;

2. выполнение операций записи в файл или чтения из файла;

3. завершение работы с файлом.

Подключение к файлу

Для связи программы с файлом используется специальная переменная – "Файловая переменная". Объявляется она так же как и любая переменная в Delphi. Тип это переменной может быть File для типизированных (хранящих данные определенного типа) файлов, а можно указать TextFile, что будет означать тип обычного текстового файла. Объявление переменной:

В исполняемом коде программы выполняется подключение к внешнему файлу:

Команда AssignFile, выполняет связь файловой переменной с внешним файлом. Вторым параметром указывается адрес файла. Он может быть задан относительным или абсолютным. Если указать только имя файла, то программа будет пытаться обнаружить его в той же директории, где она сама и находится. Абсолютный путь указывается от корневого диска:

Использование относительной директории дает возможность не привязываться к конкретным дискам и адресам. Например:

После того как выполнено подключение, выполняется процедура, устанавливающая режим работы с файлом. В основном это режим чтения или записи. Эти режимы назначаются процедурами Reset() ( для чтения) и rewrite() (для записи):

* Для команды Rewrite() следует учитывать, что при ее выполнении, она либо создает файл, указанный в файловой переменной, либо если он уже есть перезаписывает файл заново, удаляя старый без какого-то предупреждения.

Любую из указанных команд можно использовать без команды AssignFile(). Для этого достаточно вторым параметром указать путь к файлу. Таким образом, она сразу выполнит привязку файла к файловой переменной и назначит режим работы с этим файлом:

Операции с файлами

Для чтения из файла, необходимо назначить режим чтения и использовать команду Readln(), которая будет вводить в строковую переменную по одной строке из файла. Затем с этой переменой можно выполнить необходимые действия.

Обычно для загрузки всех строк из файла используется оператор цикла. Для того, чтобы определить, что файл закончился используется функция EOF() (End Of File). Таким образом получается цикл, в котором последовательно в строковую переменную вводятся все строки файла и завершающийся после окончания фала:

Для записи, назначение режим записи в файл и командой Writeln() производится запись по строкам.

Закрытие файла

По завершении работы с файлами, особенно в случае записи в них данных, необходимо корректно завершить работу с файловой переменной. Это делается для того, чтобы сохранить все внесенные в файл изменения.

Примеры работы с текстовыми файлами в Delphi

Чтение в переменную одного значения из файла:

Загрузить все строки файла в компонент Memo:

Следует отметить, что для этой задачи проще воспользоваться командой самого компонента Memo LoadFromFile().

Записать строку в файл:

Записать в текстовый файл все строки из компонента Memo:

Как и для чтения из файла в Memo, так и здесь, имеется специальная команда:

Я пробовал использовать эту кодировку для выполнения задачи:

Но я не уверен, что поместить в «индексное» пространство.

Есть ли способ получить идентификатор пользователя в качестве ввода, а затем удалить строку текста из текстового файла, в котором есть этот идентификатор пользователя? Любая помощь будет оценена.

Итак, в вашем примере, вот так

Это может быть медленным для больших файлов, так как IndexOfName проходит цикл через каждую строку в TStringList и проверяет каждую строку по очереди, пока не найдет совпадение.

Отказ от ответственности: Протестировано / работает с Delphi 2007, Delphi 7 может отличаться.

Для тех, кто любит однострочники. Это тоже работает:

Объяснение

  1. Он загружает содержимое файла в строку.
  2. Строка отправляется в TRegEx.Replace
  3. Регулярное выражение ищет имя пользователя, за которым следует знак решетки, затем любой символ, а затем CRLF. Он заменяет его пустой строкой.
  4. Полученная строка затем записывается в исходный файл.

Это просто для удовольствия, потому что я видел длинный код, в котором я думал, что это возможно с помощью одной строки кода.

До сих пор все предлагали использовать цикл For..Then , но могу ли я предложить Повторить .. Пока .

Традиционный For..Loop - хороший вариант, но может быть неэффективным, если у вас длинный список имен пользователей (обычно они уникальны). После обнаружения и удаления цикл For продолжается до конца списка. Это нормально, если у вас небольшой список, но если у вас 500 000 имен пользователей и одно из них, которое вам нужно, находится на позиции 10 000, нет причин для продолжения после этого момента.

Поэтому попробуйте это.

После вызова функция возвращает True или False, указывая, что имя пользователя было удалено или нет.

Есть единственный способ «удалить строку из текстового файла» - это создать новый файл с измененным содержимым, ПЕРЕЗАПИСАТЬ его.

Так что лучше просто сделайте это явно.

И не забывайте о защите от ошибок. Ваш текущий код может просто уничтожить файл и утечку памяти, если произойдет какая-либо ошибка .

Примечание 1. Посмотрите, должна ли проверка имени пользователя быть чувствительной к регистру или без учета регистра:

Примечание 2: в Delphi 7 SizeOf( s[1] ) всегда равно единице, потому что string является псевдонимом для AnsiString . Но в более новой версии Delphi это не так. Это может показаться утомительным и излишним, но в будущем может избавить от головной боли. Еще лучше было бы иметь временную переменную типа AnsiString , например a := AnsiString( s + ^m^J ); fs.WriteBuffer(a[1],Length(a));

Ради интереса вот компактное решение, которое мне нравится за удобочитаемость.

Очевидно, это не самое эффективное решение. Это может работать быстрее, если не добавлять отдельные элементы в массив или кэшировать строку поиска.

Я не понимаю, почему так много людей делают это так сложно. Это довольно просто:

Как говорит Arioch'The, если вы сохраните файл с тем же именем, вы рискуете потерять свои данные при сбое сохранения, поэтому вы можете сделать что-то вроде

Это сохраняет резервную копию исходного файла как FileName + '.old' .

Пояснения

Работаем в обратном направлении

Зачем работать в обратном направлении? Потому что, если у вас есть следующие предметы

И вы удаляете элемент в ^ , тогда следующие элементы сдвинутся вниз:

Если вы выполните итерацию вперед, вы теперь укажете на

И E никогда не проверяется. Если вы вернетесь назад, вы укажете на:

Обратите внимание, что E , F и G уже были проверены, так что теперь вы действительно изучите следующий элемент, C , и не пропустите ни одного. Кроме того, если вы пойдете вверх с помощью 0 to Count - 1 и удалите, Count станет на единицу меньше, и в конце вы попытаетесь получить доступ за границу списка. Этого не может произойти, если вы работаете в обратном направлении, используя Count - 1 downto 0 .

Я работаю с текстовым файлом в Delphi, и я не хочу использовать метод загрузки / сохранения со списком строк. Я намерен поддерживать открытый файловый поток, в котором я читаю и записываю туда свои данные, сохраняя огромные объемы данных на жестком диске, а не в памяти. У меня есть простая концепция записи новых строк в текстовый файл и их чтения, но когда дело доходит до их изменения и удаления, я не могу найти никаких хороших ресурсов.

Каждая строка в этом файле содержит имя и знак равенства, а остальное - данные. Например, SOMEUNIQUENAME=SomeStringValue . Я собираюсь держать файл открытым в течение некоторого времени внутри потока. Этот поток выполняет входящие запросы на получение, установку или удаление определенных полей данных. Я использую WriteLn и ReadLn в цикле, оценивая EOF . Ниже приведен пример того, как я читаю данные:

У меня есть процедуры, готовые для записи и удаления этих полей, но я не знаю, что мне нужно сделать, чтобы на самом деле выполнить действие с файлом.

В конце концов, мне нужно избегать загрузки всего файла в память, чтобы иметь возможность сделать это.

3 ответа

Мне это интересный вопрос, поэтому я сделал небольшое консольное приложение.

Я использовал 3 метода:

  • TStringList
  • Streamreader / StreamWriter
  • Текстовый файл

Все методы рассчитаны по времени и повторяются 100 раз с текстовым файлом размером 10 КБ и текстовым файлом размером 1 МБ. Вот программа:

Как видите, победителем здесь является TStringList. Поскольку вы не можете использовать TStringList, TextFile в конце концов не плохой выбор .

P.S. : этот код опускает часть, где вы должны удалить входной файл и переименовать выходной файл в исходное имя файла

Без загрузки всего файла в контейнер, например TStringList , ваш единственный вариант:

  • Откройте файл для ввода
  • Открыть отдельную копию для вывода
  • Начать цикл
  • Прочитать содержимое построчно из входного файла
  • Записывайте содержимое построчно в выходной файл, пока не дойдете до строки, которую хотите изменить / удалить.
  • Разорвать петлю
  • Прочитать строку ввода из входного файла
  • Запишите измененную строку (или пропустите запись строки, которую вы хотите удалить) в выходной файл
  • Начать новый цикл
  • Прочтите оставшуюся часть введенного содержимого построчно
  • Запишите остальную часть этого ввода в выходной файл, строка за строкой.
  • Разорвать петлю
  • Закройте файлы

Итак, чтобы ответить на ваши конкретные вопросы:

Запишите новый вывод во второй (выходной) файл.

Просто пропустите WriteLn , который выводит указанную строку во второй (выходной) файл.

Ваше искусственное ограничение «Я не хочу использовать TStringList» просто усложняет вам задачу, когда вы можете просто:

  • Загрузите исходный файл в TStringList с помощью LoadFromFile
  • Найдите строку, которую хотите изменить, по индексу, итерации или IndexOf()
  • Измените строку, изменив ее напрямую или удалив из TStringList
  • Запишите все содержимое в исходный файл с помощью TStringList.SaveToFile

Я обнаружил, что единственные причины, по которым я не использовать TStringList для выполнения таких операций, заключаются в том, что размер файла превышает емкость TStringList (никогда не было) или когда работа с файлом, который является текстовым, но на самом деле не ориентирован на «строку» (например, файлы EDI, которые обычно представляют собой одну очень длинную одиночную строку текста, или файлы XML, которые могут не содержать переводы строк и, следовательно, также являются одной очень длинной одиночной строкой строка текста). Однако даже в случае EDI или XML довольно часто их загружают в TStringList , выполняют преобразование в строковый формат (вставляя разрывы строк или что-то еще) и выполняют поиск из списка строк.

По сути, вы не можете делать то, что хотите, если обрабатываете файлы как простые текстовые файлы. Такие файлы могут быть прочитаны (только с начала) или записаны (либо с начала, создавая таким образом новый файл), либо с конца (добавление к существующему файлу). Это не файлы с произвольным доступом.

С другой стороны, вы можете захотеть определить файл со строковым типом: каждая запись в файле будет строкой, и вы можете получить доступ к этому файлу случайным образом. Тогда проблема заключается в том, чтобы узнать, к какой записи и к какой строке обращаться.

Третья возможность - использовать файлы INI, которые более структурированы и кажутся более подходящими для ваших целей. Помимо заголовка раздела, они представляют собой серию строк, ключ = значение, и к ним можно получить доступ на основе ключа.

Я попытался использовать эту кодировку для выполнения задачи:

но я не уверен, что поместить в пространство "индекс".

есть ли способ получить идентификатор пользователя в качестве входных данных, а затем удалить строку текста из текстового файла с этим идентификатором пользователя? Любая помощь будет оценена.

так в вашем примере, вот так

это может быть медленно для больших файлов, как циклы IndexOfName, хотя каждая строка в TStringList и проверяет каждую строку по очереди, пока не найдет совпадение.

отказ от ответственности: протестировано / работает с Delphi 2007, Delphi 7 может быть различным.

я не понимаю, почему так много людей делают это так сложно. Это довольно просто:

как Ариох там говорит, Если вы сохраните с тем же именем файла, вы рискуете потерять свои данные при этом сэкономить не удастся, так что вы можете сделать что-то вроде

это сохраняет резервную копию исходного файла как FileName + '.old' .

объяснениями

работает назад

зачем работать в обратном направлении? Потому что если у вас есть следующие пункты

и вы удаляете элемент в ^ , то следующие элементы будут смещаться вниз:

если вы повторите вперед, теперь вы укажете на

и E никогда не исследовал. Если вы пойдете назад, то вы укажете на:

отметим, что E , F и G были осмотрены уже, так что теперь вы действительно рассмотрите следующий пункт, C и вы не пропустите ни. Также, если вы идите вверх, используя 0 to Count - 1 и удалить Count станет одним меньше, и в конце вы попытаетесь получить доступ за границу списка. Это не может произойти, если вы работаете в обратном направлении, используя Count - 1 downto 0 .

есть единственный способ "удалить строку из текстового файла" - то есть создать новый файл с измененным содержимым, чтобы переписать его.

поэтому вам лучше просто сделать это явно.

и не забывайте о защите от ошибок. Ваш текущий код может просто уничтожить файл и утечку памяти, если произойдет какая-либо ошибка.

Примечание 1: см., если проверка имени пользователя должна быть чувствительной к регистру или случай-игнорирование:

примечание 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 нет смысла продолжать дальше.

поэтому попробуйте это.

после вызова функция возвращает True или False, указывая, что имя пользователя было удалено или нет.

просто для удовольствия, вот компактное решение, которое мне нравится за его читаемость.

очевидно, это не самое эффективное решение. Это может выполняться быстрее, не добавляя отдельные элементы в массив или кэшируя строку поиска.

для тех, кто любит шутки. Это тоже работает:

объяснение

  1. он загружает содержимое файла в строку.
  2. строка отправляется в TRegEx.Заменить
  3. регулярное выражение ищет имя пользователя, за которым следует знак хэша, затем любой символ, а затем CRLF. Он заменяет его пустой строкой.
  4. полученная строка затем записывается в оригинал файл

Это просто для удовольствия, потому что я увидел длинный код, где я думал, что это будет возможно с одной строки кода.

Читайте также: