一、项目背景与目标
在工业自动化中,经常需要处理日志文件、设备数据记录,并统计关键字出现频率。本项目的目标是:
- 批量并发创建文本文件(支持用户指定多个文件名)
- 交互式追加内容(逐个询问是否写入英文句子)
- 统计所有文件的单词频率(支持按文件、按单词查询)
- 用户交互查询(输入文件名和单词,返回出现次数)
通过这个项目,熟悉 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/await、Task、Task.WhenAll) - 并发控制(
lock、线程安全) - 正则表达式(
Regex.Split、单词分割) - 文件 I/O(异步读写、目录操作)
- 集合高级用法(嵌套字典、LINQ、元组)
- 用户交互(循环输入、异常处理)
本项目可以作为学习上位机开发 的起点,后续可以扩展为工业数据采集与监控系统。