JavaRush /Курси /C# SELF /Отримання інформації про файли та каталоги

Отримання інформації про файли та каталоги

C# SELF
Рівень 39 , Лекція 2
Відкрита

1. Вступ

У Windows (і не тільки) у кожного файлу й каталогу є набір властивостей (метаданих): повний шлях, імʼя, розширення, розмір, дати створення/зміни/доступу, атрибути тощо. Уявіть: файл — це не лише байти, а й ціла анкета, яку можна прочитати за допомогою FileInfo і DirectoryInfo.

Властивість Опис Приклад
Повний шлях Повна назва файлу/каталогу
C:\data\myfile.txt
Імʼя Імʼя без шляху
myfile.txt
Розширення .txt, .csv, .jpg тощо.
.txt
Розмір (байти) Розмір файлу в байтах
4096
Дата створення Коли було створено файл/каталог
2024-04-16 19:30:10
Дата зміни Коли востаннє змінювався вміст
2024-05-01 18:14:02
Атрибути Наприклад, тільки для читання, прихований тощо.
ReadOnly, Hidden
Батьківський каталог Каталог, у якому міститься файл або підкаталог
C:\data

2. Поглиблена робота з властивостями файлів

Детальний аналіз часових міток

Властивості CreationTime, LastWriteTime і LastAccessTime повертають DateTime, а їхня поведінка залежить від файлової системи та операцій із файлом.


var fileInfo = new FileInfo("document.txt");

if (fileInfo.Exists)
{
    Console.WriteLine($"Створено: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
    Console.WriteLine($"Змінено: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
    Console.WriteLine($"Відкривався: {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
    
    // Різниця між створенням і останньою зміною
    var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
    Console.WriteLine($"Файл змінювався протягом: {age.TotalDays:F1} днів");
}

Цікаво, що під час копіювання файлу дата створення зазвичай оновлюється на поточну, але дата зміни може зберегтися від оригіналу. Це важливо для резервного копіювання та аналізу активності.

Робота з розширеннями та іменами файлів

Властивості FullName, Name і Extension здаються простими, але є нюанси: відсутність розширення, складені розширення на кшталт .tar.gz, а також приховані файли, що починаються з крапки.


var files = new[]
{
    new FileInfo("document.txt"),
    new FileInfo("archive.tar.gz"),
    new FileInfo("README"),
    new FileInfo(".gitignore")
};

foreach (var file in files)
{
    Console.WriteLine($"Повне імʼя: {file.FullName}");
    Console.WriteLine($"Імʼя: {file.Name}");
    Console.WriteLine($"Розширення: '{file.Extension}'");
    
    // Імʼя без розширення
    string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
    Console.WriteLine($"Імʼя без розширення: {nameWithoutExtension}");
    Console.WriteLine("---");
}

Розмір файлів і форматування

Властивість Length повертає розмір у байтах; користувачеві зручніше бачити КБ/МБ/ГБ. Допоміжна функція:


static string FormatFileSize(long bytes)
{
    string[] suffixes = { "Б", "КБ", "МБ", "ГБ", "ТБ" };
    int counter = 0;
    decimal number = bytes;
    
    while (Math.Round(number / 1024) >= 1)
    {
        number /= 1024;
        counter++;
    }
    
    return $"{number:N1} {suffixes[counter]}";
}

// Використання
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
    Console.WriteLine($"Розмір файлу: {FormatFileSize(file.Length)}");
}

3. Розширена робота з атрибутами файлів

Атрибути представлені переліченням FileAttributes (набір бітових прапорців), тому у файлу може бути кілька атрибутів одночасно. Перевіряти зручно через HasFlag.


var fileInfo = new FileInfo("important.txt");

Console.WriteLine($"Атрибути файлу: {fileInfo.Attributes}");

// Перевіряємо конкретні атрибути
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
    Console.WriteLine("Файл прихований!");
}

if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
    Console.WriteLine("Файл захищений від запису!");
}

if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
    Console.WriteLine("Це системний файл!");
}

Атрибути можна змінювати програмно:


// Зробити файл прихованим
fileInfo.Attributes |= FileAttributes.Hidden;

// Прибрати атрибут «тільки для читання»
fileInfo.Attributes &= ~FileAttributes.ReadOnly;

// Встановити кілька атрибутів одразу
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;

4. Розширена робота з каталогами

Пошук файлів за шаблонами

Методи GetFiles() та GetDirectories() приймають шаблони й допомагають фільтрувати вміст.


var dir = new DirectoryInfo(@"C:\Projects");

if (dir.Exists)
{
    // Знайти всі текстові файли
    var textFiles = dir.GetFiles("*.txt");
    Console.WriteLine($"Знайдено текстових файлів: {textFiles.Length}");
    
    // Знайти всі файли, що починаються з "temp"
    var tempFiles = dir.GetFiles("temp*");
    
    // Знайти всі файли зображень
    var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
    var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
    
    Console.WriteLine($"Знайдено зображень: {allImages.Length}");
}

Рекурсивний пошук у підкаталогах

Для обходу всіх підкаталогів використовуйте SearchOption.AllDirectories.


var dir = new DirectoryInfo(@"C:\Development");

// Знайти всі файли C# у всіх підкаталогах
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($"Усього знайдено файлів .cs: {csharpFiles.Length}");

// Показати перші 10 файлів із їхніми шляхами
foreach (var file in csharpFiles.Take(10))
{
    Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}

Аналіз вмісту каталогу

Приклад зведеного аналізу: кількість файлів/каталогів, загальний розмір, розподіл за розширеннями та топ-5 найбільших файлів.


static void AnalyzeDirectory(DirectoryInfo dir)
{
    if (!dir.Exists)
    {
        Console.WriteLine("Каталогу не існує!");
        return;
    }
    
    var files = dir.GetFiles();
    var subdirs = dir.GetDirectories();
    
    Console.WriteLine($"Аналіз каталогу: {dir.FullName}");
    Console.WriteLine($"Файлів: {files.Length}, Підкаталогів: {subdirs.Length}");
    
    if (files.Length == 0)
    {
        Console.WriteLine("Файлів не знайдено.");
        return;
    }
    
    long totalSize = files.Sum(f => f.Length);
    Console.WriteLine($"Загальний розмір файлів: {FormatFileSize(totalSize)}");
    
    // Групування за розширеннями
    var byExtension = files.GroupBy(f => f.Extension.ToLower())
                           .OrderByDescending(g => g.Sum(f => f.Length));
    
    Console.WriteLine("\nРозподіл за типами файлів:");
    foreach (var group in byExtension)
    {
        string ext = string.IsNullOrEmpty(group.Key) ? "(без розширення)" : group.Key;
        long groupSize = group.Sum(f => f.Length);
        Console.WriteLine($"  {ext}: {group.Count()} файлів, {FormatFileSize(groupSize)}");
    }
    
    // Топ-5 найбільших файлів
    var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
    Console.WriteLine("\nНайбільші файли:");
    foreach (var file in largestFiles)
    {
        Console.WriteLine($"  {file.Name}: {FormatFileSize(file.Length)}");
    }
}

5. Оптимізований підрахунок розміру каталогу

Для великих каталогів замість GetFiles() використовуйте ліниві перелічувачі EnumerateFiles()/EnumerateDirectories(), обробляйте винятки й за бажанням показуйте прогрес.


static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
    long totalSize = 0;
    int fileCount = 0;
    var inaccessibleDirs = new List<string>();
    
    try
    {
        // Використовуємо EnumerateFiles для великих каталогів (ліниве перелічування)
        foreach (var file in dir.EnumerateFiles())
        {
            try
            {
                totalSize += file.Length;
                fileCount++;
                
                if (showProgress && fileCount % 1000 == 0)
                {
                    Console.WriteLine($"Оброблено файлів: {fileCount}, розмір: {FormatFileSize(totalSize)}");
                }
            }
            catch (UnauthorizedAccessException)
            {
                // Файл недоступний, пропускаємо
            }
            catch (IOException)
            {
                // Проблеми з читанням файлу, пропускаємо
            }
        }
        
        // Рекурсивно обробляємо підкаталоги
        foreach (var subdir in dir.EnumerateDirectories())
        {
            try
            {
                totalSize += GetDirectorySizeAdvanced(subdir, showProgress);
            }
            catch (UnauthorizedAccessException)
            {
                inaccessibleDirs.Add(subdir.FullName);
            }
        }
    }
    catch (UnauthorizedAccessException)
    {
        inaccessibleDirs.Add(dir.FullName);
    }
    
    if (inaccessibleDirs.Any() && showProgress)
    {
        Console.WriteLine($"Недоступних каталогів: {inaccessibleDirs.Count}");
    }
    
    return totalSize;
}

