Để đạt tốc độ tìm kiếm tệp tương đương với công cụ "Everything", việc khai thác nhật ký USN (Update Sequence Number) là chìa khóa — nhưng điều đó không đồng nghĩa với việc chỉ cần đọc log là xong. Hiệu năng thực tế phụ thuộc vào cách bạn tổ chức dữ liệu, tối ưu đường dẫn truy vấn và đồng bộ hóa giữa luồng xử lý và giao diện người dùng.
Dự án này được xây dựng hoàn toàn bằng C# thuần, không依賴 bất kỳ wrapper Win32 nào (như FindFirstFile hay ReadDirectoryChangesW) và cũng không sử dụng thư viện phân tích USN có sẵn như USNJournalReader. Toàn bộ logic đọc, phân tích và cập nhật nhật ký đều được triển khai từ đầu.
Kiến trúc hai lớp rõ ràng
Hệ thống gồm hai thành phần độc lập nhưng phối hợp nhịp nhàng:
-
Lớp dữ liệu (Indexing & Query Engine) Chịu trách nhiệm: • Đọc và phân tích USN journal theo từng volume • Xây dựng bảng ánh xạ
FileReferenceNumber → FileInfo• Hỗ trợ cập nhật tăng dần khi phát hiện sự kiện thay đổi (tạo, xóa, đổi tên) • Lưu trữ cục bộ dưới dạng file binary được memory-map khi khởi động -
Lớp giao diện (UI Layer) Được triển khai trên nền tảng Avalonia UI, hỗ trợ AOT compilation để tạo single-file executable (~19.4 MB), đảm bảo khởi động nhanh và không phụ thuộc runtime ngoại vi.
Tối ưu hiệu năng tìm kiếm trong bộ nhớ
1. Bộ lọc tiền xử lý dựa trên ký tự đặc trưng
Thay vì so sánh chuỗi đầy đủ cho mọi mục, hệ thống sử dụng một mảng bit 64-bit (ulong) để biểu diễn sự xuất hiện của các ký tự quan trọng trong tên tệp. Mỗi bit đại diện cho một ký tự trong tập con cố định (ví dụ: chữ cái tiếng Anh + số + dấu gạch ngang). Khi tìm kiếm, hệ thống chỉ duyệt những mục có bit mask giao với mask của truy vấn — giúp loại bỏ ~85% mục không liên quan trước khi thực hiện so khớp chính xác.
internal static class CharSignature
{
private const int BitWidth = 64;
private static readonly char[] KeyChars = "abcdefghijklmnopqrstuvwxyz0123456789-_".ToArray();
public static ulong Compute(string input)
{
ulong signature = 0;
if (string.IsNullOrEmpty(input)) return signature;
var lower = input.ToLowerInvariant();
foreach (char c in lower)
{
int idx = Array.IndexOf(KeyChars, c);
if (idx >= 0 && idx < BitWidth)
signature |= 1UL << idx;
}
return signature;
}
public static bool MayMatch(ulong candidate, ulong query) => (candidate & query) == query;
}
2. Tìm kiếm song song với giới hạn đồng bộ hóa
Thay vì chia nhỏ danh sách và dùng Parallel.ForEach, hệ thống áp dụng mô hình batched pipeline: dữ liệu được chia thành các khối cố định (mỗi khối ~50.000 mục), mỗi khối được xử lý bởi một tác vụ Task. Kết quả khớp được đưa vào một ConcurrentQueue<T> duy nhất — tránh lock toàn cục và giảm contention. Với bộ dữ liệu 6.2 triệu tệp, thời gian tìm kiếm trung bình dưới 18ms trên SSD NVMe.
3. Hỗ trợ tìm theo phiên âm (Pinyin) — có thể tắt linh hoạt
Chức năng chuyển đổi tên tệp tiếng Trung sang chuỗi pinyin được triển khai bằng bảng tra cứu tĩnh (không dùng Microsoft.International.Converters), và chỉ kích hoạt khi người dùng nhập ký tự tiếng Trung hoặc bật chế độ "search by pronunciation" trong cài đặt.
Quản lý giao diện không bị giật lag
Virtualized rendering với lazy projection
Danh sách kết quả không bao giờ được tải toàn bộ vào ItemsControl. Thay vào đó, hệ thống duy trì một ObservableCollection<SearchResultView> có độ dài cố định (mặc định 200 phần tử), và mỗi lần tìm kiếm thành công sẽ:
- Gán lại
SourceCollectionnội bộ (làIList<FileInfo>) - Gọi
RefreshRange(startIndex, count)để cập nhật vùng hiển thị - Dữ liệu hiển thị thực tế được sinh ra theo demand qua
ICollectionViewvàIValueConvertercho icon/path
public sealed class SearchResultModel : ReactiveObject
{
private readonly IList<FileInfo> _source;
private int _visibleCount = 200;
private IReadOnlyList<FileInfo> _view;
public IReadOnlyList<FileInfo> VisibleItems => _view ??= _source.Take(_visibleCount).ToArray();
public void UpdateVisibleCount(int newCount)
{
_visibleCount = Math.Max(0, Math.Min(newCount, _source.Count));
_view = null; // force recalc on next access
this.RaisePropertyChanged(nameof(VisibleItems));
}
public SearchResultModel(IList<FileInfo> data) => _source = data;
}
Icon và metadata được lấy đồng bộ, có cache LRU
Mỗi biểu tượng tệp được trích xuất từ shell32.dll bằng SHGetFileInfo thông qua P/Invoke có kiểm soát, nhưng chỉ gọi một lần duy nhất mỗi FileReferenceNumber. Kết quả được lưu trong bộ nhớ đệm LRU với dung lượng tối đa 10.000 mục. Việc đồng bộ hóa này loại bỏ hoàn toàn hiện tượng nhấp nháy khi cuộn danh sách.
Các tính năng mở rộng
- Hotkey toàn cầu: Đăng ký bằng
RegisterHotKey(Win32), hỗ trợ tổ hợp phím tùy chỉnh qua file cấu hình JSON - Lịch sử tìm kiếm: Lưu dưới dạng SQLite embedded, hỗ trợ tìm kiếm ngược và xóa từng mục
- Menu ngữ cảnh mở rộng: Cho phép chạy script PowerShell, sao chép đường dẫn tuyệt đối, hoặc mở thư mục chứa trong File Explorer
- Tự động cập nhật chỉ mục: Phát hiện thay đổi USN qua
DeviceIoControlvới mãFSCTL_QUERY_USN_JOURNAL, không cần polling
Triển khai và phân phối
Toàn bộ dự án được biên dịch bằng .NET 8 SDK với tùy chọn:
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishTrimmed=true /p:PublishAot=true
Kết quả là một file Searcher.exe duy nhất, không yêu cầu cài đặt .NET Runtime, và có thể chạy trực tiếp trên Windows 10 trở lên.