一、基础概念
1.1、文件和流的基本概念
1.1.1、文件
在C#中,文件被视为一个包含数据的特殊类型的流。它们可以是文本文件、二进制文件或任何其他类型的数据文件。在C#中,可以使用System.IO命名空间中的类来操作文件,例如File、FileInfo、StreamReader和StreamWriter等。
1.1.2、流
流是一种处理数据的机制,可以用来读取或写入数据块。在C#中,流的概念主要涉及几个重要的抽象概念:
Stream: 这是所有流的基类,定义了读取和写入数据的基本方法。
MemoryStream: 这是一个在内存中处理数据的流。
FileStream: 这是一个用于处理文件的流。
NetworkStream: 这是一个用于处理网络连接的流。
BufferedStream: 这是一个缓冲流,它提供了一个缓冲区来存储数据,可以提高性能。
这些流类提供了一系列的方法和属性,可以用来读取、写入、查找和跟踪数据的位置等。它们也支持异步操作,使得数据读写更为高效。
1.1.3、文件和流的关系
在C#中,文件通常被视为一种特殊的流。当您打开一个文件进行读取或写入时,实际上是创建了一个与该文件关联的流对象。通过这个流对象,可以读取或写入文件中的数据。因此,文件操作通常是通过流来实现的。
1.2、C#中的文件路径和文件名
1.2.1、文件路径
文件路径是指从根目录到文件的完整路径。在不同的操作系统中,路径分隔符是不同的。例如,Windows使用反斜杠(\
),而Linux和Mac使用正斜杠(/
)。
C#中的Path
类可以帮助你处理文件路径的相关操作,如拼接、获取目录名、获取文件名等。
string path = @"C:\Users\Username\Documents\MyFile.txt";
string directory = Path.GetDirectoryName(path); // 获取目录 "C:\Users\Username\Documents"
string fileName = Path.GetFileName(path); // 获取文件名 "MyFile.txt"
1.2.2、相对路径与绝对路径
相对路径是从当前工作目录开始的路径,而绝对路径是从根目录开始的完整路径。
string relativePath = "SubFolder/SubSubFolder/MyFile.txt";
string absolutePath = @"C:\Users\Username\Documents\" + relativePath; // 绝对路径为 "C:\Users\Username\Documents\SubFolder\SubSubFolder\MyFile.txt"
1.2.3、文件名与扩展名
文件名是文件的标识符,通常包括文件的主名和扩展名两部分,它们之间用点(.
)分隔。例如,MyFile.txt
中,MyFile
是主名,.txt
是扩展名。
1.2.4、特殊字符处理
在文件名和路径中,有些字符可能具有特殊含义或不能使用。例如,反斜杠(\
)在Windows中是路径分隔符,因此如果要在文件名或路径中使用它,需要使用双反斜杠(\\
)进行转义。
string pathWithSlash = @"C:\Users\Username\MyFolder\MyFileWith\Slash.txt";
1.2.5、跨平台考虑
如果你编写的C#代码需要在不同的操作系统上运行(例如,Windows、Linux和Mac),那么你需要确保你的代码能够正确处理不同操作系统的文件路径规则。例如,使用Path
类来处理路径可以帮助你避免许多跨平台问题。
1.2.6、使用正斜杠
为了避免在不同操作系统之间的问题,你可以在字符串中使用正斜杠(/
)作为路径分隔符。大多数现代的操作系统都能正确解析正斜杠作为路径分隔符。
string path = "C:/Users/Username/Documents/MyFile.txt"; // 使用正斜杠作为路径分隔符
1.2.7、环境变量
- 在Windows中,你可以使用环境变量来引用某些常用的目录,例如
%USERPROFILE%\Documents
会指向用户文档文件夹。在Linux中,类似的变量有$HOME
或$DOCUMENTS
等。在C#中,你可以使用Environment.GetFolderPath
方法来获取这些环境变量对应的目录。 - 示例:获取用户文档文件夹的路径:
string userDocumentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
二、文件读写操作
2.1、File类
2.1.1、创建文件
使用 File.Create()
方法可以创建一个新文件。如果文件已经存在,此方法将抛出异常。
string filePath = @"test.txt";
// 创建文件并获取文件流
using (FileStream fileStream = File.Create(filePath))
{
// 可以向文件写入数据,例如:
byte[] data = System.Text.Encoding.UTF8.GetBytes("Hello, everyone!");
fileStream.Write(data, 0, data.Length);
}
1.2.2、复制文件
使用 File.Copy()
方法可以复制一个文件到另一个位置。
string sourceFile = @"C:\example\sourcefile.txt";
string destinationFile = @"C:\example\destinationfile.txt";
// 复制文件
File.Copy(sourceFile, destinationFile, true); // 第三个参数表示如果目标文件已存在则覆盖它
1.2.3、删除文件
使用 File.Delete()
方法可以删除一个文件。
string filePath = @"C:\example\oldfile.txt";
// 删除文件
File.Delete(filePath);
1.2.4、移动文件
使用 File.Move()
方法可以将文件移动到另一个位置。
string sourcePath = @"C:\example\moveresource.txt";
string destinationPath = @"C:\example\moveddestination.txt";
// 移动文件到新位置
File.Move(sourcePath, destinationPath);
1.2.5、打开文件
要读取或写入文件,需要先打开它。可以使用 FileStream
或其他流类(如 StreamReader
和 StreamWriter
)打开文件。以下是如何使用 StreamReader
和 StreamWriter
打开文件的示例:
读取文本文件
string filePath = @"C:\example\textfile.txt";
string contentRead = "";
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
using (StreamReader reader = new StreamReader(fileStream))
{
contentRead = reader.ReadToEnd(); // 读取整个文件内容到字符串
}
}
Console.WriteLine("读取的内容: " + contentRead);
写入文本文件
string filePath = @"C:\example\newtextfile.txt";
string contentToWrite = "这是一段文本内容"; // 要写入文件的内容
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
byte[] bytesToWrite = System.Text.Encoding.UTF8.GetBytes(contentToWrite);
fileStream.Write(bytesToWrite, 0, bytesToWrite.Length);
}
2.2、StreamReader和StreamWriter类读写文件
using System;
using System.IO;
class Program
{
static void Main()
{
string filePath = @"C:\example\file.txt"; // 文件路径
string contentToWrite = "这是一段文本内容"; // 要写入文件的内容
string contentToRead = "默认读取的内容"; // 默认读取的内容
// 写入文件
using (StreamWriter writer = new StreamWriter(filePath))
{
writer.Write(contentToWrite); // 写入内容到文件
}
// 读取文件
using (StreamReader reader = new StreamReader(filePath))
{
contentToRead = reader.ReadToEnd(); // 读取整个文件内容到字符串
}
Console.WriteLine("读取的内容: " + contentToRead); // 输出读取的内容到控制台
}
}
2.3、使用BinaryReader和BinaryWriter进行二进制文件的读写操作。
using System;
using System.IO;
class Program
{
static void Main()
{
string filePath = @"C:\example\binaryfile.bin"; // 二进制文件路径
int[] numbersToWrite = { 1, 2, 3, 4, 5 }; // 要写入文件的整数数组
int[] numbersRead = new int[0]; // 用于存储读取的整数
// 写入二进制文件
using (BinaryWriter writer = new BinaryWriter(File.Open(filePath, FileMode.Create)))
{
foreach (int number in numbersToWrite)
{
writer.Write(number); // 写入整数到二进制文件
}
}
// 读取二进制文件
using (BinaryReader reader = new BinaryReader(File.Open(filePath, FileMode.Open)))
{
// 由于我们知道文件中有5个整数,所以读取5个整数。在实际应用中,可能需要通过其他方式知道要读取多少数据。
numbersRead = new int[numbersToWrite.Length];
for (int i = 0; i < numbersToWrite.Length; i++)
{
numbersRead[i] = reader.ReadInt32(); // 从二进制文件中读取整数
}
}
// 输出读取的整数数组到控制台
Console.WriteLine("读取的整数数组:");
foreach (int number in numbersRead)
{
Console.WriteLine(number);
}
}
}
三、IO流操作
3.1、IO流类型
IO流(Input/Output Streams)是一组用于在程序中执行输入和输出操作的类。它们允许数据在各种存储位置(例如文件、内存或网络)之间流动。
- FileStream: 用于读写磁盘文件。它提供了对文件系统中的文件的直接访问,并允许以字节为单位读取和写入数据。
- MemoryStream: 用于在内存中处理数据流。你可以用它来操作存储在内存中的字节序列,比如缓冲区或者byte数组。
- BufferedStream: 提供了缓冲区来存储要读取或写入的字节。使用BufferedStream可以提高IO操作的效率,因为它减少了直接从内存到文件或网络的低级读写操作的数量。
- NetworkStream: 用于处理网络连接上的数据流。它是.NET网络编程中的核心类,允许你通过TCP、UDP协议发送和接收数据。
- StreamReader/StreamWriter: 这些类用于处理文本数据流,特别是从文件或网络流中读取和写入字符序列。StreamReader基于UTF-8编码读取字符,而StreamWriter则使用UTF-8编码写入字符。
- BinaryReader/BinaryWriter: 这些类提供了一种方便的方式来以二进制格式读取和写入基本数据类型(如int, float, bool等)。它们内部使用StreamReader和StreamWriter来执行实际的IO操作。
- MemoryMappedFiles: 该类允许你将一个文件映射到进程的内存空间,这样可以像访问内存一样访问文件数据,使得大文件的处理更为高效。
3.2、如何使用不同的IO流进行读写操作
使用异步读写FileStream
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string filePath = "example.txt"; // 文件路径
string contentToWrite = "这是一段文本内容"; // 要写入的内容
string contentToRead = "默认读取的内容"; // 默认读取的内容
// 异步写入文件
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
await fileStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes(contentToWrite), 0, contentToWrite.Length);
}
// 异步读取文件
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
byte[] bytesToRead = new byte[fileStream.Length]; // 创建足够大的字节数组来存储文件内容
await fileStream.ReadAsync(bytesToRead, 0, bytesToRead.Length); // 从文件流中异步读取数据到字节数组
contentToRead = System.Text.Encoding.UTF8.GetString(bytesToRead); // 将字节数组转换回字符串
}
Console.WriteLine("读取的内容: " + contentToRead); // 输出读取的内容到控制台
}
}
使用BufferedStream进行缓冲读写操作
using System;
using System.IO;
class Program
{
static void Main()
{
string filePath = "example.txt"; // 文件路径
string contentToWrite = "这是一段文本内容"; // 要写入的内容
string contentToRead = "默认读取的内容"; // 默认读取的内容
// 写入文件
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
using (BufferedStream bufferedStream = new BufferedStream(fileStream))
{
byte[] bytesToWrite = System.Text.Encoding.UTF8.GetBytes(contentToWrite); // 将字符串转换为字节数组
bufferedStream.Write(bytesToWrite, 0, bytesToWrite.Length); // 写入缓冲流
}
}
// 读取文件
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
using (BufferedStream bufferedStream = new BufferedStream(fileStream))
{
byte[] bytesToRead = new byte[bufferedStream.Length]; // 创建足够大的字节数组来存储文件内容
bufferedStream.Read(bytesToRead, 0, bytesToRead.Length); // 从缓冲流中读取数据到字节数组
contentToRead = System.Text.Encoding.UTF8.GetString(bytesToRead); // 将字节数组转换回字符串
}
}
Console.WriteLine("读取的内容: " + contentToRead); // 输出读取的内容到控制台
}
}
3.3、如何使用流的序列化和反序列化操作
流的序列化和反序列化操作通常用于将对象的状态转换为字节流,以便于存储或传输,然后再将字节流转换回对象的状态。以下是使用流的序列化和反序列化操作的一般步骤:
1.定义要序列化的对象:首先,需要定义一个要序列化的对象。该对象可以是任何类型,只要实现了Serializable
属性即可。
[Serializable]
public class MySerializableObject
{
public int Id { get; set; }
public string Name { get; set; }
}
2.创建序列化器:接下来,需要创建一个序列化器,用于将对象转换为字节流。在C#中,可以使用BinaryFormatter
类来实现二进制序列化。
BinaryFormatter formatter = new BinaryFormatter();
3.序列化对象:使用序列化器将对象序列化为字节流。需要指定一个流来存储序列化后的数据。例如,可以使用FileStream
来将数据写入文件,或者使用MemoryStream
来将数据存储在内存中。
MySerializableObject obj = new MySerializableObject { Id = 1, Name = "Example" };
using (FileStream stream = new FileStream("serialized.bin", FileMode.Create))
{
formatter.Serialize(stream, obj);
}
3.反序列化对象:要将字节流转换回对象的状态,需要使用反序列化器。与序列化器类似,可以使用BinaryFormatter
类来实现二进制反序列化。
using (FileStream stream = new FileStream("serialized.bin", FileMode.Open))
{
MySerializableObject obj = (MySerializableObject)formatter.Deserialize(stream);
}
这样,就可以使用流的序列化和反序列化操作将对象的状态保存到文件或内存中,然后再将其恢复为对象的状态。
四、文件操作示例
4.1、简单的文件复制程序
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
string sourceFile = @"C:\path\to\source\file.txt"; // 源文件路径
string destinationFile = @"C:\path\to\destination\file.txt"; // 目标文件路径
try
{
// 使用StreamReader读取源文件内容
using (StreamReader reader = new StreamReader(sourceFile))
{
// 使用StreamWriter写入目标文件内容
using (StreamWriter writer = new StreamWriter(destinationFile))
{
string line;
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line);
}
}
}
Console.WriteLine("文件复制成功!");
}
catch (Exception ex)
{
Console.WriteLine("发生错误: " + ex.Message);
}
}
}
4.2、简单的图片查看器
注意:需要先在解决方案里引用System.Windows.Forms。
using System.IO;
using System;
using System.Windows.Forms;
using System.Drawing;
class Program
{
static void Main(string[] args)
{
string imagePath = @"C:\path\to\image.jpg"; // 图片文件路径
Image image = LoadImage(imagePath);
ShowImage(image);
}
static Image LoadImage(string path)
{
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
using (BinaryReader reader = new BinaryReader(stream))
{
// 将二进制数据读取到一个字节数组中
byte[] imageData = reader.ReadBytes((int)stream.Length);
// 使用字节数组创建一个 Image 对象
Image image = Image.FromStream(new MemoryStream(imageData));
return image;
}
}
}
static void ShowImage(Image image)
{
if (image == null)
{
Console.WriteLine("无法加载图片。");
return;
}
// 创建一个新的窗体来显示图片
Form form = new Form();
form.Width = image.Width + 20;
form.Height = image.Height + 50;
form.Paint += (sender, e) => e.Graphics.DrawImage(image, 0, 0);
form.ShowDialog(); // 显示窗体,等待关闭
}
}
4.3、网络通信程序
服务器端代码:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
class Server
{
static void Main()
{
TcpListener listener = null;
TcpClient client=null;
NetworkStream stream=null;
try
{
// 绑定到本地端口8000,并开始监听传入的连接请求
listener = new TcpListener(IPAddress.Any, 8000);
listener.Start();
Console.WriteLine("服务器已启动,等待客户端连接...");
// 等待客户端连接请求
client = listener.AcceptTcpClient();
Console.WriteLine("客户端已连接:" + client.Client.RemoteEndPoint);
// 获取网络流
stream = client.GetStream();
byte[] buffer = new byte[1024];
int bytesRead;
// 循环读取客户端发送的数据
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
{
Console.WriteLine("收到客户端消息:");
Console.Write(Encoding.ASCII.GetString(buffer, 0, bytesRead));
Console.Write("\n");
// 将接收到的数据回传给客户端
stream.Write(buffer, 0, bytesRead);
}
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
finally
{
// 关闭流和客户端连接
if (stream != null)
{
stream.Close();
}
if (client != null)
{
client.Close();
}
if (listener != null)
{
listener.Stop();
}
}
}
}
客户端代码:
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
class Client
{
static async Task Main()
{
TcpClient client = new TcpClient();
try
{
// 连接到服务器端IP地址和端口号(这里假设服务器端IP为127.0.0.1,端口号为8000)
client.Connect("127.0.0.1", 8000);
Console.WriteLine("已连接到服务器。");
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
finally
{
// 获取网络流,用于发送和接收数据
NetworkStream stream = client.GetStream();
byte[] message = Encoding.ASCII.GetBytes("Hello, server!"); // 要发送的消息内容,这里发送一个简单的字符串消息"Hello, server!"。可以根据需要修改。
byte[] buffer = new byte[1024];
}
}
}