CodeGym /Các khóa học /C# SELF /Lấy thông tin về tập tin và thư mục

Lấy thông tin về tập tin và thư mục

C# SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Trong Windows (và không chỉ vậy) mỗi tập tin và thư mục có một tập thuộc tính (metadata): đường dẫn đầy đủ, tên, phần mở rộng, kích thước, ngày tạo/ghi/ truy cập, các thuộc tính, v.v. Hãy tưởng tượng: tập tin không chỉ là các byte mà còn là một hồ sơ mà ta có thể đọc bằng FileInfoDirectoryInfo.

Thuộc tính Mô tả Ví dụ
Đường dẫn đầy đủ Tên đầy đủ của tập tin/thư mục
C:\data\myfile.txt
Tên Tên không kèm đường dẫn
myfile.txt
Phần mở rộng .txt, .csv, .jpg, v.v.
.txt
Kích thước (Bytes) Kích thước tập tin tính theo byte
4096
Ngày tạo Khi tập tin/thư mục được tạo
2024-04-16 19:30:10
Ngày sửa Khi nội dung được thay đổi lần cuối
2024-05-01 18:14:02
Thuộc tính Ví dụ: chỉ đọc, ẩn, v.v.
ReadOnly, Hidden
Thư mục cha Thư mục chứa tập tin/thư mục
C:\data

2. Làm việc sâu hơn với thuộc tính tập tin

Phân tích chi tiết các dấu thời gian

Các thuộc tính CreationTime, LastWriteTime, LastAccessTime trả về DateTime và hành vi của chúng phụ thuộc vào hệ thống tập tin và các thao tác trên tập tin.


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

if (fileInfo.Exists)
{
    Console.WriteLine($"Tạo: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
    Console.WriteLine($"Đã sửa: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
    Console.WriteLine($"Đã mở: {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
    
    // Chênh lệch giữa thời điểm tạo và lần sửa cuối
    var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
    Console.WriteLine($"Tập tin đã được chỉnh sửa trong: {age.TotalDays:F1} ngày");
}

Điều thú vị là khi copy tập tin, ngày tạo thường được cập nhật thành hiện tại, nhưng ngày sửa có thể được giữ lại từ bản gốc. Điều này quan trọng cho backup và phân tích hoạt động.

Làm việc với phần mở rộng và tên tập tin

Các thuộc tính FullName, NameExtension có vẻ đơn giản, nhưng có những tình huống đặc biệt: thiếu phần mở rộng, phần mở rộng ghép như .tar.gz và các tập tin ẩn bắt đầu bằng dấu chấm.


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($"Đường dẫn đầy đủ: {file.FullName}");
    Console.WriteLine($"Tên: {file.Name}");
    Console.WriteLine($"Phần mở rộng: '{file.Extension}'");
    
    // Tên không có phần mở rộng
    string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
    Console.WriteLine($"Tên không có phần mở rộng: {nameWithoutExtension}");
    Console.WriteLine("---");
}

Kích thước tập tin và định dạng

Thuộc tính Length trả về kích thước theo byte; người dùng thường muốn nhìn KБ/МБ/ГБ. Hàm trợ giúp:


static string FormatFileSize(long bytes)
{
    string[] suffixes = { "B", "KB", "MB", "GB", "TB" };
    int counter = 0;
    decimal number = bytes;
    
    while (Math.Round(number / 1024) >= 1)
    {
        number /= 1024;
        counter++;
    }
    
    return $"{number:N1} {suffixes[counter]}";
}

// Sử dụng
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
    Console.WriteLine($"Kích thước tập tin: {FormatFileSize(file.Length)}");
}

3. Nâng cao làm việc với thuộc tính tập tin

Các thuộc tính được biểu diễn bằng enum FileAttributes (tập các flag bit), vì vậy một tập tin có thể có nhiều thuộc tính cùng lúc. Kiểm tra thuận tiện bằng HasFlag.


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

Console.WriteLine($"Thuộc tính của tập tin: {fileInfo.Attributes}");

// Kiểm tra các thuộc tính cụ thể
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
    Console.WriteLine("Tập tin bị ẩn!");
}

if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
    Console.WriteLine("Tập tin chỉ đọc!");
}

if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
    Console.WriteLine("Đây là tập tin hệ thống!");
}

