并发单词频率统计器 - 从零到完整实现(C# 实战)

一、项目背景与目标

在工业自动化中,经常需要处理日志文件、设备数据记录,并统计关键字出现频率。本项目的目标是:

  • 批量并发创建文本文件(支持用户指定多个文件名)
  • 交互式追加内容(逐个询问是否写入英文句子)
  • 统计所有文件的单词频率(支持按文件、按单词查询)
  • 用户交互查询(输入文件名和单词,返回出现次数)

通过这个项目,熟悉 C# 异步编程、LINQ、正则表达式、字典操作等核心技能。

二、整体流程设计

三、核心代码与语法解析

3.1 异步创建文件(并发)

使用 Task.WhenAll 并发创建多个文件,提高效率。

csharp 复制代码
private static async Task<List<string>> createTxt()
{
    // 确保目录存在
    if (!Directory.Exists(DEFAULT_PATH))
        Directory.CreateDirectory(DEFAULT_PATH);

    while (true)
    {
        Console.Write("Please input text type as you want, you can input more:>>>");
        string? input = Console.ReadLine();
        if (string.IsNullOrWhiteSpace(input)) continue;

        string[] filesName = input.Split(',', StringSplitOptions.RemoveEmptyEntries)
                                  .Select(s => s.Trim())
                                  .ToArray();
        var textList = new List<Task<string>>();

        foreach (string text in filesName)
        {
            if (!parityText(text)) continue;               // 文件名校验
            if (File.Exists(Path.Combine(DEFAULT_PATH, text)))
            {
                Console.WriteLine("File is Existing");
                continue;
            }
            textList.Add(createAsyncFile(text));           // 每个文件一个异步任务
        }

        if (textList.Count == 0) continue;

        string[] files = await Task.WhenAll(textList);     // 并发等待所有文件创建完成
        List<string> fileList = files.Where(f => f != null)
                                     .Select(f => Path.Combine(DEFAULT_PATH, f))
                                     .ToList();
        Console.WriteLine($"所有文件创建完成,共 {textList.Count} 个。");
        return fileList;
    }
}

private static async Task<string> createAsyncFile(string text)
{
    try
    {
        string default_content = $"// Created:{DateTime.Now:yyyy-MM-dd HH:mm:ss} \n// Author: Dong Wang  ";
        await File.WriteAllTextAsync(Path.Combine(DEFAULT_PATH, text), default_content);
        lock (_lock) Console.WriteLine($"Created successfully!!!! File Name:{text}");
        return text;
    }
    catch (Exception ex)
    {
        lock (_lock) Console.WriteLine($"{ex.Message}");
        return null;
    }
}

语法点:

  • async Task<string> 表示异步方法,返回 string
  • await Task.WhenAll(textList) 等待所有任务完成。
  • lock (_lock) 保证控制台输出不混乱。

3.2 正则分割单词(统计词频)

csharp 复制代码
private static Dictionary<string, int> GetTextsFromFile(string fileReal)
{
    string text = File.ReadAllText(Path.Combine(DEFAULT_PATH, fileReal)).ToLower();
    // 按非单词字符分割(空格、标点、换行等)
    string[] words = Regex.Split(text, @"\W+");
    var wordCount = new Dictionary<string, int>();
    foreach (string w in words)
    {
        if (string.IsNullOrEmpty(w)) continue;
        string word = w.ToLower();
        if (wordCount.ContainsKey(word))
            wordCount[word]++;
        else
            wordCount[word] = 1;
    }
    return wordCount;
}

关键正则: \W+ 匹配一个或多个非单词字符,相当于分割符。

3.3 并发统计所有文件(使用元组返回文件名和结果)

csharp 复制代码
var tasks = filesReal.Select(async filePath =>
{
    var wordCount = await Task.Run(() => GetTextsFromFile(filePath));
    return (FileName: Path.GetFileName(filePath), WordCount: wordCount);
});
var results = await Task.WhenAll(tasks);
var textWords = results.ToDictionary(r => r.FileName, r => r.WordCount);
  • 每个任务返回元组 (string FileName, Dictionary<string,int> WordCount)
  • Task.WhenAll 等待所有文件统计完毕,返回数组。
  • 使用 ToDictionary 快速构建嵌套字典。

3.4 忽略文件名大小写的字典

csharp 复制代码
Dictionary<string, Dictionary<string, int>> textWords = 
    new Dictionary<string, Dictionary<string, int>>(StringComparer.OrdinalIgnoreCase);

这样用户输入 "HAHA.TXT""haha.txt" 都能匹配。

3.5 用户交互查询

csharp 复制代码
private static void UserInputQueryWord(string v, Dictionary<string, Dictionary<string, int>> textwords)
{
    while (true)
    {
        Console.Write("Please input query word:>>");
        string? input = Console.ReadLine();
        if (input == null) continue;
        input = input.ToLower();
        string[] words = SplitWord(input);
        foreach (string word in words)
        {
            if (textwords.TryGetValue(v, out var wordCount) &&
                wordCount.TryGetValue(word, out int count))
            {
                Console.WriteLine($"该文件 {v} 中的搜索词 {word} 查询出现的频率: {count} 次");
            }
            else
            {
                Console.WriteLine($"该文件 {v} 中的搜索词 {word} 未找到");
            }
        }
        break;
    }
}
  • TryGetValue 安全获取嵌套字典的值。
  • 单词统一转为小写,保证统计一致性。

四、遇到的问题与解决方案

问题描述 原因 解决方案
正则分割后得到很多标点符号 误用 \w+ 作为分隔符 改为 \W+(大写 W)
并发输出控制台内容混乱 多个线程同时调用 Console.WriteLine 使用 lock 同步
文件名大小写导致查询失败 默认字典区分大小写 构造函数传入 StringComparer.OrdinalIgnoreCase
Task.WhenAll 返回数组无法直接转换成嵌套字典 任务只返回字典,丢失文件名 任务返回元组 (FileName, WordCount)
并发统计时重复创建任务 在循环内执行 Select 一次性创建所有任务,再 WhenAll

五、运行示例

复制代码
Please input text type as you want, you can input more:>>>a.txt, b.txt
Created successfully!!!! File Name:a.txt
Created successfully!!!! File Name:b.txt
所有文件创建完成,共 2 个。
是否需要为该文件:...\a.txt,写入内容?Y/N:>>y
请输入你要往...\a.txt 文件传输的内容:>>hello world hello C# is powerful
文件...\a.txt,成功被写入
是否需要为该文件:...\b.txt,写入内容?Y/N:>>n
用户不进行写入,该文件:...\b.txt
Please input the specify file:>a.txt
1 words
文件名:a.txt
Please input query word:>>hello
该文件 a.txt 中的搜索词 hello 查询出现的频率: 2 次
是否退出?(Y/N)::>>y
CreateTxt Method is Done

六、可以继续优化的方向

  • 支持空格分隔文件名:修改 SplitWord 同时支持逗号和空格。
  • 过滤停用词(如 a, an, the, and, of 等)。
  • 导出统计报告到文件(使用 StreamWriter)。
  • 增加全局词频统计(合并所有文件,显示 Top N)。
  • 进度提示:在并发统计时显示当前处理进度。
  • 处理大文件:改用 StreamReader 逐行读取。
  • 配置文件支持:将文件夹路径、停用词表等放到 appsettings.json

七、总结

通过本项目,我掌握了以下 C# 核心技术:

  • 异步编程(async/awaitTaskTask.WhenAll
  • 并发控制(lock、线程安全)
  • 正则表达式(Regex.Split、单词分割)
  • 文件 I/O(异步读写、目录操作)
  • 集合高级用法(嵌套字典、LINQ、元组)
  • 用户交互(循环输入、异常处理)

本项目可以作为学习上位机开发 的起点,后续可以扩展为工业数据采集与监控系统

相关推荐
idolao1 小时前
Oligo 7.60 安装教程:引物设计+Java 环境配置
java·开发语言
不知名的老吴1 小时前
Lambda表达式与新的Streams API相结合
开发语言·python
石山代码8 小时前
ArrayList / HashMap / ConcurrentHashMap
java·开发语言
程序大视界8 小时前
【Python系列课程】Python正则表达式(下):环视、命名分组与日志实战
开发语言·python·正则表达式
枫叶v.9 小时前
Agent 分层存储架构设计:从记忆方法到中间件选型
开发语言·python
sleven fung10 小时前
MinerU与BabelDOC与KTransformers与OpenAI API库
开发语言·python·ai·langchain
萤萤七悬10 小时前
【Python笔记】AI帮实现CLI工具-使用argparse.ArgumentParser接收命令参数
开发语言·笔记·python
iCxhust10 小时前
C# 命令行指令 查看二进制文件
开发语言·单片机·嵌入式硬件·c#·proteus·微机原理·8088单板机
csdn_aspnet10 小时前
Java 霍尔分区算法(Hoare‘s Partition Algorithm)
java·开发语言·算法