C#文本搜索器,文件遍历与关键词匹配,快速查找本地文件内容

C++
在日常开发或资料整理中,我们经常需要在大量本地文件中快速定位含有关键词的文档。虽然系统自带搜索功能,但往往速度慢、不支持复杂匹配,且难以定制。本文将介绍如何用 C# 实现一个轻量级文本搜索器,支持多格式文件遍历、关键词匹配,并具备较好的性能。

🎯 功能设计

我们要实现的搜索器需满足以下需求:
  1. 递归遍历目录:支持指定根目录,递归扫描所有子文件夹。
  2. 灵活过滤文件:可按扩展名(如 .txt, .cs, .md)筛选。
  3. 关键词匹配
    • 区分大小写选项;
    • 返回匹配的行号与内容。
  4. 异常处理:跳过无权限访问的文件/目录。
  5. 简单易用:提供清晰的 API,便于集成或扩展。

📦 核心代码实现

下面是一个完整的 TextSearcher类实现,代码结构清晰,可直接用于项目。
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

/// <summary>
/// 文本搜索结果项
/// </summary>
public class SearchResult
{
    public string FilePath { get; set; }
    public int LineNumber { get; set; }
    public string MatchedLine { get; set; }

    public override string ToString()
        => $"{FilePath}:{LineNumber} -> {MatchedLine?.Trim()}";
}

/// <summary>
/// 文本搜索器,支持多文件遍历与关键词匹配
/// </summary>
public class TextSearcher
{
    /// <summary>
    /// 执行文本搜索
    /// </summary>
    /// <param name="rootDir">搜索根目录</param>
    /// <param name="keywords">关键词列表</param>
    /// <param name="extensions">文件扩展名过滤,如 ".txt", ".cs"</param>
    /// <param name="ignoreCase">是否忽略大小写</param>
    /// <returns>匹配结果集合</returns>
    public IEnumerable<SearchResult> Search(
        string rootDir,
        IEnumerable<string> keywords,
        IEnumerable<string> extensions = null,
        bool ignoreCase = false)
    {
        if (!Directory.Exists(rootDir))
            throw new DirectoryNotFoundException($"目录不存在: {rootDir}");
        
        var comparison = ignoreCase 
            ? StringComparison.OrdinalIgnoreCase 
            : StringComparison.Ordinal;

        // 构建扩展名过滤集合
        var extSet = extensions?
            .Select(ext => ext.StartsWith(".") ? ext : "." + ext)
            .ToHashSet(StringComparer.OrdinalIgnoreCase);

        // 并发收集结果,适用于稍大规模搜索
        var results = new ConcurrentBag<SearchResult>();

        // 获取所有符合条件的文件
        var files = SafeEnumerateFiles(rootDir, "*.*", SearchOption.AllDirectories)
            .Where(filePath => extSet == null || extSet.Contains(Path.GetExtension(filePath)));

        Parallel.ForEach(files, filePath =>
        {
            try
            {
                using var reader = new StreamReader(filePath, Encoding.UTF8);
                int lineNum = 0;
                string line;

                while ((line = reader.ReadLine()) != null)
                {
                    lineNum++;
                    
                    foreach (var keyword in keywords)
                    {
                        if (line.Contains(keyword, comparison))
                        {
                            results.Add(new SearchResult
                            {
                                FilePath = filePath,
                                LineNumber = lineNum,
                                MatchedLine = line
                            });
                            break; // 同一行匹配多个关键词只记录一次
                        }
                    }
                }
            }
            catch (IOException) { /* 跳过无法读取的文件 */ }
            catch (UnauthorizedAccessException) { /* 跳过无权限文件 */ }
        });

        return results;
    }

    /// <summary>
    /// 安全枚举文件,自动跳过无权访问的目录
    /// </summary>
    private IEnumerable<string> SafeEnumerateFiles(
        string directory,
        string searchPattern,
        SearchOption searchOption)
    {
        Queue<string> dirQueue = new Queue<string>();
        dirQueue.Enqueue(directory);

        while (dirQueue.Count > 0)
        {
            string currentDir = dirQueue.Dequeue();
            string[] files = Array.Empty<string>(), subDirs = Array.Empty<string>();

            try
            {
                files = Directory.GetFiles(currentDir, searchPattern);
                if (searchOption == SearchOption.AllDirectories)
                    subDirs = Directory.GetDirectories(currentDir);
            }
            catch (UnauthorizedAccessException) { continue; }
            catch (DirectoryNotFoundException) { continue; }

            foreach (string file in files)
                yield return file;

            if (searchOption == SearchOption.AllDirectories)
                foreach (string subDir in subDirs)
                    dirQueue.Enqueue(subDir);
        }
    }
}

🔍 使用示例

class Program
{
    static void Main(string[] args)
    {
        var searcher = new TextSearcher();

        // 示例:在当前项目的 cs 文件中搜索关键词
        var results = searcher.Search(
            rootDir: @"..\..\..",
            keywords: new[] { "SearchResult", "TextSearcher" },
            extensions: new[] { ".cs" },
            ignoreCase: true
        );

        Console.WriteLine($"找到 {results.Count()} 条匹配:");
        foreach (var r in results.OrderBy(r => r.FilePath).ThenBy(r => r.LineNumber))
            Console.WriteLine(r);
    }
}
输出示例:
找到 12 条匹配:
D:\Project\SearchDemo\SearchResult.cs:1 -> public class SearchResult
D:\Project\SearchDemo\TextSearcher.cs:45 -> public class TextSearcher
...

⚡ 性能与扩展建议

优化方向

  • 并行处理:代码使用了 Parallel.ForEach,对大文件集可明显提升速度。
  • 缓冲区调整:对极大文件可改用缓冲读取减少 IO 次数。
  • 异步支持:若需 UI 不卡顿,可改为 async/await版本。

适用场景

  • 代码库/文档库的内容检索
  • 日志分析与错误排查
  • 个人知识库全文搜索

💡 总结

这个 C# 文本搜索器实现简洁,核心功能约百行代码完成。通过安全的文件枚举和并行处理,既保证了稳定性又兼顾了效率。你可以将其封装为独立工具,或集成到桌面应用中作为内置搜索模块。
如需进一步强化,可考虑加入正则表达式匹配、结果高亮显示或索引持久化等功能,使其更适合大型项目使用。

会员自媒体 C++ C#文本搜索器,文件遍历与关键词匹配,快速查找本地文件内容 https://yuelu1.cn/26281.html

下一篇:

已经没有下一篇了!

相关文章

猜你喜欢