本章主要记录 FileStream 和 StreamRead 在需要频繁读写时,如何联合使用的场景。
备注:任何性能提升都几乎完全来自并行处理而不是异步处理。 异步的优点在于它不会占用多个线程,也不会占用用户界面线程。
【参考文章】
https://learn.microsoft.com/zh-cn/dotnet/api/system.io?view=net-7.0
https://blog.csdn.net/awangdea99/article/details/100694544
https://blog.csdn.net/zhangguoliang0210/article/details/4138428
FileStream 基本操作
为文件提供 Stream,既支持同步读写操作,也支持异步读写操作。主要是以字节为单位。
以下演示了 FileStream 的一些常用操作:
using System;
using System.IO;
using System.Text;
class Test
{
public static void Main()
{
string path = @"c:\temp\MyTest.txt";
// 如果文件存在则删除
if (File.Exists(path))
{
File.Delete(path);
}
//创建文件,并写入内容
using (FileStream fs = File.Create(path))
{
AddText(fs, "This is some text");
AddText(fs, "This is some more text,");
AddText(fs, "\r\nand this is on a new line");
AddText(fs, "\r\n\r\nThe following is a subset of characters:\r\n");
for (int i=1;i < 120;i++)
{
AddText(fs, Convert.ToChar(i).ToString());
}
}
//打开文件并读取至输出窗口
using (FileStream fs = File.OpenRead(path))
{
byte[] b = new byte[1024];
UTF8Encoding temp = new UTF8Encoding(true);
int readLen;
//readLen:读入缓冲区中的总字节数。 如果字节数当前不可用,则总字节数可能小于所请求的字节数;如果已到达流结尾,则为零。
//b:当此方法返回时,包含指定的字节数组,此数组中 offset 和 (offset + count - 1) 之间的值被从当前源中读取的字节所替换。
while ((readLen = fs.Read(b,0,b.Length)) > 0)
{
Console.WriteLine(temp.GetString(b,0,readLen));
}
}
}
private static void AddText(FileStream fs, string value)
{
byte[] info = new UTF8Encoding(true).GetBytes(value);
//将字节块写入文件流。info:要写入该流的数据的缓冲区;0:从0开始;info.length:最多写入的字节数
fs.Write(info, 0, info.Length);
}
}
以下示例演示如何使用两个 FileStream 对象将文件从一个目录异步复制到另一个目录。 FileStream 类是从 Stream 类派生的。 需要注意 Click 控件的 Button 事件处理程序具有 async 修饰符标记,因为它调用异步方法。
using System;
using System.Threading.Tasks;
using System.Windows;
using System.IO;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
string StartDirectory = @"c:\Users\exampleuser\start";
string EndDirectory = @"c:\Users\exampleuser\end";
foreach (string filename in Directory.EnumerateFiles(StartDirectory))
{
using (FileStream SourceStream = File.Open(filename, FileMode.Open))
{
using (FileStream DestinationStream = File.Create(EndDirectory + filename.Substring(filename.LastIndexOf('\\'))))
{
await SourceStream.CopyToAsync(DestinationStream);
}
}
}
}
}
}
以下演示文件复制操作
static void Main(string[] args)
{
string sourcePath = @"D:\From\source.mp4";//需要被复制的文件的路径
string targetPath = @"D:\To\target.mp4";//复制到的路径
using (FileStream fsRead = new FileStream(sourcePath, FileMode.OpenOrCreate, FileAccess.Read))
{//创建读取文件的流
using (FileStream fsWrite = new FileStream(targetPath, FileMode.OpenOrCreate, FileAccess.Write))
{//创建写入文件的流
byte[] buffer = new byte[1024 * 1024 * 2];//缓存设置2MB;
while (true)//循环读取
{
int r = fsRead.Read(buffer, 0, buffer.Length);//读数据
if (r == 0)//读不到数据了,跳出循环
{
break;
}
fsWrite.Write(buffer, 0, r);//写数据
}
}
}
Console.WriteLine("复制完成!");
Console.ReadKey();
}
以下示例演示如何以异步方式写入文件。 此代码在 WPF 应用中运行,该应用具有一个名为 UserInput 的 TextBlock 和一个与名为 Button_Click 的 Click 事件处理程序挂钩的按钮。 需要将文件路径更改为计算机上存在的文件。
using System;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.IO;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
UnicodeEncoding uniencoding = new UnicodeEncoding();
string filename = @"c:\Users\exampleuser\Documents\userinputlog.txt";
byte[] result = uniencoding.GetBytes(UserInput.Text);
using (FileStream SourceStream = File.Open(filename, FileMode.OpenOrCreate))
{
SourceStream.Seek(0, SeekOrigin.End);
await SourceStream.WriteAsync(result, 0, result.Length);
}
}
}
}
StreamRead 和 StreamWrite 基本操作
StreamRead: 实现从字符串进行读取的 TextReader。
StreamWrite: 实现用于将信息写入字符串的 TextWriter。 信息存储在基础 StringBuilder 中。
以下演示如何使用 StreamWriter 对象写入一个文件,该文件列出 C 驱动器上的目录,然后使用 StreamReader 对象读取和显示每个目录名称。 一种很好的做法是在语句中使用这些对象, using 以便正确地释放非托管资源。 using当使用对象的代码已完成时,该语句会自动对 Dispose 该对象调用。 在此示例中使用的构造函数不支持在 Windows 应用商店应用程序中使用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace StreamReadWrite
{
class Program
{
static void Main(string[] args)
{
// Get the directories currently on the C drive.
DirectoryInfo[] cDirs = new DirectoryInfo(@"c:\").GetDirectories();
// Write each directory name to a file.
using (StreamWriter sw = new StreamWriter("CDriveDirs.txt"))
{
foreach (DirectoryInfo dir in cDirs)
{
sw.WriteLine(dir.Name);
}
}
// Read and show each line from the file.
string line = "";
using (StreamReader sr = new StreamReader("CDriveDirs.txt"))
{
while ((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
}
}
FileStream 和 StreamWrite
以下演示使用指定的编码及默认的缓冲区大小,为指定的流初始化 StreamWriter 类的新实例。
using System;
using System.IO;
using System.Text;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
string fileName = "test.txt";
string textToAdd = "Example text in file";
FileStream fs = null;
try
{
fs = new FileStream(fileName, FileMode.CreateNew);
using (StreamWriter writer = new StreamWriter(fs, Encoding.Default))
{
writer.Write(textToAdd);
}
}
finally
{
if (fs != null)
fs.Dispose();
}
}
}
}
以下演示写操作,不确定添加lock是否影响性能,望指正。
public async void WriteCommandLog(string fileName, string strData)
{
try
{
lock (locker) //这里使用lock是为了防止线程之间竞争,造成记录缺失
{
using (FileStream SourceStream = new FileStream(fileName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
using (StreamWriter writer = new StreamWriter(SourceStream, Encoding.UTF8))
{
writer.WriteLine(strData);
}
}
}
catch(IOException ioEx)
{
MessageBox.Show(ioEx.Message.ToString());
}
}