>> >> >> Reference << << << <<<<<<Ref>>>>>>
IO Stream
Modified: 2025-06-01 | Author:ljf12825

I/O(Input/Output)流是C#中处理数据输入输出的核心机制,它提供了一种统一的方式来处理不同类型的数据源和目标(如文件、内存、网络等)

I/O流的概念

在C#中,流(Stream)是一个抽象概念,表示一系列连续的字节数据。所有流都继承自System.IO.Stream抽象类,它定义了流的基本操作:

在C#中,I/O流分为两种:

字节流(Byte Streams)操作

FileStream

FileStream是.NET中用于读取和写入文件的类。它提供了对文件的字节级别的访问,可以处理较大的文件并允许逐字节地操作文件内容,适用于需要直接操作文件内容的场景

基本概念

FileStream类位于System.IO命名空间中,支持异步和同步操作,通常用于文件的读取、写入以及定位。它可以处理任意类型的文件,不仅仅是文本文件,还可以处理二进制文件

常见用法

构造函数
FileStream有多个构造函数,每个构造函数都可以根据不同的需求来打开或创建文件

  1. 基本构造函数
public FileStream(string path, FileMode mode);
  1. 指定文件访问权限
public FileStream(string path, FileMode mode, FileAccess access)
  1. 指定文件访问权限和共享模式
public FileStream(string path, FileMode mode, FileAccess access, FileShare share);
  1. 指定缓冲区大小
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize);
  1. 指定文件路径和流的创建方式
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options);

文件模式(FileMode)

文件访问权限(FileAccess)

文件共享(FileShare)
控制其他进程对文件的访问权限。常用的选项有

文件的其他选项(FileOptions)

文件位置操作
FileStream支持文件中的位置操作,允许控制当前读取或写入的位置。可以通过Position属性来获取或设置当前的文件指针位置

fs.Position = 0; // 设置文件指针到文件开头

读取和写入数据 FileStream提供了字节级别的操作方法,例如:

// 写入文件
byte[] data = Encoding.UTF8.GetBytes("Hello, World!");
fs.Write(data, 0, data.Length);

// 读取文件
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
Console.WriteLine(Encoding.UTF8.GetString(buffer));

异步操作 FileStream还支持异步操作,尤其适用于需要处理大量数据或在UI应用中避免阻塞主线程的场景

await fs.WriteAsync(data, 0, data.Length);
byte[] buffer = new byte[fs.Length];
await fs.ReadAsync(buffer, 0, buffer.Length);

适用场景

使用完FileStream后的清理
通常,当操作文件流时,需要及时释放资源。最安全的方式是使用using语句,它确保即使发生异常,文件流也会正常关闭

using (FileStream fs = new FileStream("example.txt", FileMode.OpenOrCreate))
{
    // file ops
}// stream close

MemoryStream

MemoryStream允许在内存中读写数据,而不是像FileStream那样操作磁盘文件。它的底层是一个字节数组byte[],所以读写速度非常快,但数据是非持久化的,程序结束或对象被回收后数据就消失了

核心特点