Có thể thay đổi thuộc tính chương trình:


// Làm tập tin thành ẩn
fileInfo.Attributes |= FileAttributes.Hidden;

// Bỏ thuộc tính "chỉ đọc"
fileInfo.Attributes &= ~FileAttributes.ReadOnly;

// Gán nhiều thuộc tính cùng lúc
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;

4. Nâng cao làm việc với thư mục

Tìm tập tin theo mẫu

Các phương thức GetFiles()GetDirectories() nhận mẫu và giúp lọc nội dung.


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

if (dir.Exists)
{
    // Tìm tất cả tập tin văn bản
    var textFiles = dir.GetFiles("*.txt");
    Console.WriteLine($"Tìm được tập tin văn bản: {textFiles.Length}");
    
    // Tìm tất cả tập tin bắt đầu bằng "temp"
    var tempFiles = dir.GetFiles("temp*");
    
    // Tìm tất cả tập tin ảnh
    var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
    var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
    
    Console.WriteLine($"Tìm được ảnh: {allImages.Length}");
}

Tìm đệ quy trong các thư mục con

Để duyệt tất cả thư mục con dùng SearchOption.AllDirectories.


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

// Tìm tất cả file C# trong mọi thư mục con
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($"Tổng số file .cs tìm được: {csharpFiles.Length}");

// Hiển thị 10 file đầu tiên với đường dẫn
foreach (var file in csharpFiles.Take(10))
{
    Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}

Phân tích nội dung thư mục

Ví dụ phân tích tổng hợp: số file/thư mục, tổng kích thước, phân bố theo phần mở rộng và top-5 file lớn nhất.


static void AnalyzeDirectory(DirectoryInfo dir)
{
    if (!dir.Exists)
    {
        Console.WriteLine("Thư mục không tồn tại!");
        return;
    }
    
    var files = dir.GetFiles();
    var subdirs = dir.GetDirectories();
    
    Console.WriteLine($"Phân tích thư mục: {dir.FullName}");
    Console.WriteLine($"Files: {files.Length}, Subdirectories: {subdirs.Length}");
    
    if (files.Length == 0)
    {
        Console.WriteLine("Không tìm thấy tập tin.");
        return;
    }
    
    long totalSize = files.Sum(f => f.Length);
    Console.WriteLine($"Tổng kích thước tập tin: {FormatFileSize(totalSize)}");
    
    // Nhóm theo phần mở rộng
    var byExtension = files.GroupBy(f => f.Extension.ToLower())
                           .OrderByDescending(g => g.Sum(f => f.Length));
    
    Console.WriteLine("\nPhân bố theo loại tập tin:");
    foreach (var group in byExtension)
    {
        string ext = string.IsNullOrEmpty(group.Key) ? "(không có phần mở rộng)" : group.Key;
        long groupSize = group.Sum(f => f.Length);
        Console.WriteLine($"  {ext}: {group.Count()} files, {FormatFileSize(groupSize)}");
    }
    
    // Top-5 file lớn nhất
    var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
    Console.WriteLine("\nCác tập tin lớn nhất:");
    foreach (var file in largestFiles)
    {
        Console.WriteLine($"  {file.Name}: {FormatFileSize(file.Length)}");
    }
}

5. Tối ưu hoá tính kích thước thư mục

Với thư mục lớn thay vì GetFiles() hãy dùng các iterator lazy EnumerateFiles()/EnumerateDirectories(), xử lý ngoại lệ và nếu muốn hiển thị tiến độ.


static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
    long totalSize = 0;
    int fileCount = 0;
    var inaccessibleDirs = new List<string>();
    
    try
    {
        // Dùng EnumerateFiles cho thư mục lớn (tải theo lazy)
        foreach (var file in dir.EnumerateFiles())
        {
            try
            {
                totalSize += file.Length;
                fileCount++;
                
                if (showProgress && fileCount % 1000 == 0)
                {
                    Console.WriteLine($"Đã xử lý file: {fileCount}, kích thước: {FormatFileSize(totalSize)}");
                }
            }
            catch (UnauthorizedAccessException)
            {
                // File không truy cập được, bỏ qua
            }
            catch (IOException)
            {
                // Lỗi đọc file, bỏ qua
            }
        }
        
        // Đệ quy xử lý thư mục con
        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($"Số thư mục không truy cập được: {inaccessibleDirs.Count}");
    }
    
    return totalSize;
}

