С выходом версии 2010 в Delphi появился ещё один модуль — IOUtils.pas облегчающий работу с файлами и директориями. Признаться я просто катострофически не люблю работу с файлами в Delphi. Незнаю почему, но всегда напрягало реализовывать поиск по маске, чтение атрибутов и т.д. и т.п. Может из-за этого и решил рассмотреть, что же такого нового и облегчающего мою жизнь приготовили разработчики из Embarcadero.
Вначале обратимся к официальной Wiki Embarcadero и посмотрим, что там пишут про IOUtils:
…IOUtils contains three static classes: TDirectory, TPath and TFile. These classes expose a number of static methods useful for I/O tasks. …
Отлично. Всего три класса, содержащие ряд классовых методов. Начнем по порядку — с TDirectory.
TDirectory — работа с директориями.
Вспомним навскидку, какие операции с директориями обычно приходится выполнять. Мне вспомнились следующие операции:
1. Проверить существование директории;
2. Проверить наличие файлов в директории;
3. Перечислить все файлы в директории;
4. Перечислить вложенные директории;
5. Проверить даты создания/изменения директории;
6. Перечислить все логические диски в системе.
В целом, все эти операции уже давным давно расписаны на форумах, всяческих FAQ по Delphi и т.д. Так что, если потребуется найти «классический» вариант выполнения операции — Google в помощь. А мы посмотрим как можно реализовать всё это с помощью IOUtils.pas.
Как проверить существование директории, создать, удалить или скопировать директорию?
Используя IOUtils.pas проверить существование директории можно следующим образом:
if TDirectory.Exists('full_path_to_dir') then ShowMessage('Directory Exists') else ShowMessage('Directory Not Found');
Соответственно, если директория отсутствует, то её можно создать:
TDirectory.CreateDirectory('path_to_dir');
А если директория существует, то её можно скопировать в другое место:
TDirectory.Copy('source_path','dest_path');
При этом копируется всё содержимое директории. И наконец удалить ненужную директорию:
TDirectory.Delete('path_to_dir');
Как проверить наличие файлов в директории? Как построить дерево каталогов? Поиск по маске. Использование TSearchRec.
Если необходимо получить только факт — есть или нет что-либо в директории, что можно воспользоваться следующим методом:
if TDirectory.IsEmpty(Edit1.Text) then ShowMessage('Directory Empty') else ShowMessage('Directory Not Empty');
При этом, если даже в директории содержится ещё одна пустая папка, то IsEmpty вернет False.
Если нам необходимо перечислить все поддиректории, то здесь в нашем распоряжении сразу несколько вариантов:
1. Простое перичисление всех директорий:
var Dirs: TStringDynArray; ... Dirs:=TDirectory.GetDirectories('path'); ...
Здесь TStringDynArray — динамический массив (array of string). Тип TStringDynArray определен в модуле Types.
2. Поиск директорий по маске
Если простого перечисления недостаточно и необходимо найти только определенные директории, то можно воспользоваться методом GetDirectories, используя маску. В следующем примере в массив попадут все поддиректории, содержищие в названии «ee»:
Drives:=TDirectory.GetDirectories(Edit1.Text,'*ee*');
2. Поиск директорий по уровням
По умолчанию GetDirectories проводит поиск директорий только первого уровня. Как быть, если нам необходимо пройтись по всем вложенным папкам? С появлением IOUtils в Delphi все стало проще. Достаточно использовать опции поиска:
TSearchOption = (soTopDirectoryOnly, soAllDirectories);
Соответсвенно, включив soAllDirectories в GetDirectories мы получим полное дерево каталогов:
Drives:=TDirectory.GetDirectories('path_to_dir','*',TSearchOption(1));
А как быть, если по-мимо простого поиска нам необходимо как-то визуализировать процесс или, например, в процессе поиска учитывать атрибуты директории?
3. Поиск с использованием TFilterPredicate
В этом случае можно воспользоваться процедурной ссылкой:
type TFilterPredicate = reference to function(const Path: string; const SearchRec: TSearchRec): Boolean;
Как видите, в параметрах используется уже давно нам изветный тип TSearchRec, которым мы привыкли пользоваться при поиске файлов/директорий:
TSearchRec = record Time: Integer; Size: Int64; Attr: Integer; Name: TFileName; ExcludeAttr: Integer; {$IFDEF MSWINDOWS} FindHandle: THandle platform; FindData: TWin32FindData platform; {$ENDIF MSWINDOWS} {$IFDEF POSIX} Mode: mode_t platform; FindHandle: Pointer platform; PathOnly: String platform; Pattern: String platform; {$ENDIF POSIX} end;
Посмотрим простой пример поиска директорий с использованием TFilterPredicate. В следующем коде в результат поиска будут включены только те директории, которые будут содержить в названии подстроку ‘api’, поиск будет проводится по всему дереву каталогов:
... function FilterPredicate(const Path: string; const SearchRec: TSearchRec):boolean; begin Result:=pos('api',SearchRec.Name)>0 end; ... Drives:=TDirectory.GetDirectories(Edit1.Text,'*',TSearchOption(1),FilterPredicate);
Так как при использовании TFilterPredicate в нашем распоряжении имеется достаточное количество информации по текущему результату поиска, то помимо простого фильтра поиска, мы можем также подсчитать в процессе поиска размер всех файлов, запомнить какие-либо результаты и т.д. и т.п.
Всего TDirectory содержит шесть переопределенных методов GetDirectories:
class function GetDirectories(const Path: string): TStringDynArray; overload; inline; static; class function GetDirectories(const Path: string; const Predicate: TFilterPredicate): TStringDynArray; overload; inline; static; class function GetDirectories(const Path, SearchPattern: string): TStringDynArray; overload; inline; static; class function GetDirectories(const Path, SearchPattern: string; const Predicate: TFilterPredicate): TStringDynArray; overload; inline; static; class function GetDirectories(const Path, SearchPattern: string; const SearchOption: TSearchOption): TStringDynArray; overload; static; class function GetDirectories(const Path, SearchPattern: string; const SearchOption: TSearchOption; const Predicate: TFilterPredicate): TStringDynArray; overload; static; class function GetDirectories(const Path: string; const SearchOption: TSearchOption; const Predicate: TFilterPredicate): TStringDynArray; overload; static;
Так что, для поиска и перечисления директорий возможностей у нас более, чем досаточно. Аналогичным образом проводится и поиск всех файлов в директории. Поиск файлов проводится с использованием одной из семи классовых функций GetFiles — параметры методов абсолютно идентичны тем, что используются в GetDirectories.
Как получить даты создания, последнего доступа и записи в директорию?
Получим даты создания, последнего доступа и последней записи в директории. Для этого можно воспользоваться следующими методами:
var Date:TDateTime; Date:=TDirectory.GetCreationTime('path');//дата создания Date:=TDirectory.GetLastAccessTime('path');//дата последнего доступа Date:=TDirectory.GetLastWriteTime('path');//дата последнего изменения
Как перечислить все логические диски в системе?
Получить все логические диски теперь можно, выполнив всего лишь одну классовую функцию:
var Drives: TStringDynArray; ... Drives:=TDirectory.GetLogicalDrive
В результате чего мы получим массив, содержащий все логические диски в системе.
И, наконец, ещё три метода, которые могут пригодится нам при работе с директориями:
TDirectory.GetCurrentDirectory
Возвращает текущую директорию. Можно использовать, например, аналог
ExtractFilePath(Application.ExeName)
GetCurrentDirectory возвращает полный путь к директории без последнего ‘\’.
Второй метод:
TDirectory.GetDirectoryRoot('path')
Возвращает корень для директории, т.е. по сути, логический диск на котором располагается директория. Если надо получить родительскую директорию, то используется третий метод:
TDirectory.GetParent('path')
Вот пожалуй в нескольких словах обзор методов TDirectory. В целом можно сказать, что IOUtils.pas может достаточно облегчить нашу работу в плане выполнения каких-то, ставших уже рутинных для нас, операций. Например, обход дерева каталогов, поиск файлов и директорий по маске и т.д. В следующей части посмотрим, что можно сделать с помощью методов TFile.
Если введение Character и TCharacter выглядит естественно (это новый функционал), то большого смысла в новом IOUtils я не наблюдаю: переписали старый код на новый лад. Зачем? Непонятно.
Согласен, нового ничего нет — есть только организация ряда методов в три большие группы. Здесь скорее не новый функционал, а больше… упрощение что ли работы. Мне допустим, всегда было вломы копаться в Delphi по части работы с файлами, организовывать поиск по каталогам, обходить все директории в поисках
кто насрал в тапкигде лежит файл и пр. А тут получается всё сделано — бери и ищи. Что-то типо костыля для особо ленивых, как я :)Наткнулся только что)
На всякий случай, вставлю пять копеек: использование IOUtils вместо старого pascal-функционала облегчает переход к кросс-платформенному коду. Спустя три года введение смотрится необходимым.
P.s.: Влад, спасибо за статью. Как и всегда, приятно и полезно)
balmo, спасибо за отзыв :)
> переписали старый код на новый лад. Зачем? Непонятно.
С классами работать как минимум удобнее. Имхо, было бы здорово, если бы они вообще весь VCL переписали, полностью заменив старые процедуры/функции/простые типы классами. Т.е. конечно, оставив для совместимости весь старый хлам, но позволив работать только с классами как в Java или том же C#.
А как это хозяйство работает с символическими ссылками?
Есть ли способ отличить? Главное — удаление, не лазит ли в глубь?
atruhin, Алексей (Минск) этот пост только первая часть, так что, думаю, что ещё будет время сравнивать, проверять и т.д. Кстати,символические ссылки меня тоже интересуют. Проверим…
Надо будет сравнить с подключенным sysutils & ioutils размер выходного файла
На днях попробовал с помощью IOUtils создать временный файлик,а затем удалить. Споткнулся о множественные проверки имен файлов и длины пути (соотв. константа не актуальна на текущих target-платформах RAD 2010). Так что пока проще обходиться Jcl.
if TDirectory.IsEmpty(Caption) then
ShowMessage(‘Отсутствуют подходящие файлы’)
else
Dirs := TDirectory.GetDirectories(Caption);
Если описать TStringDynArray в Type то выдает ошибку
[DCC Error] Main.pas(98): E2010 Incompatible types: ‘Main.TStringDynArray’ and ‘IOUtils.TStringDynArray’
Видимо, потому что в uses нет модуля Sytem.Types?
Были жалобы, что в Делфи сложно найти какую-то функцию для определенных действий. Собрали в кучу всё, что касается IO. То же самое, отчасти, и в хэлперах к строкам и т. п. В общем — верной дорогой!