特性说明
数据存储内存中(byte[]
可读写可以读、写,支持随机访问(Seek)
性能高速,因为不涉及磁盘 I/O
数据长度可变内部缓冲区会自动扩容
可转字节数组可通过 ToArray() 获取整个内容

注意:如果使用MemoryStream构造函数时传入一个固定的byte[],默认不可扩展,写入过多数据会抛异常

常用构造函数

MemoryStream() // 空的内存流
MemoryStream(byte[] buffer) // 用已有字节数组初始化
MemoryStream(byte[] buffer, bool writable) // 指定是否可写
MemoryStream(int capacity) // 指定初始容量

核心方法和属性

方法 / 属性用法说明
Write(byte[] buffer, int offset, int count)写入字节向流写入数据
Read(byte[] buffer, int offset, int count)读取字节从流读取数据
Seek(long offset, SeekOrigin origin)定位设置读写指针位置
ToArray()获取字节数组返回内存流中所有数据的副本
GetBuffer()获取内部缓冲区注意:可能比实际数据长
Length流长度已写入的数据长度
Position读写位置当前指针位置
SetLength(long value)设置长度可以截断或扩展流
Flush()刷新流MemoryStream 通常无操作

示例

写入数据

using System;
using System.IO;
using System.Text;

class Program
{
  staitc void Main()
  {
    using (MemoryStream ms = new MemoryStream())
    {
      string text = "Hello MemoryStream!";
      byte[] bytes = Encoding.UTF8.GetBytes(text);
      ms.Write(bytes, 0, bytes.Length);

      Console.WriteLine("Length: " + ms.Length); // 输出长度
      Condole.WriteLine("Position: " + ms.Position); // 输出当前位置

      // 重置位置以便读取
      ms.Position = 0;

      byte[] buffer = new byte[ms.Length];
      ms.Read(buffer, 0, buffer.Length);
      string result = Encoding.UTF8.GetString(buffer);
      Console.WriteLine(result); // 输出 Hello MemoryStream
    }
  }
}

从字节数组创建并读取

byte[] data = {10, 20, 30, 40};
using (MemoryStream ms = new MemoryStream(data))
{
  for (int i = 0; i < ms.Length; ++i)
  {
    int value = ms.ReadByte(); // 单字节读取
    Console.WriteLine(value);
  }
}

使用场景

  1. 临时数据缓存 比如读取文件后先在内存处理,然后再写回磁盘或发送网络

  2. 网络数据处理 和BinaryReader/BinaryWriter配合,序列化/反序列化数据

  3. 图片/音频处理 在内存中编辑或转换图像,再通过Image.FromStream(ms)使用

  4. 避免频繁磁盘操作 流程中大量临时数据操作时,用MemoryStream会快很多

注意事项

BufferedStream

BufferedStream是.NET提供的一个带缓冲的流包装器。它本身不直接读写文件或内存,而是包装另一个流(如FileStreamNetworkStream),在读写时增加一个内存缓冲区来提升性能

工作原理

这种机制大大减少了I/O的调用次数,提高了性能

构造函数

BufferedStream(Stream stream)
BufferedStream(Stream stream, int bufferSize)
using (FileStream fs = new FileStream("test.txt", FileMode.OpenOrCreate))
using (BufferedStream bs = new BufferedStream(fs, 8192))
{
  // bs.Read / bs.Write
}

常用方法和属性

方法 / 属性说明
Read(byte[] buffer, int offset, int count)从缓冲区或底层流读取数据
Write(byte[] buffer, int offset, int count)写入数据到缓冲区
Flush()把缓冲区数据写入底层流
Seek(long offset, SeekOrigin origin)改变读写位置(影响底层流和缓冲区)
Length底层流的长度
Position当前读写位置

示例

写文件(带缓冲)

using System;
using System.IO;
using System.Text;

class Program
{
  static void Main()
  {
    using (FileStream fs = new FileStream("data.txt", FileMode.Create, FileAccess.Write))
    using (BufferedStream bs = new BufferedStream(fs, 8192)) // 8KB缓冲区
    using (StreamWriter sw = new StreamWriter(bs, Encoding.UTF8))
    {
      for (int i = 0; i < 1000; ++i)
        sw.WriteLine("Line " + i);
    } // 这里会自动 Flush + Dispose
  }
}

读文件(带缓冲)

using (FileStream fs = new FileStream("data.txt", FileMode.Open, FileAccess.Read))
using (BufferedStream bs = new BufferedStream(fs, 8192))
using (StreamReader sr = new StreamReader(bs, Encoding.UTF8))
{
  string line;
  while ((line = sr.ReadLine()) != null)
    Console.WriteLine(line);
}

使用场景

注意事项

BufferedStream vs MemoryStream

特点MemoryStreamBufferedStream
存储位置内存(字节数组)内存缓冲区 + 底层流
是否依赖底层流是(必须包装其他流)
数据是否持久化取决于底层流(比如 FileStream)
适用场景临时数据处理减少 I/O 调用次数

NetworkStream

NetworkStream是.NET中用于网络数据传输的流类型,它直接封装了底层Socket对象,可以用流的方式来读写网络数据。因为它继承自Stream,所以可以和StreamReaderStreamWriterBinaryReaderBinaryWriter等搭配使用,方便进行文本/二进制通信

工作方式

NetworkStream基于TCP套接字(Socket/TcpClinet/TcpListener
它不自己管理连接,而是依赖外部的Socket

数据走向

程序 <-> NetworkStream <-> Socket <-> TCP/IP协议栈 <-> 网络

构造函数

通常不会手动new NetworkStream(Socket),而是通过TcpClient.GetStream()获取

TcpClient client = new TcpClient("127.0.0.1", 8080);
NetworkStream ns = client.GetStream();

如果真的要自己构造,可以传入一个已连接的Socket

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect("127.0.0.1", 8080);
NetworkStream ns = new NetworkStream(socket ownsSocket: true);

常用方法

方法/属性说明
Read(byte[] buffer, int offset, int size)从网络中读取数据(阻塞)
Write(byte[] buffer, int offset, int size)向网络写数据
Flush()TCP 流不需要手动 Flush,调用没效果
CanRead/CanWrite是否可读写
DataAvailable是否有可读数据(非阻塞检查)
ReadAsync/WriteAsync异步读写(推荐)

示例

简单客户端

using System;
using System.IO;
using System.Net.Scokets;
using System.Text;

class Client
{
  static void Main()
  {
    using (TcpClient client = new TcpClient("127.0.0.1", 8080))
    using (NetworkStream ns = client.GetStream())
    using (StreamWriter writer = new StreamWriter(ns, Encoding.UTF8) { AutoFlush = true })
    using (StreamReader reader = new StreamReader(ns, Encoding.UTF8))
    {
      writer.WriteLine("Hello Server!");
      string response = reader.ReadLine();
      Console.WriteLine("Server says: " + response);
    }
  }
}

简单服务器端

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;

class Server
{
  staitc void Main()
  {
    TcpListener listener = new TcpListener(IPAddress.Any, 8080);
    listener.Start();
    Console.WriteLine("Server started...");

    while (true)
    {
      TcpClient client = listener.AcceptTcpClient();
      Console.WriteLine("Client connected.");

      using (NerworkStream ns = client.GetStream())
      using (StreamReader reader = new StreamReader(ns, Encoding.UTF8))
      using (StreamWriter writer = new StreamWriter(ns, Encoding.UTF8) { AutoFlush = ture })
      {
        string msg = reader.ReadLine();
        Console.WriteLine("Client says: " + msg);
        writer.WriteLine("Hello Client!");
      }
    }
  }
}

使用场景

注意事项

  1. 阻塞问题
  1. TCP是流式协议
  1. 生命周期
  1. 性能优化

字符流(Character Streams)操作

StreamReader & StreamWriter

Stream本身只处理字节byte[],但经常处理文本(比如日志、配置文件、JSON、XML、网络消息)
所以:

构造函数

StreamReader

StreamReader(Stream stream)
StreamReader(Stream stream, Encoding encoding)
StreamReader(string path)
StreamReader(string path, Encoding encoding)

StreamWriter

StreamWriter(Stream stream)
StreamWriter(Stream stream, Encodign encoding)
StreamWriter(string path)
StringWriter(string path, bool append)
StreamWriter(string path, bool append, Encoding encoding)

常用方法

StreamReader

方法 / 属性说明
Read()读取一个字符(返回 int,-1 表示结束)
ReadLine()读取一行文本
ReadToEnd()读取整个流的剩余内容
Peek()查看下一个字符但不移动指针
EndOfStream是否读到结尾

StreamWriter

方法 / 属性说明
Write(string)写文本(不换行)
WriteLine(string)写文本并换行
Flush()把缓冲区内容写入底层流
AutoFlush设置是否每次写入后自动刷新

示例

写入文本文件

using System;
using System.IO;
using System.Text;

class Program
{
  static void Main()
  {
    using (StreamWriter writer = new StreamWriter("log.txt", false, Encoding.UTF8))
    {
      writer.WriteLine("Hello World!");
      writer.WriteLine("时间:" + DateTime.Now);
    }
    Console.WriteLine("写入完成");
  }
}

读取文本文件

using (StreamReader reader = new StreamReader("log.txt", Encoding.UTF8))
{
  string line;
  while ((line = reader.ReadLine()) != null)
    Console.WriteLine(line);
}

结合NetworkStream

using (StreamReader reader = new StreamReader(networkStream, Encoding.UTF8))
using (StreamWriter writer = new StreamWriter(networkStream, Encoding.UTF8) { AutoFlush = ture })
{
  writer.WriteLine("Hello Server");
  string response = reader.ReadLine();
  Console.WriteLine("Server: " + response);
}

使用场景

注意事项

  1. 编码问题
  1. 缓冲区
  1. 换行符
  1. 文件占用

StringReader & StringWriter

这两个类专门用来在内存里处理字符串,不依赖文件或网络,完全运行在内存中,非常轻量

构造函数

StringReader

StringReader(string s)

StringWriter

StringWriter()
StringWriter(StringBuilder sb)
StringWriter(IFormatProvider formatProvider)
StringWriter(StringBuilder sb, IFormatProvider formatProvider)

常用方法

StringReader

方法 / 属性说明
Read()读一个字符(返回 int,-1 表示结束)
ReadLine()读一行(遇到 \r\n / \n 就结束)
ReadToEnd()读完剩余的所有文本
Peek()看下一个字符但不移动位置

StringWriter

方法 / 属性说明
Write(string)写字符串
WriteLine(string)写一行(带换行符)
ToString()返回最终拼接好的字符串
GetStringBuilder()返回内部的 StringBuilder

示例

使用StringReader

using System;
using System.IO;

class Program
{
  static void Main()
  {
    string text = "Hello\nWrold\nC#";

    using (StringReader reader = new StringReader(text))
    {
      string? line;
      while((line = reader.ReadLine()) != null)
        Console.WriteLine("Line: " + line);
    }
  }
}

使用StringWriter

using System;
using System.IO;

class Program
{
  static void Main()
  {
    using (StringWriter writer = new StringWriter())
    {
      writer.WriteLine("Hello");
      writer.WriteLine("World");
      writer.WriteLine("C#");

      string result = writer.ToString();
      Console.WriteLine(result);
    }
  }
}

联合使用

string original = "Line1\nLine3\nLine3";
string transformed;

using (StringReader reader = new StringReader(original));
using (StringWriter writer = new StringWriter());
{
  string? line;
  while ((line = reader.ReadLine()) != null)
    writer.WriteLine(line.ToUpper()); // 转大写再写入
  
  transformed = writer.ToString();
}

Console.WriteLine(transformed);

使用场景

异步I/O和缓冲流

I/O流异常处理

常见I/O操作

文件操作

目录操作

I/O流优化

File

File类是.NET中System.IO命名空间下提供的一个静态类,封装了与我呢见操作相关的常见方法。它提供了许多用于创建、删除、赋值、读取和写入文件的方法,简化了文件操作的过程,尤其是在不需要处理流(Stream)或文件的高级特性时,使用File类会非常方便

File类的常见用途

File类本身是一个静态类,因此它不需要实例化。它提供了一些静态方法,适用于文件的常见操作。File类比FileStream类更为简化和高层,适合于文件的常规操作

常用方法

  1. 创建文件
File.Create(string path);
  1. 复制文件
File.Copy(string sourceFileName, string destFileName);
File.Copy(string sourceFileName, string destFileName, bool overwrite);
  1. 删除文件
File.Delete(string path)
  1. 检查文件是否存在
File.Exists(string path);
  1. 移动文件
File.Move(string sourceFileName, string destFileName);
  1. 读取文件内容
File.ReadAllText(string path);
File.ReadAllLines(string path);
  1. 写入内容到文件
File.WriteAllText(string path, string contents);
File.WriteAllLines(string path, string[] contents);
  1. 追加内容到文件
File.AppendAllText(string path, string contents);
File.AppendAllLines(string path, string[] contents);
  1. 获取文件信息
File.GetCreationTime(string path);
File.GetLastAccessTime(string path);
File.GetLastWriteTime(string path);

总结

Binary Stream

二进制流是以二进制数据序列(0和1的字节序列)的方式在存储设备或网络中传输/存储信息的一种形式
相比于文本流(Text Stream,通常是以字符编码形式的字节流),二进制流更原始,它不依赖编码,而是直接操作原始字节

二进制流的作用

如果只用文本流,没法高效处理非文本数据比如:

这些文件都不能简单地用字符集去解释,只能用二进制流来表示

二进制流特点

  1. 原始性:直接操作字节,不依赖字符编码
  2. 通用性:任何文件最终都可以用二进制流表示
  3. 高效性:避免字符转换,更接近底层存储
  4. 不可读性:无法直接阅读,必须通过解析器或协议还原 // TODO: