.NET 中的流操作:从基础到高级应用
在 .NET 编程中,流(Stream)是处理输入输出(I/O)操作的核心概念。流提供了一种统一的方式来处理不同类型的数据源,包括文件、内存和网络连接。本文将深入探讨 .NET 中流的各种应用场景,包括二进制文件读写、缓冲流、文本文件处理、异步 I/O 和网络 I/O。
二进制文件读写
当不确定文件是否仅包含文本时,将其视为二进制文件进行处理是最安全的做法。在 .NET 中,可以使用
Stream
类来执行二进制文件的读写操作。
Stream
类提供了多个重要方法,如
Read()
、
Write()
、
BeginRead()
、
BeginWrite()
和
Flush()
。
以下是一个简单的二进制文件读写示例:
using System;
using System.IO;
namespace ImplementingBinaryReadWriteToFile
{
class Tester
{
const int SizeBuff = 1024;
public static void Main()
{
Tester t = new Tester();
t.Run();
}
private void Run()
{
Stream inputStream = File.OpenRead(@"C:\test\source\test1.cs");
Stream outputStream = File.OpenWrite(@"C:\test\source\test1.bak");
byte[] buffer = new Byte[SizeBuff];
int bytesRead;
while ((bytesRead = inputStream.Read(buffer, 0, SizeBuff)) > 0)
{
outputStream.Write(buffer, 0, bytesRead);
}
inputStream.Close();
outputStream.Close();
}
}
}
在这个示例中,我们首先创建了两个
Stream
对象,一个用于读取文件,另一个用于写入文件。然后,我们使用一个循环不断从输入流中读取数据,并将其写入输出流,直到没有更多数据可读为止。最后,我们关闭了两个流。
缓冲流
在前面的示例中,我们手动创建了一个缓冲区来读取数据。然而,操作系统可能可以通过一次读取更多或更少的字节来提高效率。缓冲流(
BufferedStream
)可以帮助我们实现这一点。
以下是一个使用缓冲流的示例:
using System;
using System.IO;
namespace Programming_CSharp
{
class Tester
{
const int SizeBuff = 1024;
public static void Main()
{
Tester t = new Tester();
t.Run();
}
private void Run()
{
Stream inputStream = File.OpenRead(@"C:\test\source\folder3.cs");
Stream outputStream = File.OpenWrite(@"C:\test\source\folder3.bak");
BufferedStream bufferedInput = new BufferedStream(inputStream);
BufferedStream bufferedOutput = new BufferedStream(outputStream);
byte[] buffer = new Byte[SizeBuff];
int bytesRead;
while ((bytesRead = bufferedInput.Read(buffer, 0, SizeBuff)) > 0)
{
bufferedOutput.Write(buffer, 0, bytesRead);
}
bufferedOutput.Flush();
bufferedInput.Close();
bufferedOutput.Close();
}
}
}
在这个示例中,我们首先创建了两个普通的
Stream
对象,然后将它们传递给
BufferedStream
的构造函数,创建了两个缓冲流。接下来,我们使用缓冲流进行读写操作,最后记得调用
Flush()
方法确保数据被写入文件。
文本文件处理
如果确定要读写的文件仅包含文本,可以使用
StreamReader
和
StreamWriter
类来简化文本处理。这两个类提供了
ReadLine()
和
WriteLine()
方法,方便逐行读写文本。
以下是一个文本文件读写的示例:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ReadingWritingToTextFile
{
class Tester
{
public static void Main()
{
Tester t = new Tester();
t.Run();
}
private void Run()
{
FileInfo theSourceFile = new FileInfo(@"C:\test\source\test.cs");
StreamReader reader = theSourceFile.OpenText();
StreamWriter writer = new StreamWriter(@"C:\test\source\test.bak", false);
string text;
do
{
text = reader.ReadLine();
writer.WriteLine(text);
Console.WriteLine(text);
} while (text != null);
reader.Close();
writer.Close();
}
}
}
在这个示例中,我们首先创建了一个
FileInfo
对象,然后调用其
OpenText()
方法创建了一个
StreamReader
对象。接着,我们创建了一个
StreamWriter
对象,并使用一个循环逐行读取文件内容,将其写入新文件并打印到控制台。最后,我们关闭了两个流。
异步 I/O
前面介绍的所有程序都是同步 I/O,即在读写操作进行时,程序的其他活动会被暂停。对于大文件或网络读写,异步 I/O 可以提高程序的性能。.NET 框架通过
Stream
类的
BeginRead()
和
BeginWrite()
方法提供了异步 I/O 支持。
以下是一个异步 I/O 的示例:
using System;
using System.IO;
namespace AsynchronousIO
{
public class AsynchIOTester
{
private Stream inputStream;
private AsyncCallback myCallBack;
private byte[] buffer;
const int BufferSize = 256;
AsynchIOTester()
{
inputStream = File.OpenRead(@"C:\test\source\AskTim.txt");
buffer = new byte[BufferSize];
myCallBack = new AsyncCallback(this.OnCompletedRead);
}
public static void Main()
{
AsynchIOTester theApp = new AsynchIOTester();
theApp.Run();
}
void Run()
{
inputStream.BeginRead(
buffer, 0, buffer.Length,
myCallBack, null);
for (long i = 0; i < 500000; i++)
{
if (i % 1000 == 0)
{
Console.WriteLine("i: {0}", i);
}
}
}
void OnCompletedRead(IAsyncResult asyncResult)
{
int bytesRead = inputStream.EndRead(asyncResult);
if (bytesRead > 0)
{
String s = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine(s);
inputStream.BeginRead(
buffer, 0, buffer.Length, myCallBack, null);
}
}
}
}
在这个示例中,我们首先创建了一个
AsynchIOTester
类,在构造函数中打开文件、分配缓冲区并设置回调方法。在
Run()
方法中,我们调用
BeginRead()
方法开始异步读取文件,然后进行其他工作。当读取完成时,CLR 会调用回调方法
OnCompletedRead()
,在该方法中我们处理读取的数据,并可以再次调用
BeginRead()
方法进行下一次读取。
网络 I/O
网络 I/O 与本地文件读写类似,可以使用基于套接字(Socket)创建的流来实现。套接字在客户端/服务器和点对点(P2P)应用程序以及远程过程调用中非常有用。
在本节中,我们将创建一个基于 TCP/IP 的服务器和客户端连接。TCP/IP 是一种基于连接的流协议,一旦建立连接,两个进程就可以像通过直接电话线连接一样进行通信。
以下是网络 I/O 的基本流程:
1.
端口概念
:每个应用程序在网络中需要一个唯一的 ID,即端口。端口号范围从 0 到 65535,分为不同的范围:
- 0 - 1023:知名端口
- 1024 - 49151:注册端口
- 49152 - 65535:动态和/或私有端口
2.
服务器端操作
:
- 实例化
TcpListener
,指定 IP 地址和端口。
- 调用
Start()
方法开始接受网络连接。
- 调用
AcceptSocket()
方法等待客户端连接,该方法会阻塞当前线程。
3.
客户端操作
:
- 连接到指定的 IP 地址和端口。
以下是一个简单的 TCP/IP 服务器示例的 mermaid 流程图:
graph TD;
A[创建 TcpListener] --> B[调用 Start() 方法];
B --> C[调用 AcceptSocket() 方法];
C --> D[等待客户端连接];
D --> E[与客户端通信];
通过以上介绍,我们了解了 .NET 中流的各种应用场景,包括二进制文件读写、缓冲流、文本文件处理、异步 I/O 和网络 I/O。掌握这些知识可以帮助我们更高效地处理各种 I/O 操作,提高程序的性能和稳定性。
.NET 中的流操作:从基础到高级应用(续)
网络 I/O 深入探讨
在前面我们已经了解了网络 I/O 的基本概念和 TCP/IP 连接的基本流程,接下来我们进一步深入探讨网络 I/O 的一些细节。
在实际应用中,服务器可能需要同时处理多个客户端的请求。如果采用前面简单的单线程模型,当一个客户端连接时,其他客户端就需要等待,这显然不能满足高并发的需求。为了解决这个问题,我们可以使用多线程来处理客户端连接。
以下是一个简单的多线程 TCP/IP 服务器示例:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
class Server
{
private const int Port = 12345;
public static void Main()
{
TcpListener listener = new TcpListener(IPAddress.Any, Port);
listener.Start();
Console.WriteLine("Server started. Waiting for connections...");
while (true)
{
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("Client connected.");
// 为每个客户端创建一个新线程来处理
Thread clientThread = new Thread(() => HandleClient(client));
clientThread.Start();
}
}
private static void HandleClient(TcpClient client)
{
try
{
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Received: " + message);
// 回显消息给客户端
byte[] response = Encoding.ASCII.GetBytes("Server received: " + message);
stream.Write(response, 0, response.Length);
}
}
catch (Exception ex)
{
Console.WriteLine("Error handling client: " + ex.Message);
}
finally
{
client.Close();
Console.WriteLine("Client disconnected.");
}
}
}
这个示例中,服务器在一个无限循环中等待客户端连接。当有客户端连接时,会为该客户端创建一个新的线程来处理,这样就可以同时处理多个客户端的请求。
客户端代码示例如下:
using System;
using System.Net.Sockets;
using System.Text;
class Client
{
private const string ServerIp = "127.0.0.1";
private const int Port = 12345;
public static void Main()
{
try
{
TcpClient client = new TcpClient();
client.Connect(ServerIp, Port);
Console.WriteLine("Connected to server.");
NetworkStream stream = client.GetStream();
string message = "Hello, server!";
byte[] data = Encoding.ASCII.GetBytes(message);
stream.Write(data, 0, data.Length);
byte[] buffer = new byte[1024];
int bytesRead = stream.Read(buffer, 0, buffer.Length);
string response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine("Server response: " + response);
client.Close();
}
catch (Exception ex)
{
Console.WriteLine("Error: " + ex.Message);
}
}
}
客户端代码相对简单,它连接到服务器,发送一条消息,然后接收服务器的响应。
不同流类型的比较
在 .NET 中,有多种不同类型的流,它们适用于不同的场景。以下是一个简单的比较表格:
| 流类型 | 特点 | 适用场景 |
| ---- | ---- | ---- |
|
FileStream
| 用于读写文件,支持同步和异步操作 | 本地文件的读写 |
|
MemoryStream
| 数据直接存储在内存中,无需与外部设备交互 | 临时数据的处理,如数据缓存 |
|
NetworkStream
| 基于网络连接的流,用于网络 I/O | 网络通信,如客户端/服务器应用 |
|
BufferedStream
| 提供内部缓冲区,提高读写效率 | 频繁读写操作,尤其是对磁盘文件的读写 |
|
StreamReader
和
StreamWriter
| 专门用于文本处理,提供逐行读写功能 | 文本文件的读写 |
流操作的最佳实践
在进行流操作时,有一些最佳实践可以帮助我们避免常见的错误,提高代码的性能和可维护性。
-
资源管理
:所有的流对象都需要正确地释放资源,避免资源泄漏。可以使用
using语句来确保流对象在使用完毕后自动关闭和释放资源。例如:
using (Stream inputStream = File.OpenRead(@"C:\test\source\test1.cs"))
using (Stream outputStream = File.OpenWrite(@"C:\test\source\test1.bak"))
{
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0)
{
outputStream.Write(buffer, 0, bytesRead);
}
}
- 异常处理 :流操作可能会抛出各种异常,如文件不存在、网络连接失败等。在代码中应该进行适当的异常处理,确保程序的健壮性。例如:
try
{
Stream inputStream = File.OpenRead(@"C:\test\source\test1.cs");
// 其他操作
}
catch (FileNotFoundException ex)
{
Console.WriteLine("文件未找到: " + ex.Message);
}
catch (IOException ex)
{
Console.WriteLine("I/O 错误: " + ex.Message);
}
- 性能优化 :对于频繁的读写操作,可以使用缓冲流来提高性能。同时,合理选择缓冲区的大小也很重要,过大或过小的缓冲区都可能影响性能。
总结
通过本文的介绍,我们全面了解了 .NET 中流的各种应用场景,包括二进制文件读写、缓冲流、文本文件处理、异步 I/O 和网络 I/O。我们学习了不同流类型的特点和适用场景,以及流操作的最佳实践。掌握这些知识可以帮助我们更高效地处理各种 I/O 操作,提高程序的性能和稳定性。在实际开发中,我们应该根据具体的需求选择合适的流类型和操作方式,同时注意资源管理和异常处理,确保代码的健壮性和可维护性。
希望本文对你在 .NET 流操作方面的学习和实践有所帮助。如果你有任何疑问或建议,欢迎留言讨论。
以下是一个总结流操作流程的 mermaid 流程图:
graph LR;
A[选择流类型] --> B{是否为文件操作};
B -- 是 --> C[使用 FileStream 或 StreamReader/StreamWriter];
B -- 否 --> D{是否为网络操作};
D -- 是 --> E[使用 NetworkStream];
D -- 否 --> F{是否需要临时缓存};
F -- 是 --> G[使用 MemoryStream];
F -- 否 --> H[根据需求选择其他流];
C --> I[进行读写操作];
E --> I;
G --> I;
H --> I;
I --> J[资源管理和异常处理];
通过这个流程图,我们可以清晰地看到在不同场景下如何选择合适的流类型,并进行相应的操作。
超级会员免费看
1808

被折叠的 条评论
为什么被折叠?



