前言
在 c# 开发中,streamreader
和 streamwriter
是处理文本文件的核心类,属于 system.io
命名空间。它们基于流(stream)操作文本数据,支持读写、编码设置、异步操作等,适用于日志记录、配置文件处理、数据导出等场景。本文将从基础到高级用法,结合代码示例,全面解析其核心功能、性能优化及常见问题解决方案。
一、什么是 streamreader 和 streamwriter?
1. 定义
- streamreader:用于从流(如文件、网络流)中读取文本数据,支持逐行读取、读取指定长度数据或一次性读取全部内容。
- streamwriter:用于向流中写入文本数据,支持追加模式、格式化输出及自定义编码。
streamreader
和 streamwriter
是 system.io
命名空间中的两个类,它们分别用于读取和写入文本数据。两者均继承自 textreader
和 textwriter
抽象类,通常与 filestream
、memorystream
等流结合使用。
2. 特点
- 提供了方便的方法来读取和写入文本数据,支持多种编码格式。
- 可以处理大文件,通过流的方式逐步读取或写入数据,节省内存。
- 支持异步操作,提高程序的响应性和性能。
3. 用途
- 文本文件读写:
- 替代
filestream
直接操作字节的复杂性,自动处理编码和换行符。如日志文件、配置文件等。
- 替代
- 网络流解析:
- 与
networkstream
结合,实现 http 响应内容的高效解码。
- 与
- 内存流操作:
- 配合
memorystream
处理内存中的文本缓存,如 json/xml 序列化。
- 配合
- 日志与数据记录: 支持追加模式写入,避免频繁覆盖文件内容。
适用于以下场景:
- 日志记录:将日志追加到文件。
- 配置文件操作:读取
.ini
、.json
等文本配置。 - 数据导出:将数据写入 csv、txt 文件。
4. 为什么需要 streamreader/streamwriter?
在c#中处理文本文件时,直接使用 filestream
操作字节数组不仅繁琐,还需手动处理编码、换行符等问题。streamreader 和 streamwriter 提供了更高级的文本流操作接口,支持自动编码检测、换行符处理及便捷的读写方法,大幅简化开发流程。
二、基础用法
1. 创建 streamreader 和 streamwriter 对象
在 c# 中,有多种方式可以创建 streamreader
和 streamwriter
对象:
1)从文件路径创建
使用文件路径创建 streamreader
和 streamwriter
对象是最常见的方法。
自动处理编码(默认utf-8)
using system.io; // 创建 streamreader using (streamreader reader = new streamreader("example.txt")) { // 读取操作 } // 创建 streamwriter using (streamwriter writer = new streamwriter("example.txt")) { // 写入操作 }
2)从流创建
也可以从现有的流 stream
对象创建 streamreader
和 streamwriter
,例如从 memorystream
或网络流、 filestream
,适合复杂场景。
using system.io; // 从 memorystream 创建 memorystream memorystream = new memorystream(); streamreader reader = new streamreader(memorystream); streamwriter writer = new streamwriter(memorystream); // 从 filestream 创建 filestream fs = new filestream("data.bin", filemode.open); using (streamreader sr = new streamreader(fs)) { /*...*/ }
3)编码与格式设置
▶ 指定编码
默认编码为 utf-8
,但可通过构造函数指定其他编码(如 utf-16
、ascii
):
// 使用 utf-16 编码 using (streamwriter writer = new streamwriter("data.txt", false, encoding.unicode)) { writer.writeline("hello, unicode!"); }
▶ 追加写入模式
通过指定streamwriter
的 append
参数为true
设置为 追加写入(append: true
)。
streamwriter sw = new streamwriter("log.txt", true, encoding.utf8); // 追加模式
▶ 控制底层流是否关闭
leaveopen
:控制底层流是否随读写器关闭(默认 false
)。
memorystream ms= new memorystream(); streamreader streamreader = new streamreader(ms, encoding.unicode, leaveopen: true);
▶ 自动检测编码
通过 streamreader
的 detectencodingfrombyteordermarks
属性,自动识别 bom 标记:
using (streamreader reader = new streamreader("data.txt", encoding.utf8, detectencodingfrombyteordermarks:true)) { // 如果文件开头有 bom,会自动检测编码 string content = reader.readtoend(); }
2. 使用 streamwriter 写入数据
streamwriter
提供了多种方法来写入文本数据,最常用的是 write
和 writeline
方法。
1)写入数据
使用 write
方法可以写入数据到文件中。支持字符串、数值等类型。
using (streamwriter writer = new streamwriter("example.txt")) { // 写入不同类型的数据 writer.write(1.1f); writer.write(42); writer.write(new byte[] { 1, 2, 3 }); writer.write("hello, world!"); }
2)写入带换行符的数据
使用 writeline
方法可以写入一个带换行符的数据。支持字符串、数值等类型。
using (streamwriter writer = new streamwriter("example.txt")) { // 写入不同类型的数据 writer.writeline(1.1f); writer.writeline(42); writer.writeline(new byte[] { 1, 2, 3 }); writer.writeline("hello, world!"); }
3. 使用 streamreader 读取数据
streamreader
提供了多种方法来读取文本数据,最常用的是 read
、readline
和 readtoend
方法。
1)读取字符
使用 read
方法可以读取一个字符。
using (streamreader reader = new streamreader("example.txt")) { int character; while ((character = reader.read()) != -1) { console.write((char)character); } }
2)读取一行
使用 readline
方法可以读取一行文本。
using (streamreader reader = new streamreader("example.txt")) { string line; while ((line = reader.readline()) != null) { console.writeline(line); } }
3)读取所有文本
使用 readtoend
方法可以读取整个文件的内容。
using (streamreader reader = new streamreader("example.txt")) { string content = reader.readtoend(); console.writeline(content); }
批量操作:
readtoend()
一次性读取全文,write()
支持字符数组写入。
4. streamreader 和 streamwriter 的常用属性和方法
1)streamreader的常用属性和方法
basestream
:获取streamreader
所使用的基础流。currentencoding
:获取当前使用的字符编码。endofstream
:指示是否已到达流的末尾。peek
:查看下一个字符而不读取它。read
:读取单个字符或字符数组。readblock
:读取指定数量的字符。readline
:读取一行文本。readtoend
:读取流中的所有文本。
2)streamwriter的常用属性和方法
basestream
:获取streamwriter
所使用的基础流。autoflush
:获取或设置一个值,该值指示是否在写入数据后自动刷新流。encoding
:获取当前使用的字符编码。write
:写入指定的数据。writeline
:写入指定的数据,并添加换行符。flush
:将所有缓冲的字符写入基础流。close
:关闭流并释放所有相关资源。
3)使用示例
▶ 获取当前编码
可以使用 currentencoding
属性获取当前使用的编码。
using (streamreader reader = new streamreader("example.txt")) { encoding encoding = reader.currentencoding; console.writeline("encoding: " + encoding.encodingname); }
▶ 获取基础流对象
使用 basestream
属性可以获取基础流对象。
using (streamreader reader = new streamreader("example.txt")) { stream stream = reader.basestream; // 操作流 }
▶ 指示是否已到达流的末尾
while (!sr.endofstream) { string line = sr.readline(); console.writeline(line); }
5. 示例代码
下面是一个完整的示例,演示了如何使用 streamreader
和 streamwriter
:
class program { static void main() { string filepath = "example.txt"; // 使用 streamwriter 写入数据 using (streamwriter writer = new streamwriter(filepath)) { writer.writeline("hello, world!"); writer.writeline("this is a new line."); writer.writeline("the answer is: 42"); } // 使用 streamreader 读取数据 using (streamreader reader = new streamreader(filepath)) { string line; while ((line = reader.readline()) != null) { console.writeline(line); } } // 使用 streamreader 读取数据 + endofstream 属性判断 using (streamreader reader = new streamreader(filepath)) { while (!reader.endofstream) { console.writeline(reader.readline()); } } // 读取整个文件内容 using (streamreader reader = new streamreader(filepath)) { console.writeline(reader.readtoend()); } string content = file.readalltext(filepath); console.writeline("file content:"); console.writeline(content); // 读取字符 using (streamreader reader = new streamreader(filepath)) { int character; while ((character = reader.read()) != -1) { console.write((char)character); } } } }
通过这个示例,我们可以看到 streamreader
和 streamwriter
在处理文本文件时是多么方便和高效。它们提供了丰富的功能,满足了多种文本处理需求。
三、高级用法
1. 高级技巧
1)异步操作
使用 readasync
、writeasync
实现异步读写,避免阻塞主线程,提升i/o性能
public async task writeasync() { using (streamwriter writer = new streamwriter("data.txt")) { await writer.writelineasync("异步写入"); } } public async task readasync() { using (streamreader reader = new streamreader("data.txt")) { string content = await reader.readtoendasync(); } }
2)缓冲区优化
预分配容量:若已知文件大小,初始化时指定 buffersize
减少扩容开销。 平衡性能与内存占用
// 创建带自定义缓冲区的流 using (streamreader reader = new streamreader("data.txt", encoding.utf8, true, 8192)) { // 缓冲区大小为 8kb }
3)大文件处理
逐行读取大文件以避免内存溢出:
using (streamreader reader = new streamreader("large_file.txt")) { string line; while ((line = await reader.readlineasync()) != null) { // 处理每一行 } }
4)显式刷新缓冲区
- 显式刷新缓冲区:高频写入时调用
flush()
避免内存堆积。 - 延迟写入与批量提交:
sw.autoflush = false; // 关闭自动刷新 for (int i = 0; i < 1000; i++) { sw.write($"data {i}"); } sw.flush(); // 手动批量提交
2. 高级应用示例
1)案例1:csv文件解析
假设需读取包含逗号分隔的csv文件并提取数据:
using (streamreader sr = new streamreader("data.csv")) { while (!sr.endofstream) { string line = sr.readline(); string[] fields = line.split(',').select(f => f.trim()).toarray(); // 处理字段数据... } }
优势:自动处理编码与换行符,简化字符串分割逻辑。
2)案例2:大文件逐行处理与内存优化
当处理 gb 级日志文件时,需避免一次性加载全部数据导致内存溢出:
using (var sr = new streamreader("large.log", encoding.utf8, buffersize: 8192)) { while (!sr.endofstream) { string line = sr.readline(); if (line.contains("error")) { // 实时处理错误行 } } }
优化点:
- 设置
buffersize
为 8kb(默认 1kb),减少磁盘读取次数。 - 逐行释放内存,避免
readtoend()
的全量加载风险。
3)案例3:日志记录系统
public static void log(string message) { using var writer = new streamwriter("app.log", true); writer.writeline($"[{datetime.now:yyyy-mm-dd hh:mm:ss}] {message}"); }
4)案例4:配置文件读写
var config = new dictionary<string, string>(); using var reader = new streamreader("config.ini"); while ((line = reader.readline()) != null) { var parts = line.split('='); if (parts.length == 2) { config[parts[0]] = parts[1]; } }
四、常见问题与最佳实践
1. 常见问题
1)文件不存在时的异常
try { using (streamreader reader = new streamreader("non_existent.txt")) { // 处理文件 } } catch (filenotfoundexception ex) { console.writeline("文件不存在:" + ex.message); }
2)编码不匹配导致乱码
确保读写时编码一致:
// 写入时使用 utf-8 using (streamwriter writer = new streamwriter("data.txt", false, encoding.utf8)) { ... } // 读取时指定相同编码 using (streamreader reader = new streamreader("data.txt", encoding.utf8)) { ... }
3)资源未释放
始终使用 using
语句确保流正确关闭:
// 错误示例:未使用 using streamreader reader = new streamreader("data.txt"); // ... 处理后未关闭,可能导致文件被锁定 reader.close(); // 需手动调用 // 正确做法:使用 using 自动释放资源 using (streamreader reader = new streamreader("data.txt")) { ... } // c# 8+简化写法 using var writer = new streamwriter("output.txt");
4)编码问题
若文件包含bom(字节顺序标记),可通过 detectencodingfrombyteordermarks: true
自动识别。
处理含 bom 头的多编码文件时,自动适配编码:
using (filestream fs = file.openread("mixed_encoding.txt")) { using (streamreader sr = new streamreader(fs, encoding.default, detectencodingfrombyteordermarks: true)) { console.writeline($"检测到编码:{sr.currentencoding}"); // 按正确编码解析内容 } }
技巧:通过 currentencoding
属性获取实际使用的编码。
5)流位置重置
写入后需调用 seek(0, seekorigin.begin)
重置位置,否则后续读取会从末尾开始。
6)避免嵌套流生命周期
避免嵌套流生命周期:确保底层流与读写器释放顺序一致(先关读写器,再关流)。
2. 最佳实践总结
- 资源管理:必须使用using语句
- 编码明确:尽量指定确定编码
- 异常处理:全面捕获io异常
- 性能考量:大文件使用缓冲读取
- 模式选择:追加模式注意参数设置
到此这篇关于c#中的 streamreader/streamwriter 使用示例详解的文章就介绍到这了,更多相关c# streamreader/streamwriter 使用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论