// Sử dụng
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("Bắt đầu tính kích thước thư mục...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"Tổng kích thước: {FormatFileSize(size)}");

6. Ứng dụng thực tế của metadata

Tìm tập tin theo ngày

Tìm file đã được sửa trong khoảng thời gian cho trước (tiện để dọn dẹp, phân tích hoặc audit).


static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
    Console.WriteLine($"Tìm file từ {fromDate:yyyy-MM-dd} đến {toDate:yyyy-MM-dd}");
    
    var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
                           .Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
                           .OrderByDescending(f => f.LastWriteTime);
    
    Console.WriteLine($"Tìm thấy file: {matchingFiles.Count()}");
    
    foreach (var file in matchingFiles.Take(20))
    {
        Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
    }
}

// Ví dụ: tìm tất cả file được sửa trong tuần trước
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);

Tìm file trùng lặp

Cách nhanh — nhóm theo kích thước. Để chính xác hơn có thể thêm so sánh hash nội dung.


static void FindPotentialDuplicates(DirectoryInfo dir)
{
    Console.WriteLine($"Tìm các file có thể trùng lặp trong {dir.FullName}");
    
    var files = dir.GetFiles("*", SearchOption.AllDirectories)
                   .Where(f => f.Length > 0) // Bỏ qua file rỗng
                   .GroupBy(f => f.Length)
                   .Where(g => g.Count() > 1) // Chỉ nhóm có nhiều file
                   .OrderByDescending(g => g.Key); // Sắp theo kích thước
    
    foreach (var sizeGroup in files.Take(10))
    {
        Console.WriteLine($"\nCác file có kích thước {FormatFileSize(sizeGroup.Key)} ({sizeGroup.Count()} cái):");
        foreach (var file in sizeGroup)
        {
            Console.WriteLine($"  {file.FullName}");
            Console.WriteLine($"    Đã sửa: {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
        }
    }
}

Giám sát thay đổi trong thư mục

Hiển thị file đã thay đổi trong N phút gần đây.


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($"Các file thay đổi trong {minutesBack} phút gần đây:");
    
    if (!recentFiles.Any())
    {
        Console.WriteLine("Không tìm thấy thay đổi.");
        return;
    }
    
    foreach (var file in recentFiles)
    {
        var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
        Console.WriteLine($"{file.Name} - {minutesAgo:F0} phút trước ({FormatFileSize(file.Length)})");
    }
}

7. Làm việc với thư mục cha

Thuộc tính Directory của FileInfoParent của DirectoryInfo cho phép leo lên theo cấu trúc.


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

Console.WriteLine($"File: {file.Name}");
Console.WriteLine($"Thư mục: {file.Directory.Name}");
Console.WriteLine($"Thư mục cha: {file.Directory.Parent.Name}");
Console.WriteLine($"Thư mục gốc của project: {file.Directory.Parent.Parent.Name}");

// Có thể leo lên đến root
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
    Console.WriteLine($"Cấp: {currentDir.Name}");
    currentDir = currentDir.Parent;
}
Console.WriteLine($"Root: {currentDir.Name}");

8. Cạm bẫy và lỗi thường gặp

1. Cache. Các đối tượng FileInfoDirectoryInfo có cache dữ liệu. Nếu đối tượng thay đổi sau khi tạo, dữ liệu có thể lỗi thời. Dùng Refresh() để cập nhật.


var file = new FileInfo("test.txt");
file.Refresh(); // Cập nhật metadata

2. Ngoại lệ truy cập. Một số file và thư mục không truy cập được: xử lý UnauthorizedAccessException và các lỗi truy cập khác.


try
{
    var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Không có quyền truy cập");
}

3. Dấu thời gian. Khi copy CreationTime có thể thay đổi, còn LastWriteTime — có thể được giữ lại. Điều này ảnh hưởng tới báo cáo và thuật toán đồng bộ.


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

4. Hiệu năng. GetFiles() nạp toàn bộ ngay lập tức và có thể chậm trên thư mục lớn. Ưu tiên EnumerateFiles() để liệt kê lazy.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Duyệt nội dung thư mục
Duyệt nội dung thư mục
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION