PHP速学视频免费教程(入门到精通)
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
filestream是c#中用于直接操作文件字节流的类,适用于处理二进制文件、需要精确控制文件指针或性能敏感的大文件场景;2. 使用时必须通过using语句确保资源释放,并捕获ioexception、unauthorizedaccessexception等异常以增强健壮性;3. 优化大文件处理时可设置缓冲区大小、使用readasync/writeasync异步方法、分块读写,或考虑memorymappedfile提升性能。
C#里的
FileStream类,说白了,就是你和文件系统之间搭的一座桥,让你能以字节流的形式直接和文件打交道。它不是用来处理文本编码那么“高级”的,它更底层,更原始,直接操作的是二进制数据,就像你在搬砖,一块一块地搬,而不是管这砖上刻了什么字。当你需要精确控制文件读写,或者处理非文本的二进制文件时,比如图片、音频或者自定义的数据格式,
FileStream就是你的不二之选。它高效,但需要你对数据流的概念有更深的理解。
要用
FileStream读写文件,核心就是创建实例、操作字节数组,然后别忘了释放资源。
写入文件:
想象你要把一些文字写进文件,但
FileStream只认字节。所以,第一步是把你的文字(或其他数据)转换成字节数组。
using System; using System.IO; using System.Text; // 引入这个命名空间来处理编码 public class FileWriteExample { public static void WriteToFile() { string filePath = "my_log.bin"; // 故意用.bin,表示可能不是纯文本 string content = "这是一段要写入文件的内容,可能会包含中文。"; // 将字符串内容编码成字节数组 // UTF-8 是个不错的选择,因为它兼容性好,尤其处理中文 byte[] data = Encoding.UTF8.GetBytes(content); // 使用 using 语句,确保 FileStream 在使用完毕后被正确关闭和释放 // FileMode.Create: 如果文件不存在就创建,如果存在就覆盖 // FileAccess.Write: 允许写入 try { using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write)) { // 将字节数组写入文件 fs.Write(data, 0, data.Length); Console.WriteLine($"内容已成功写入到 {filePath}"); } } catch (IOException ex) { Console.WriteLine($"写入文件时发生IO错误:{ex.Message}"); // 真实场景中,你可能需要更详细的日志记录 } catch (UnauthorizedAccessException ex) { Console.WriteLine($"没有权限写入文件:{ex.Message}"); } catch (Exception ex) { Console.WriteLine($"发生未知错误:{ex.Message}"); } } }
读取文件:
读取文件则是一个逆向过程:从文件里读出字节,再把这些字节转换回你想要的数据类型。
using System; using System.IO; using System.Text; public class FileReadExample { public static void ReadFromFile() { string filePath = "my_log.bin"; // 在读取前,最好先检查文件是否存在 if (!File.Exists(filePath)) { Console.WriteLine($"文件 {filePath} 不存在,无法读取。"); return; } try { // FileMode.Open: 打开一个已存在的文件 // FileAccess.Read: 允许读取 using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { // 创建一个与文件大小相同的字节数组,或者一个合适的缓冲区大小 // 对于非常大的文件,不建议一次性读入所有内容,应该分块读取 byte[] buffer = new byte[fs.Length]; // 假设文件不大,一次性读完 int bytesRead = fs.Read(buffer, 0, buffer.Length); Console.WriteLine($"从文件中读取了 {bytesRead} 字节。"); // 将字节数组解码回字符串 string content = Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine("文件内容:"); Console.WriteLine(content); } } catch (IOException ex) { Console.WriteLine($"读取文件时发生IO错误:{ex.Message}"); } catch (UnauthorizedAccessException ex) { Console.WriteLine($"没有权限读取文件:{ex.Message}"); } catch (Exception ex) { Console.WriteLine($"发生未知错误:{ex.Message}"); } } }
这里只是最基础的读写。
FileStream还支持
Seek方法来定位文件中的特定位置,实现随机读写,这在处理大型二进制文件格式时非常有用。比如,你可能只想读取图片文件头部的几个字节来判断文件类型,而不是加载整个图片。
这俩兄弟,
FileStream和
StreamReader/
StreamWriter,在.NET文件操作家族里,定位是完全不一样的。简单来说,
FileStream是“搬运工”,它只管把字节从A点搬到B点,或者从B点搬到A点,不关心这些字节代表什么意义。它操作的是原始的字节流,没有字符编码的概念。
而
StreamReader和
StreamWriter则更像是“翻译官”。它们是建立在
FileStream之上的,专门用来处理文本数据。它们知道如何根据指定的编码(比如UTF-8、GBK等)把字节序列转换成字符,或者把字符转换成字节序列。这意味着,当你用
StreamReader读取时,你得到的是字符串,而不是字节数组;用
StreamWriter写入时,你直接传入字符串。它们为你处理了字符编码、行尾符(比如Windows的
\r\n)这些细节。
那么,什么时候该用FileStream
?
我的经验是,当你处理以下场景时,
FileStream就该登场了:
StreamReader/
StreamWriter就完全帮不上忙了,它们会把二进制数据错误地解释成字符。
FileStream的
Seek()方法是你的利器。比如,你可能想更新一个大型日志文件中间的某个状态标记,而不是重写整个文件。
StreamReader/
StreamWriter内部也用了缓冲区,但
FileStream提供了更底层的控制,你可以根据实际需求调整缓冲区大小,甚至进行异步I/O操作(后面会提到)。在处理超大文件时,如果你需要极致的性能优化,
FileStream能提供更多的调优空间。
FileStream作为底层数据源或目标。
什么时候用StreamReader
/StreamWriter
?
几乎所有处理文本文件的场景。如果你只是想读写一个
.txt文件、
.csv文件、
.json文件或
.xml文件,那么
StreamReader/
StreamWriter会大大简化你的代码,让你不用操心编码问题,直接以字符串为单位进行操作。它们更符合我们处理文本的直觉。
所以,选择哪个,取决于你面对的是“砖头”(字节)还是“文字”(字符)。
处理大型文件,尤其是GB级别甚至TB级别的文件,直接操作很容易遇到性能瓶颈,甚至内存溢出。
FileStream在这方面确实提供了一些优化空间,但需要你主动去利用。
合理设置缓冲区大小:
FileStream的构造函数允许你指定一个
bufferSize参数。默认情况下,它会有一个内部缓冲区(通常是4KB或8KB)。这个缓冲区的作用是减少对底层操作系统的I/O调用次数。每次
Write()或
Read()操作,数据会先进入或从这个缓冲区取出,只有当缓冲区满了或者你显式调用
Flush()时,数据才真正写入磁盘。 对于大型文件,增加缓冲区大小通常能提升性能,因为它减少了昂贵的磁盘I/O操作。例如,你可以尝试16KB、64KB甚至更大的缓冲区(比如256KB),但也要注意,过大的缓冲区会占用更多内存,而且可能在某些情况下适得其反,需要根据实际应用场景和硬件条件进行测试。
// 创建一个带有64KB缓冲区的FileStream using (FileStream fs = new FileStream("large_file.dat", FileMode.Create, FileAccess.Write, FileShare.None, 65536)) { // ... 写入数据 }
使用异步I/O (Asynchronous I/O): 这是处理大文件时非常重要的一个优化点,尤其是在GUI应用或服务器端应用中。传统的
Read()和
Write()方法是同步的,它们会阻塞当前线程,直到I/O操作完成。这意味着在文件读写过程中,你的UI可能会卡死,或者服务器无法响应其他请求。
FileStream提供了
ReadAsync()和
WriteAsync()方法,它们允许I/O操作在后台进行,不阻塞调用线程。当I/O完成时,会通过回调或
await关键字通知你。这极大地提升了应用的响应性和吞吐量。
using System.Threading.Tasks; // 引入异步相关命名空间 public static async Task WriteLargeFileAsync(string filePath, int sizeInMB) { byte[] data = new byte[1024 * 1024]; // 1MB 缓冲区 new Random().NextBytes(data); // 填充一些随机数据 try { using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, useAsync: true)) { Console.WriteLine("开始异步写入大文件..."); for (int i = 0; i <p>记住,<pre class="brush:php;toolbar:false">useAsync: true这个参数在
FileStream构造函数里很重要,它告诉操作系统为这个流启用异步I/O。
分块读取/写入: 对于超大文件,你不可能一次性把所有内容都加载到内存中。你需要分块(chunk)处理。比如,每次读取固定大小的块(例如4MB),处理完后再读取下一块。写入也是同理。这避免了内存溢出,并且可以让你在处理数据时保持较低的内存占用。
public static void ReadLargeFileInChunks(string filePath, int chunkSize = 4096) // 默认4KB { if (!File.Exists(filePath)) return; byte[] buffer = new byte[chunkSize]; try { using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { int bytesRead; while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0) { // 在这里处理每一块数据 // 例如:处理 buffer 中从 0 到 bytesRead-1 的数据 // Console.WriteLine($"读取了 {bytesRead} 字节..."); // 实际应用中,这里会是你的业务逻辑,比如解析、转换、写入到另一个文件等 } } } catch (Exception ex) { Console.WriteLine($"分块读取文件时发生错误: {ex.Message}"); } }
考虑MemoryMappedFile
(内存映射文件):
虽然这严格来说不是
FileStream的优化技巧,但对于处理超大文件,尤其是需要随机访问的场景,
MemoryMappedFile是一个非常强大的替代方案。它允许你将文件的一部分或全部内容直接映射到进程的虚拟内存空间中,这样你就可以像访问内存数组一样访问文件内容,而操作系统会负责按需从磁盘加载数据。这在处理非常大的文件时,可以提供极高的性能和非常低的内存占用。当然,它的API会更复杂一些,适用场景也更特定。这算是一个思维发散,但确实是处理大文件时的一个高级选项。
这些优化技巧,结合起来使用,能够让你在C#中更高效、更稳定地处理大型文件。
文件操作天生就是个“危险活”,磁盘可能满、文件可能被占用、路径可能不存在、权限可能不够……各种幺蛾子都可能发生。所以,在
FileStream的使用中,异常处理和资源管理是重中之重,甚至比业务逻辑本身还要重要。
资源管理:IDisposable
和using
语句
FileStream是一个非托管资源(因为它涉及到操作系统级别的文件句柄)。非托管资源必须在使用完毕后被显式释放,否则会导致文件被锁定、内存泄漏等问题。
FileStream实现了
IDisposable接口,这意味着它有一个
Dispose()方法来清理这些资源。
黄金法则:永远使用using
语句。
using语句是C#提供的一个语法糖,它能确保实现了
IDisposable接口的对象在代码块结束时,无论是否发生异常,其
Dispose()方法都会被自动调用。这极大地简化了资源管理,避免了遗漏释放资源的问题。
// 错误示范:没有使用 using,如果在 fs.Write() 之前发生异常,文件句柄可能不会被释放 // FileStream fs = new FileStream(filePath, FileMode.Create); // fs.Write(data, 0, data.Length); // fs.Close(); // 如果写到这行前就抛异常,fs.Close() 就不会执行 // 正确且推荐的做法 try { using (FileStream fs = new FileStream("test.txt", FileMode.Create)) { // 在这里进行文件操作 // 即使这里抛出异常,fs.Dispose() 也会被调用 } // 离开 using 块时,fs.Dispose() 会自动调用 } catch (Exception ex) { Console.WriteLine($"文件操作失败:{ex.Message}"); }
如果你因为某些非常特殊的原因(比如需要在多个方法间传递一个未关闭的流,这通常是个坏设计)不能使用
using,那么你必须在
finally块中手动调用
Dispose()或
Close():
FileStream fs = null; try { fs = new FileStream("test.txt", FileMode.Create); // 文件操作 } catch (Exception ex) { Console.WriteLine($"文件操作失败:{ex.Message}"); } finally { // 确保 fs 不为 null 且可释放 fs?.Dispose(); // C# 6.0 及以上版本可以使用 ?. 安全调用 }
但说实话,除了极少数特殊情况,
using语句总是你的首选。
异常处理:try-catch
块
文件操作是I/O密集型任务,很容易遇到各种运行时错误。你需要预料到这些错误并进行适当的捕获和处理。
常见的FileStream
相关异常:
IOException: 这是最常见的I/O操作通用异常。它有很多子类,但很多时候,你捕获
IOException就足够了。
FileNotFoundException: 尝试打开一个不存在的文件(当
FileMode是
Open或
Append时)。
DirectoryNotFoundException: 文件路径中指定的目录不存在。
PathTooLongException: 文件路径或文件名太长。
UnauthorizedAccessException: 没有足够的权限读写文件(比如文件是只读的,或者当前用户没有该目录的写入权限)。
EndOfStreamException: 尝试在流的末尾之外读取。
FileLoadException: 尝试加载一个文件,但文件被锁定或损坏。
ArgumentException/
ArgumentNullException: 构造
FileStream时,如果路径参数为空或格式不正确。
NotSupportedException: 尝试对一个不支持的操作进行调用(比如对只读流调用
Write())。
ObjectDisposedException: 在流被关闭或释放后,你还尝试对其进行操作。
处理策略:
UnauthorizedAccessException,你可以提示用户检查权限;对于
FileNotFoundException,可以提示文件路径错误。
一个健壮的
FileStream操作代码块,通常会是
try-catch-using的组合,像这样:
public static void RobustFileOperation(string filePath, string dataToWrite) { try { // 尝试写入 byte[] bytes = Encoding.UTF8.GetBytes(dataToWrite); using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write)) { fs.Write(bytes, 0, bytes.Length); Console.WriteLine($"数据已成功写入到 {filePath}"); } // 尝试读取 using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { byte[] buffer = new byte[fs.Length]; int bytesRead = fs.Read(buffer, 0, buffer.Length); string readContent = Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine($"从 {filePath} 读取到:{readContent}"); } } catch (FileNotFoundException) { Console.WriteLine($"错误:文件 '{filePath}' 不存在。"); // 记录日志:Logger.LogError($"FileNotFound: {filePath}", ex); } catch (UnauthorizedAccessException) { Console.WriteLine($"错误:没有权限访问文件 '{filePath}',请检查文件权限。"); // 记录日志:Logger.LogError($"UnauthorizedAccess: {filePath}", ex); } catch (IOException ex) when ((ex.HResult & 0xFFFF) == 32) // 32 是文件被占用的错误码 { Console.WriteLine($"错误:文件 '{filePath}' 正在被其他程序占用,请稍后再试。"); // 记录日志:Logger.LogError($"FileInUse: {filePath}", ex); } catch (IOException ex) { Console.WriteLine($"发生