JavaRush /Курсы /C# SELF /Получение информации о файлах и директориях

Получение информации о файлах и директориях

C# SELF
39 уровень , 2 лекция
Открыта

1. Введение

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

Свойство Описание Пример
Полный путь Полное имя файла/папки
C:\data\myfile.txt
Имя Имя без пути
myfile.txt
Расширение .txt, .csv, .jpg и т.д.
.txt
Размер (Bytes) Размер файла в байтах
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() для ленивого перечисления.

2
Задача
C# SELF, 39 уровень, 2 лекция
Недоступна
Перебор содержимого директории
Перебор содержимого директории
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