// Використання
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("Починаємо підрахунок розміру каталогу...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"Загальний розмір: {FormatFileSize(size)}");

6. Практичні застосування метаданих

Пошук файлів за датою

Шукаємо файли, змінені у заданий період (зручно для прибирання, аналізу або аудиту).


static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
    Console.WriteLine($"Пошук файлів від {fromDate:yyyy-MM-dd} до {toDate:yyyy-MM-dd}");
    
    var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
                           .Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
                           .OrderByDescending(f => f.LastWriteTime);
    
    Console.WriteLine($"Знайдено файлів: {matchingFiles.Count()}");
    
    foreach (var file in matchingFiles.Take(20))
    {
        Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
    }
}

// Приклад: знайти всі файли, змінені за останній тиждень
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);

Пошук дублікатів файлів

Швидкий метод — згрупувати за розміром. Для точності можна додати порівняння хешів вмісту.


static void FindPotentialDuplicates(DirectoryInfo dir)
{
    Console.WriteLine($"Пошук потенційних дублікатів у {dir.FullName}");
    
    var files = dir.GetFiles("*", SearchOption.AllDirectories)
                   .Where(f => f.Length > 0) // Вилучаємо порожні файли
                   .GroupBy(f => f.Length)
                   .Where(g => g.Count() > 1) // Лише групи з кількома файлами
                   .OrderByDescending(g => g.Key); // Сортуємо за розміром
    
    foreach (var sizeGroup in files.Take(10))
    {
        Console.WriteLine($"\nФайли розміром {FormatFileSize(sizeGroup.Key)} ({sizeGroup.Count()} файлів):");
        foreach (var file in sizeGroup)
        {
            Console.WriteLine($"  {file.FullName}");
            Console.WriteLine($"    Змінено: {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
        }
    }
}

Моніторинг змін у каталозі

Показуємо файли, змінені за останні N хвилин.


static void MonitorRecentChanges(DirectoryInfo dir, int minutesBack = 60)
{
    var cutoffTime = DateTime.Now.AddMinutes(-minutesBack);
    
    var recentFiles = dir.GetFiles("*", SearchOption.AllDirectories)
                         .Where(f => f.LastWriteTime > cutoffTime)
                         .OrderByDescending(f => f.LastWriteTime);
    
    Console.WriteLine($"Файли, змінені за останні {minutesBack} хвилин:");
    
    if (!recentFiles.Any())
    {
        Console.WriteLine("Змін не знайдено.");
        return;
    }
    
    foreach (var file in recentFiles)
    {
        var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
        Console.WriteLine($"{file.Name} - {minutesAgo:F0} хв тому ({FormatFileSize(file.Length)})");
    }
}

7. Робота з батьківськими каталогами

Властивість Directory у FileInfo і Parent у DirectoryInfo дозволяють підніматися ієрархією.


var file = new FileInfo(@"C:\Projects\MyApp\src\Program.cs");

Console.WriteLine($"Файл: {file.Name}");
Console.WriteLine($"Каталог: {file.Directory.Name}");
Console.WriteLine($"Батьківський каталог: {file.Directory.Parent.Name}");
Console.WriteLine($"Кореневий каталог проєкту: {file.Directory.Parent.Parent.Name}");

// Можна підніматися ієрархією до кореня
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
    Console.WriteLine($"Рівень: {currentDir.Name}");
    currentDir = currentDir.Parent;
}
Console.WriteLine($"Корінь: {currentDir.Name}");

8. Пастки й типові помилки

1. Кешування. Обʼєкти FileInfo і DirectoryInfo кешують значення. Якщо обʼєкт змінювався після створення, дані можуть застаріти. Використовуйте Refresh() для актуалізації.


var file = new FileInfo("test.txt");
file.Refresh(); // Оновлення метаданих

2. Винятки доступу. Деякі файли й каталоги недоступні: обробляйте UnauthorizedAccessException та інші помилки доступу.


try
{
    var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Немає доступу.");
}

3. Часові мітки. Під час копіювання CreationTime може змінитися, а LastWriteTime — зберегтися. Це впливає на звіти та алгоритми синхронізації.


File.Copy("a.txt", "b.txt");

4. Продуктивність. GetFiles() завантажує все одразу й може гальмувати на великих каталогах. Краще використовуйте EnumerateFiles() для лінивого перелічування.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