10.6.1 文本文件读、写和追加
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。
文本文件的读写通常的做法是建立一个与文件关联的filestream,然后使用StreamReader读取或者StreamWriter写入。
为了详细说明文件读写,提供的示例采用的是读和写方法对应的形式,并不是说某种读写方法非得对应才能进行,第A种写文件的方法可以对应第B种读文件的方法,在实际编程中,请根据实际情况选择最简单的方法使用。
本节要使用到文件读写和字符编码,为简化代码,请在本节所有代码的最前面加上:
using System.IO;
10.6.1.1 使用File类读写文件
File类提供Read开头的方法用于读取文件,提供Write开头的方法用于写入文件。例如ReadAllText方法打开一个文本文件并读取文件的所有行,然后关闭该文件;WriteAllText方法创建一个新文件并写入指定的字符串,然后关闭文件。
【例 10.14】【项目:code10-014】使用File类读写文件。
//读取文件
private void button1_Click(object sender, EventArgs e)
{
//使用打开文件对话框获得文本文件路径
string filename;
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if(ofd.ShowDialog()!= DialogResult.OK)
return;
filename = ofd.FileName;
//使用ReadAllText方法读取文件
textBox1.Text = File.ReadAllText(filename, Encoding.Default );
}
//写入文件
private void button2_Click(object sender, EventArgs e)
{
//使用保存文件对话框获得要保存的文本文件路径
String filename;
SaveFileDialog sfd= new SaveFileDialog();
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() != DialogResult.OK)
return;
filename = sfd.FileName;
//使用WriteAllText方法向文件中写入文本
File.WriteAllText(filename, textBox1.Text, Encoding.Default);
MessageBox.Show("写入文件完毕");
}
需要注意的是:1、在实际代码中,写入文件的时候,如果文件已经存在还判断是否需要覆盖;2、读取、写入文件的时候都需要注意使用的字符编码,写入和读取使用的编码不一致会导致乱码。
运行结果如下图所示:
图10-11 使用File类读写文件
10.6.1.2 StreamReader类和StreamWriter类
StreamReader类提供对文件读取的方法,而StreamWriter类提供对文件写入的方法。
1、StreamReader类以一种特定的编码从文件或字节流中读取字符。
StreamReader常用构造函数:
- StreamReader(Stream):用指定的流初始化StreamReader类的新实例。
- StreamReader(String):用指定的文件初始化StreamReader类的新实例。
- StreamReader(Stream, Encoding):用指定的字符编码为指定的流初始化StreamReader类的新实例。
- StreamReader(String, Encoding):用指定的字符编码,为指定的文件名初始化StreamReader类的新实例。
StreamReader常用方法:
- Close:关闭 StreamReader 对象和基础流,并释放与读取器关联的所有系统资源。
- Peek:返回下一个可用字符,但不使用它。
- Read:读取输入流中字符并使该字符位置提升相应数量字符。
- ReadLine:从当前流中读取一行字符并将数据作为字符串返回。
- ReadToEnd:读取来自流的当前位置到结尾的所有字符。
- ReadAsync:Read的异步版本。
- ReadLineAsync:ReadLine的异步版本。
- ReadToEndAsync:ReadToEnd的异步版本。
2、StreamWriter类以一种特定的编码向文件或流中写入数据。
StreamWriter常用构造函数:
- StreamWriter(String) :用UTF-8编码和默认缓冲区大小,为指定的文件初始化StreamWriter类的新实例。
- StreamWriter(Stream) :用UTF-8编码及默认缓冲区大小,为指定的流初始化StreamWriter类的新实例。
- StreamWriter(Stream, Encoding): 用指定的编码及默认缓冲区大小,为指定的流初始化StreamWriter类的新实例。
- StreamWriter(String, Boolean, Encoding) :用指定的编码和默认缓冲区大小,为指定的文件初始化StreamWriter类的新实例。当指定的文件已经存在,第二个参数为True时向文件追加数据;为False则覆盖文件。 如果指定的文件不存在,则创建一个新文件。
StreamWriter常用方法:
- Flush:清理当前编写器的所有缓冲区,使所有缓冲数据写入基础设备。
- Close:关闭当前的 StreamWriter 对象和基础流。必须调用Close方法以确保所有数据正确都写出到基础流。
- Write:根据所带参数类型,将数据写入文本字符串或流。
- WriteLine:将数据和行结束符写入文本字符串或流。
- WriteAsync:Write的异步版本。
- WriteLineAsync:WriteLine的异步版本。
【例 10.15】【项目:code10-015】一次性读写文件内容。
//读取文件
private void button1_Click(object sender, EventArgs e)
{
//使用打开文件对话框获得文本文件路径
String filename;
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
return;
filename = ofd.FileName;
//用文件名、编码初始化一个StreamReader类的一个新实例
StreamReader sr = new StreamReader(filename, Encoding.Default);
//使用ReadToEnd方法读取文件
textBox1.Text = sr.ReadToEnd();
//关闭并释放资源
sr.Close();
}
//写入文件
private void button2_Click(object sender, EventArgs e)
{
//使用保存文件对话框获得要保存的文本文件路径
String filename;
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() != DialogResult.OK)
return;
filename = sfd.FileName;
//用文件名初始化StreamWriter类的一个新实例
StreamWriter sw =new StreamWriter(filename);
//使用Write方法向文件中写入文本
sw.Write(textBox1.Text, true, Encoding.Default);
//关闭并释放资源
sw.Close();
}
【例 10.16】【项目:code10-016】按行读写文件。
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
return;
string filename = ofd.FileName;
//用文件名、编码初始化一个StreamReader类的一个新实例
StreamReader sr = new StreamReader(filename, Encoding.Default);
string textContent;
//使用ReadLine方法读取一行
textContent = sr.ReadLine();
//如果ReadLine返回的是Nothing,那么表示已经读取到了末尾
while(textContent!=null)
{
if (textBox1.Text == "")
textBox1.Text = textContent;
else
textBox1.Text += "\r\n" + textContent;
textContent = sr.ReadLine();
}
sr.Close();
}
private void button2_Click(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() != DialogResult.OK)
return;
string filename = sfd.FileName;
StreamWriter sw = new StreamWriter(filename, false,Encoding.Default);
string textContent = textBox1.Text;
string[] arrTextContent = textContent.Split(new string[] { "\r\n" }, StringSplitOptions.None);
//写入的时候排除掉字符串数组最后一个空行
for (Int32 i =0; i<arrTextContent.Length-1; i++)
{
//使用WriteLine写入一行
sw.WriteLine(arrTextContent[i]);
}
//关闭并释放资源
sw.Close();
}
【例 10.17】【项目:code10-017】使用流读写文件。
//读取文件
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
return;
string filename = ofd.FileName;
FileStream fs;
//使用FileStream的构造函数,或者File.Open方法都可以获得文件流
//下面两种方法选其一
fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Read);
//或者:
//fs = File.Open(filename, FileMode.OpenOrCreate, FileAccess.Read);
StreamReader sr = new StreamReader(fs, Encoding.Default);
textBox1.Text = sr.ReadToEnd();
sr.Close();
fs.Close();
}
//写入文件
private void button2_Click(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() != DialogResult.OK)
return;
string filename = sfd.FileName;
string textContext = textBox1.Text;
FileStream fs;
//使用FileStream的构造函数,或者File.Open方法都可以获得文件流
//下面两种方法选其一
fs = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Write);
//或者:
//fs = File.Open(filename, FileMode.OpenOrCreate, FileAccess.Write);
//用文件流初始化StreamWriter类的新实例
StreamWriter sw = new StreamWriter(fs, Encoding.Default);
sw.Write(textContext);
//关闭并释放资源
sw.Close();
fs.Close();
}
【例 10.18】【项目:code10-018】按照设定读取字符数量循环读写文件。
本例使用StreamReader.Read和StreamWriter.Write的重载方法:
1、StreamReader.Read(char[] buffer, int index, int count)
第一参数为读取保存到的字符数组,第二个参数为读取的位置,第三个参数为读取的字符数量。
2、StreamWriter.Write(char[] buffer, int index, int count)
第一个参数为要写入流的数据的字符数组,第二个参数为字符数组的读取位置,第三个参数为要写入的最大字符数。
具体代码如下:
//读取文件
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
return;
string filename = ofd.FileName;
//保存循环读取时每次读取的字符串
string textContent;
//每次读取的字符数量
Int32 lenRead = 100;
//字符数组
char[] buffer = new char[lenRead];
//每次读取到的字符数量
Int32 readLength;
StreamReader sr = new StreamReader(filename, Encoding.Default);
//循环,当读取到的字符数小于设置应该读取的字符数时退出
do
{
//使用Read方法读取
readLength = sr.Read(buffer, 0, lenRead);
//字符数组转字符串
textContent = new string(buffer);
//将字符串加入文本框
textBox1.Text += textContent;
//重新为数组变量分配存储空间
buffer = new char[lenRead];
} while (readLength == lenRead);
sr.Close();
}
//写入文件
private void button2_Click(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() != DialogResult.OK)
return;
string filename = sfd.FileName;
string textContent = textBox1.Text;
//字符数组
char[] buffer = textContent.ToCharArray();
//每次写入流的字符数量/读取字符数组的字符数量
int lenWrite = 100;
//'用文件名覆盖该文件,初始化StreamWriter类的一个新实例
StreamWriter sw = new StreamWriter(filename, false);
//当前读取字符数组的位置
int pos = 0;
for(int i=0;i<buffer.Length;i=i+ lenWrite)
{
//如果剩余需要读取的字符数大于最大读取数量
if (buffer.Length - pos * lenWrite > lenWrite)
//写入最大读取数量
sw.Write(buffer, i, lenWrite);
else
//写入剩余部分
sw.Write(buffer, i, buffer.Length - pos * lenWrite);
pos++;
}
//关闭并释放资源
sw.Close();
}
10.6.1.3 异步读写
FileStream提供了BeginRead方法用于异步读取,EndRead方法结束异步读取;BeginWrite方法用于异步写入,EndWrite方法结束异步写入。
1、BeginRead方法的语法:
public override IAsyncResult BeginRead (byte[] array, int offset, int numBytes, AsyncCallback userCallback, object stateObject);
参数说明:
- array:将数据读入的缓冲区。
- offset:array中的字节偏移量,从此处开始读取。
- numBytes:最多读取的字节数。
- userCallback:异步读操作完成后调用的方法,一般称为回调函数。
- stateObject:一个用户提供的对象,这个对象会保存到返回值IasyncResult的属性AsyncState。
返回值:
- 引用异步读取的对象。这是一个System.IAsyncResult类型。
注意:回调函数userCallback是一个固定的格式:
private void AsyncReadCallBack(IAsyncResult aResult){ }
2、EndRead方法的语法:
public override int EndRead (IAsyncResult asyncResult);
参数说明:
- asyncResult:对所等待的挂起异步请求的引用。
返回值:
- 从流中读取的字节数,介于 0 和所请求的字节数之间。 流仅在流结尾返回 0,否则在至少有 1 个字节可用之前应一直进行阻止。
3、BeginWrite方法的语法:
public override IAsyncResult BeginWrite (byte[] array, int offset, int numBytes, AsyncCallback userCallback, object stateObject);
参数说明:
- array:包含要写入当前流的数据的缓冲区。
- offset:array 中的从零开始的字节偏移量,从此处开始将字节复制到当前流。
- numBytes:写入的最大字节数。
- userCallback:异步写操作完成后调用的方法。
- stateObject:一个用户提供的对象,它将该特定的异步写入请求与其他请求区别开来。
返回值:
- 引用异步写的 IAsyncResult。
4、EndWrite方法的语法:
public override void EndWrite (IAsyncResult asyncResult);
参数说明:
- asyncResult:挂起的异步 I/O 请求。
BeginRead方法和EndRead方法在.NET Framework版本4.5以上将逐步被ReadAsync替代;BeginWrite方法和EndWrite方法在.NET Framework版本4.5以上将逐步被WriteAsync替代。
ReadAsync、WriteAsync比BeginRead、BeginWrite使用更为简单。
【例 10.19】【项目:code10-019】异步读写文件。
在本例中为了演示异步操作,使用了FileStream的BeginRead、EndRead、BeginWrite、EndWrite方法,以及StreamReader的ReadAsync、ReadLineAsync方法,StreamWriter的WriteAsync、WriteLineAsync方法。
本例中使用到了一个fsAsyncInfo类,用来演示在异步BeginRead中如何使用传递给回调函数参数IasyncResult的AsyncState属性。
public class fsAsyncInfo
{
//包含执行读取的文件流
public FileStream fs;
//执行读取时的缓冲
public Byte[] buffer;
}
其中还使用到了Task 类,它表示一个异步操作。它的Wait方法,确保Task 完成执行过程。
具体代码如下:
//定义每次进行异步读取的缓冲区字节数组大小
int buffersize = 10240;
//异步读取
private void btnBeginRead_Click(object sender, EventArgs e)
{
CheckForIllegalCrossThreadCalls = false;
//使用打开文件对话框获得文本文件路径
string filename;
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
return;
filename = ofd.FileName;
textBox1.Text = "";
//用文件名、编码初始化一个StreamReader类的一个新实例
FileStream fsRead = new FileStream(filename, FileMode.Open, FileAccess.Read);
fsAsyncInfo fsinfo = new fsAsyncInfo();
fsinfo.fs = fsRead;
fsinfo.buffer = new byte[buffersize];
//调用BeginRead进行异步读取,其中回调函数为AsyncReadCallBack
fsRead.BeginRead(fsinfo.buffer, 0, buffersize, new AsyncCallback( AsyncReadCallBack), fsinfo);
}
//异步读操作完成后调用的方法
private void AsyncReadCallBack(IAsyncResult aResult)
{
fsAsyncInfo fsinfo = new fsAsyncInfo();
fsinfo = (fsAsyncInfo)aResult.AsyncState;
//从流中读取的字节数,如果在流的末尾则会返回 0
int intRead;
intRead = fsinfo.fs.EndRead(aResult);
//读取到的字节数组
byte[] readbuffer;
if( intRead > 0 )
{
readbuffer = new byte[intRead];
//将实际读取的字节拷贝到readbuffer中
Array.Copy(fsinfo.buffer, readbuffer, intRead);
textBox1.Text+= Encoding.Default.GetString(readbuffer);
}
//如果读取的缓冲字节数量小于设定的字节数量,那么表示已经读取完毕,
if(intRead >= buffersize)
{
//清除数组
Array.Clear(fsinfo.buffer, 0, buffersize);
//继续调用BeginRead异步
fsinfo.fs.BeginRead(fsinfo.buffer, 0, buffersize, AsyncReadCallBack, fsinfo);
}
else
{
//关闭流
fsinfo.fs.Close();
MessageBox.Show("读取完毕");
}
}
//异步写入
private void btnBeginWrite_Click(object sender, EventArgs e)
{
//使用保存文件对话框获得要保存的文本文件路径
string filename;
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() != DialogResult.OK)
return;
filename = sfd.FileName;
//初始化一个StreamReader类的一个新实例
FileStream fsWrite = new FileStream(filename, FileMode.CreateNew, FileAccess.Write);
//将字符串转入字节数组
Byte[] buffer = Encoding.Default.GetBytes(textBox1.Text);
//调用BeginWrite进行异步写入,其中回调函数为AsyncWriteCallBack
fsWrite.BeginWrite(buffer, 0, buffer.Length, AsyncWriteCallBack, fsWrite);
}
//异步写操作完成后调用的方法
private void AsyncWriteCallBack(IAsyncResult aResult )
{
FileStream fs;
fs = (FileStream)aResult.AsyncState;
//结束异步写入
fs.EndWrite(aResult);
fs.Close();
}
//异步读取文件
private void btnReadAsync_Click(object sender, EventArgs e)
{
//使用打开文件对话框获得文本文件路径
string filename;
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
return;
filename = ofd.FileName;
//用文件名、编码初始化一个StreamReader类的一个新实例
StreamReader sr = new StreamReader(filename, Encoding.Default);
Char[] buffer;
//定义缓冲区大小
buffer = new char[sr.BaseStream.Length];
//声明一个可以返回值的异步操作
Task <int> mytask;
//从当前流中异步读取指定的最大字符,并且从指定的索引位置开始将该数据写入缓冲区。
mytask = sr.ReadAsync(buffer, 0, (int)sr.BaseStream.Length);
//等待mytask完成执行
mytask.Wait();
//关闭并释放资源
sr.Close();
textBox1.Text = new String(buffer);
}
//异步写入文件
private void btnWriteAsync_Click(object sender, EventArgs e)
{
//使用保存文件对话框获得要保存的文本文件路径
string filename;
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() != DialogResult.OK)
return;
filename = sfd.FileName;
string textContent = textBox1.Text;
//用文件名初始化StreamWriter类的一个新实例
StreamWriter sw = new StreamWriter(filename, false, Encoding.Default);
//声明一个可以返回值的异步操作
Task mytask;
//使用WriteAsync异步写入字符串
mytask = sw.WriteAsync(textContent);
//等待mytask完成执行
mytask.Wait();
//关闭并释放资源
sw.Close();
}
//按行异步读取
private void btnReadLineAsync_Click(object sender, EventArgs e)
{
//使用打开文件对话框获得文本文件路径
string filename;
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
return;
filename = ofd.FileName;
textBox1.Text = "";
//用文件名、编码初始化一个StreamReader类的一个新实例
StreamReader sr = new StreamReader(filename, Encoding.Default);
Char[] buffer;
//定义缓冲区大小
buffer = new char[sr.BaseStream.Length];
//声明一个可以返回值的异步操作
Task<string> mytask;
//一直循环,直到返回的Result为Nothing
while(true)
{
//使用ReadLineAsync异步读取一行字符串
mytask = sr.ReadLineAsync();
//等待mytask完成执行
mytask.Wait();
if (mytask.Result == null)
break;
//读取的数据存在mytask.Result
textBox1.Text += mytask.Result + "\r\n";
}
//关闭并释放资源
sr.Close();
}
//按行异步写入
private void btnWriteLineAsync_Click(object sender, EventArgs e)
{
//使用保存文件对话框获得要保存的文本文件路径
string filename;
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "文本文件|*.txt";
if (sfd.ShowDialog() != DialogResult.OK)
return;
filename = sfd.FileName;
string textContent= textBox1.Text;
//用文件名初始化StreamWriter类的一个新实例
StreamWriter sw = new StreamWriter(filename, false, Encoding.Default);
string[] splitStrings = { "\r\n" };
//将文本分割成多行字符串
string[] arrTextContent = textContent.Split(splitStrings, StringSplitOptions.None);
//声明一个可以返回值的异步操作
Task mytask;
//写入的时候排除掉字符串数组最后一个空行
for(int i = 0;i<= arrTextContent.Length - 1;i++)
{
//使用WriteLineAsync异步写入一行字符串
mytask = sw.WriteLineAsync(arrTextContent[i]);
//等待mytask完成执行
mytask.Wait();
}
//关闭并释放资源
sw.Close();
}
注意:在读写文件时也可以考虑使用多线程方式(请参看第15.3节),防止程序在读写数据时卡死。
10.6.1.4 文本文件的追加
文本文件的追加指的是将新的字符串文本增加到指定文本文件的末尾。
【例 10.20】【项目:code10-020】使用File类向文件追加文本。
使用File类的AppendAllText方法将指定的字符串追加到文件中,如果文件不存在则创建新文件。使用到的AppendAllText方法重载版本:
public static void AppendAllText (string path, string contents, System.Text.Encoding encoding);
参数说明:
- path:文件路径;
- contents:要添加的文本内容字符串;
- encoding:字符编码。
具体代码如下:
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "文本文件|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
return;
label1.Text = ofd.FileName;
}
private void button2_Click(object sender, EventArgs e)
{
//需要添加文本的文件
string filename = label1.Text;
//添加TextBox1中的文本
string textToAppend = textBox1.Text;
//使用File类的AppendAllText方法将文本追加到文件末尾
File.AppendAllText(filename, textToAppend, Encoding.Default );
}
【例 10.21】【项目:code10-021】使用StreamWriter类向文件追加文本。
使用File.AppendText返回一个StreamWriter的实例,然后使用将UTF-8编码文本追加到现有文件或新文件(如果指定文件不存在)。
private void button2_Click(object sender, EventArgs e)
{
//需要添加文本的文件
string filename = label1.Text;
//添加TextBox1中的文本
string textToAppend = textBox1.Text;
//使用File类的AppendText方法返回StreamWriter的实例
StreamWriter sw = File.AppendText(filename);
//写入文本
sw.Write(textToAppend);
//关闭
sw.Close();
}
注意:此方法不能设置文本编码,只能将UTF-8编码文本追加到现有文件或新文件
【例 10.22】【项目:code10-022】使用StreamWriter类向文件追加文本。
使用StreamWriter的构造函数的重载版本:
public StreamWriter(string path, bool append, Encoding encoding, int bufferSize);
参数说明:
- path:文件路径;
- append:指示是否是追加数据。True为追加数据;False为覆盖该文件。如果文件不存在则创建新文件。
- encoding:字符编码。
- bufferSize:缓冲区大小。
具体代码如下:
private void button2_Click(object sender, EventArgs e)
{
string filename = label1.Text;
string textToAppend = textBox1.Text;
//使用StreamWriter构造函数,第二个参数设置为True表示添加数据
StreamWriter sw=new StreamWriter(filename, true, Encoding.UTF8, 1024);
//写入数据
sw.WriteLine(textToAppend);
sw.Close();
}
学习更多vb.net知识,请参看vb.net 教程 目录
学习更多C#知识,请参看C#教程 目录