使用C#从零到完整实现的并发单词频率统计器

 更新时间:2026年06月04日 09:25:20   作者:freedom ⁠  
在工业自动化中,经常需要处理日志文件、设备数据记录,并统计关键字出现频率,所以本文给大家使用C#从零到完整实现了一个并发单词频率统计器,需要的朋友可以参考下

一、项目背景与目标

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

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

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

二、核心代码与语法解析

2.1 异步创建文件(并发)

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

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) 保证控制台输出不混乱。

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

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+ 匹配一个或多个非单词字符,相当于分割符。

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

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 快速构建嵌套字典。

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

Dictionary<string, Dictionary<string, int>> textWords = 
    new Dictionary<string, Dictionary<string, int>>(StringComparer.OrdinalIgnoreCase);

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

2.5 用户交互查询

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、元组)
  • 用户交互(循环输入、异常处理)

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

以上就是使用C#从零到完整实现的并发单词频率统计器的详细内容,更多关于C#并发单词频率统计器的资料请关注脚本之家其它相关文章!

相关文章

  • C#中lock死锁实例教程

    C#中lock死锁实例教程

    这篇文章主要介绍了C#中lock死锁的用法,对于共享资源的访问及C#程序设计的安全性而言,有着非常重要的意义!需要的朋友可以参考下
    2014-08-08
  • C#中 const 和 readonly 的不同

    C#中 const 和 readonly 的不同

    const 和 readonly 的区别,总是不太清楚,于是查了查资料。
    2013-04-04
  • c#获取本机的IP地址的代码

    c#获取本机的IP地址的代码

    c#获取本机的IP地址的代码,需要的朋友可以参考一下
    2013-03-03
  • C#集合根据对象的某个属性进行去重的代码示例

    C#集合根据对象的某个属性进行去重的代码示例

    当根据对象的Name属性进行去重时,你可以使用以下三种方法:使用Distinct方法和自定义比较器、使用LINQ的GroupBy方法,以及使用HashSet,下面给大家介绍C#集合根据对象的某个属性进行去重的代码示例,感兴趣的朋友一起看看吧
    2024-03-03
  • C#编写SqlHelper类

    C#编写SqlHelper类

    在C#中使用ADO.NET连接数据库的时候,每次连接都要编写连接,打开,执行SQL语句的代码,很麻烦,编写一个SqlHelper类,把每次连接都要写的代码封装成方法,把要执行的SQL语句通过参数传进去,可以大大简化编码
    2017-09-09
  • c# 调用Surfer软件,添加引用的具体操作方法

    c# 调用Surfer软件,添加引用的具体操作方法

    本篇文章主要是对c#中调用Surfer软件,添加引用的具体操作方法进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01
  • C#实现将Excel工作表拆分为多个窗格

    C#实现将Excel工作表拆分为多个窗格

    在日常工作中,我们经常需要处理包含大量数据的 Excel 文件,本文将深入探讨如何在 C# 中利用强大的 Spire.XLS for .NET 自动化实现 Excel 工作表的窗格拆分功能,感兴趣的小伙伴可以了解下
    2025-12-12
  • C#使用BarcodeLib生成条形码的完整代码

    C#使用BarcodeLib生成条形码的完整代码

    现代工业、物流、零售等领域,条形码作为信息识别的重要手段被广泛应用,BarcodeLib是一个开源的C#库,专为大家提供便捷、高效的条形码生成功能,本文将详细介绍如何在WinForm项目中集成和使用BarcodeLib库,帮助大家快速实现条形码的生成与显示,需要的朋友可以参考下
    2025-06-06
  • c#用Treeview实现FolderBrowerDialog 和动态获取系统图标(运用了Win32 dll类库)

    c#用Treeview实现FolderBrowerDialog 和动态获取系统图标(运用了Win32 

    其实,FolderBrowerDialog 很好用呢,有木有啊亲,反正我特别的喜欢,微软大哥把这个浏览文件夹的东东封装的多好呀
    2013-03-03
  • 基于C#的抽象类别详解

    基于C#的抽象类别详解

    下面小编就为大家分享一篇基于C#的抽象类别详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12

最新评论