理论上,可以使用FileStream类读取和显示文本文件。前面已经介绍了这个类。上面显示NewFile.txt文件的格式不太容易理解,但这并不是FileStream类的问题——而在于我们在文本框中显示结果所使用的方式。
如果知道某个文件包含文本,通常就可以使用StreamReader 和 StreamWriter类更方便地读写它们。这是因为这些类工作的级别比较高,特别适合于读写文本。它们执行的方法可以根据流的内容,自动检测出停止读取文本较方便的位置,特别是:
●这些类执行的方法可以一次读写一行文本(StreamReader.ReadLine() 和 StreamWriter.WriteLine())。在读取文件时,流会自动确定下一个回车符的位置,并在该处停止读取。在写入文件时,流会自动把回车符和换行符添加到文本的末尾。
●使用StreamReader 和 StreamWriter类,就不需要担心文件中使用的编码方式(文本格式)了。可能的编码方式是ASCII(一个字节表示一个字符)或者基于Unicode的格式,UNICODE、UTF7和 UTF8.Windows 9x系统上的文本文件总是ASCII格式,因为Windows 9x系统不支持Unicode,但Windows NT、2000、XP和2003都支持Unicode,所以文本文件除了包含ASCII数据之外,理论上可以包含Unicode、UTF7或 UTF8数据。其约定是:如果文件是ASCII格式,就只包含文本。如果是Unicode格式,就用文件的前两个或三个字节来表示,这几个字节可以设置为表示文件中格式的值的特定组合。
这些字节称为字节码标记。在使用标准Windows应用程序打开一个文件时,例如Notepad 或 WordPad,不需要考虑这个问题,因为这些应用程序都支持不同的编码方法,会自动正确地读取文件。StreamReader类也是这样,它可以正确读取任何格式的文件,而StreamWriter类可以使用任何一种编码技术格式化它要写入的文本。另一方面,如果要使用FileStream类读取和显示文本文件,就不必自己处理这个过程了。
1. StreamReader 类
StreamReader 用于读取文本文件。用某些方式构造一个StreamReader 要比构造一个FileStream 实例更简单,因为使用StreamReader 时不需要FileStream 的一些选项。特别是不需要模式和访问类型,因为StreamReader 只能执行读取操作。除此以外,没有指定共享许可的直接选项,但StreamReader 有两个新选项:
● 需要指定不同的编码方法所执行的不同操作。可以构造一个StreamReader 检查文件开头的字节码标记,确定编码方法,或者告诉StreamReader 该文件使用某个编码方法。
● 不提供要读取的文件名,而为另一个流提供引用。
最后一个选项需要解释一下,因为它涉及到把读写数据的模型建立在流概念上的另一个优点。StreamReader 工作在相对比较高的级别上,如果有另一个流在读取其他源的数据,就要使用由StreamReader 提供的工具来处理这个流,因为这个流包含文本,此时StreamReader 就非常有用了。可以把这个流的输出传送到StreamReader 上,这样,StreamReader 就可以读取和处理任何数据源( 不仅仅是文件) 中的数据了。前面在讨论BinaryReader 类时也讨论了这种情况。但在本书中,只使用StreamReader 来直接连接文件。
其结果是StreamReader 有非常多的构造函数。而且,还有两个返回StreamReader 引用的FileInfo 方法:OpenText() 和 CreateText() 。下面仅说明其中一些构造函数。
最简单的构造函数只带一个文件名参数。StreamReader 会检查字节码标记,确定编码 方法:
StreamReader sr = new StreamReader(@"C:"My Documents"ReadMe.txt");
另外,如果指定UTF8 编码方法:
StreamReader sr = new StreamReader(@"C:"My Documents"ReadMe.txt",
Encoding.UTF8);
使用类System.Text.Encoding 上的几个属性之一 ,就可以指定编码方法。
●使用StreamReader 和 StreamWriter类,就不需要担心文件中使用的编码方式(文本格式)了。可能的编码方式是ASCII(一个字节表示一个字符)或者基于Unicode的格式,UNICODE、UTF7和 UTF8.Windows 9x系统上的文本文件总是ASCII格式,因为Windows 9x系统不支持Unicode,但Windows NT、2000、XP和2003都支持Unicode,所以文本文件除了包含ASCII数据之外,理论上可以包含Unicode、UTF7或 UTF8数据。其约定是:如果文件是ASCII格式,就只包含文本。如果是Unicode格式,就用文件的前两个或三个字节来表示,这几个字节可以设置为表示文件中格式的值的特定组合。
这些字节称为字节码标记。在使用标准Windows应用程序打开一个文件时,例如Notepad 或 WordPad,不需要考虑这个问题,因为这些应用程序都支持不同的编码方法,会自动正确地读取文件。StreamReader类也是这样,它可以正确读取任何格式的文件,而StreamWriter类可以使用任何一种编码技术格式化它要写入的文本。另一方面,如果要使用FileStream类读取和显示文本文件,就不必自己处理这个过程了。
1. StreamReader 类
StreamReader 用于读取文本文件。用某些方式构造一个StreamReader 要比构造一个FileStream 实例更简单,因为使用StreamReader 时不需要FileStream 的一些选项。特别是不需要模式和访问类型,因为StreamReader 只能执行读取操作。除此以外,没有指定共享许可的直接选项,但StreamReader 有两个新选项:
● 需要指定不同的编码方法所执行的不同操作。可以构造一个StreamReader 检查文件开头的字节码标记,确定编码方法,或者告诉StreamReader 该文件使用某个编码方法。
● 不提供要读取的文件名,而为另一个流提供引用。
最后一个选项需要解释一下,因为它涉及到把读写数据的模型建立在流概念上的另一个优点。StreamReader 工作在相对比较高的级别上,如果有另一个流在读取其他源的数据,就要使用由StreamReader 提供的工具来处理这个流,因为这个流包含文本,此时StreamReader 就非常有用了。可以把这个流的输出传送到StreamReader 上,这样,StreamReader 就可以读取和处理任何数据源( 不仅仅是文件) 中的数据了。前面在讨论BinaryReader 类时也讨论了这种情况。但在本书中,只使用StreamReader 来直接连接文件。
其结果是StreamReader 有非常多的构造函数。而且,还有两个返回StreamReader 引用的FileInfo 方法:OpenText() 和 CreateText() 。下面仅说明其中一些构造函数。
最简单的构造函数只带一个文件名参数。StreamReader 会检查字节码标记,确定编码 方法:
StreamReader sr = new StreamReader(@"C:"My Documents"ReadMe.txt");
另外,如果指定UTF8 编码方法:
StreamReader sr = new StreamReader(@"C:"My Documents"ReadMe.txt",
Encoding.UTF8);
使用类System.Text.Encoding 上的几个属性之一 ,就可以指定编码方法。
这个类是一个抽象基类,可以根据这个类定义许多类,其方法可执行实际的文本编码。每个属性都返回相应类的一个实例,可以使用的属性包括:
●ASCII
●Unicode
●UTF7
●UTF8
●BigEndianUnicode
下面的示例解释了如何把StreamReader 关联到FileStream 上。其优点是可以显式指定是否创建文件和共享许可,如果直接把StreamReader 关联到文件上,就不能这么做:
FileStream fs = new FileStream(@"C:"My Documents"ReadMe.txt",
FileMode.Open, FileAccess.Read, FileShare.None);
StreamReader sr = new StreamReader(fs);
对于本例,指定StreamReader 查找字节码标记,以确定使用了什么编码方法,以后的示例也是这样,从一个FileInfo 实例中获得StreamReader :
FileInfo myFile = new FileInfo(@"C:"My Documents"ReadMe.txt");
StreamReader sr = myFile.OpenText();
与FileStream 一样,应在使用后关闭StreamReader 。如果没有这样做,就会致使文件一直锁定,因此不能执行其他过程( 除非使用FileStream 构造StreamReader 和特定的FileShare. ShareReadWrite) :
sr.Close();
介绍完实例化StreamReader 后,就可以用该实例作一些工作了。与FileStream 一样,我们仅指出可以用于读取数据的许多方式,您应在SDK 文档说明书中查阅其他不太常用的StreamReader 方法。
所使用的最简单的方式是ReadLine() ,该方法一次读取一行,但返回的字符串中不包括标记该行结束的回车换行符:
string nextLine = sr.ReadLine();
另外,还可以在一个字符串中提取文件的所有剩余内容( 严格地说,是流的全部剩余内容) :
string restOfStream = sr.ReadToEnd();
可以只读取一个字符:
int nextChar = sr.Read();
Read() 的重载方法可以把返回的字符转换为一个整数,如果到达流的尾端,就返回-1 。
最后,可以用一个偏移值,把给定个数的字符读到数组中:
// to read 100 characters in.
int nChars = 100;
char [] charArray = new char[nChars];
int nCharsRead = sr.Read(charArray, 0, nChars);
如果要求读取的字符数多于文件中剩余的字符数,nCharsRead 应小于nChars 。
2. StreamWriter 类
StreamWriter 类的工作方式与StreamReader 的类似,但StreamWriter 只能用于写入文件( 或另一个流) 。构造StreamWriter 的方法包括:
StreamWriter sw = new StreamWriter(@"C:"My Documents"ReadMe.txt");
上面的代码使用了UTF8 编码方法,.NET 把这种编码方法设置为默认的编码方法。如果要指定其他的编码方法:
StreamWriter sw = new StreamWriter(@"C:"My Documents"ReadMe.txt", true,
Encoding.ASCII);
在这个构造函数中,第二个参数是Boolean 型,表示文件是否应以追加方式打开。构造函数的参数不能仅是一个文件名和一个编码类。
当然,可以把StreamWriter 关联到一个文件流上,以获得打开文件的更多控制选项:
FileStream fs = new FileStream(@"C:"My Documents"ReadMe.txt",
FileMode.CreateNew, FileAccess.Write, FileShare.Read);
StreamWriter sw = new StreamWriter(fs);
FileInfo 不执行返回StreamWriter 的任何方法。
另外,如果要创建一个新文件,并开始给它写入数据,可以使用下面的代码:
FileInfo myFile = new FileInfo(@"C:"My Documents"NewFile.txt");
StreamWriter sw = myFile.CreateText();
与其他流类一样,在使用完后,要关闭StreamWriter :
sw.Close();
写入流可以使用StreamWriter.Write() 的4 个重载方法来完成。最简单的方式是写入一个流,后面加上一个回车换行符:
string nextLine = "Groovy Line";
sw.Write(nextLine);
也可以写入一个字符:
char nextChar = ~a~;
sw.Write(nextChar);
也可以写入一个字符数组:
char [] charArray = new char[100];
// initialize these characters
sw.Write(charArray);
甚至可以写入字符数组的一部分:
int nCharsToWrite = 50;
int startAtLocation = 25;
char [] charArray = new char[100];
// initialize these characters
sw.Write(charArray, startAtLocation, nCharsToWrite);
3.ReadWriteText 示例
ReadWriteText 示例说明了StreamReader 和StreamWriter 类的用法。它非常类似于前面的ReadBinaryFile 示例,但假定要读取的文件是一个文本文件,并显示其内容。它还可以保存文件( 包括在文本框中对文本进行的修改) 。它将以Unicode 格式保存文件。
图30-9 所示的ReadWriteText 用于显示前面的NewFile.aspx 文件。但这次读取内容会更容易一些。
这里不打算介绍给打开文件对话框添加事件处理程序的详细内容,因为它们基本上与前面的BinaryFileReader 示例相同。与这个示例相同,打开一个新文件,将调用DisplayFile() 方法。其惟一的区别是DisplayFile 的执行方式,本例有一个保存文件的选项。这由另一个菜单项save 来表示,这个选项的处理程序调用我们添加到代码中的另一个方法SaveFile()( 注意,这个新文件总是重写原来的文件—— 这个示例没有写入另一个文件的选项) 。
●ASCII
●Unicode
●UTF7
●UTF8
●BigEndianUnicode
下面的示例解释了如何把StreamReader 关联到FileStream 上。其优点是可以显式指定是否创建文件和共享许可,如果直接把StreamReader 关联到文件上,就不能这么做:
FileStream fs = new FileStream(@"C:"My Documents"ReadMe.txt",
FileMode.Open, FileAccess.Read, FileShare.None);
StreamReader sr = new StreamReader(fs);
对于本例,指定StreamReader 查找字节码标记,以确定使用了什么编码方法,以后的示例也是这样,从一个FileInfo 实例中获得StreamReader :
FileInfo myFile = new FileInfo(@"C:"My Documents"ReadMe.txt");
StreamReader sr = myFile.OpenText();
与FileStream 一样,应在使用后关闭StreamReader 。如果没有这样做,就会致使文件一直锁定,因此不能执行其他过程( 除非使用FileStream 构造StreamReader 和特定的FileShare. ShareReadWrite) :
sr.Close();
介绍完实例化StreamReader 后,就可以用该实例作一些工作了。与FileStream 一样,我们仅指出可以用于读取数据的许多方式,您应在SDK 文档说明书中查阅其他不太常用的StreamReader 方法。
所使用的最简单的方式是ReadLine() ,该方法一次读取一行,但返回的字符串中不包括标记该行结束的回车换行符:
string nextLine = sr.ReadLine();
另外,还可以在一个字符串中提取文件的所有剩余内容( 严格地说,是流的全部剩余内容) :
string restOfStream = sr.ReadToEnd();
可以只读取一个字符:
int nextChar = sr.Read();
Read() 的重载方法可以把返回的字符转换为一个整数,如果到达流的尾端,就返回-1 。
最后,可以用一个偏移值,把给定个数的字符读到数组中:
// to read 100 characters in.
int nChars = 100;
char [] charArray = new char[nChars];
int nCharsRead = sr.Read(charArray, 0, nChars);
如果要求读取的字符数多于文件中剩余的字符数,nCharsRead 应小于nChars 。
2. StreamWriter 类
StreamWriter 类的工作方式与StreamReader 的类似,但StreamWriter 只能用于写入文件( 或另一个流) 。构造StreamWriter 的方法包括:
StreamWriter sw = new StreamWriter(@"C:"My Documents"ReadMe.txt");
上面的代码使用了UTF8 编码方法,.NET 把这种编码方法设置为默认的编码方法。如果要指定其他的编码方法:
StreamWriter sw = new StreamWriter(@"C:"My Documents"ReadMe.txt", true,
Encoding.ASCII);
在这个构造函数中,第二个参数是Boolean 型,表示文件是否应以追加方式打开。构造函数的参数不能仅是一个文件名和一个编码类。
当然,可以把StreamWriter 关联到一个文件流上,以获得打开文件的更多控制选项:
FileStream fs = new FileStream(@"C:"My Documents"ReadMe.txt",
FileMode.CreateNew, FileAccess.Write, FileShare.Read);
StreamWriter sw = new StreamWriter(fs);
FileInfo 不执行返回StreamWriter 的任何方法。
另外,如果要创建一个新文件,并开始给它写入数据,可以使用下面的代码:
FileInfo myFile = new FileInfo(@"C:"My Documents"NewFile.txt");
StreamWriter sw = myFile.CreateText();
与其他流类一样,在使用完后,要关闭StreamWriter :
sw.Close();
写入流可以使用StreamWriter.Write() 的4 个重载方法来完成。最简单的方式是写入一个流,后面加上一个回车换行符:
string nextLine = "Groovy Line";
sw.Write(nextLine);
也可以写入一个字符:
char nextChar = ~a~;
sw.Write(nextChar);
也可以写入一个字符数组:
char [] charArray = new char[100];
// initialize these characters
sw.Write(charArray);
甚至可以写入字符数组的一部分:
int nCharsToWrite = 50;
int startAtLocation = 25;
char [] charArray = new char[100];
// initialize these characters
sw.Write(charArray, startAtLocation, nCharsToWrite);
3.ReadWriteText 示例
ReadWriteText 示例说明了StreamReader 和StreamWriter 类的用法。它非常类似于前面的ReadBinaryFile 示例,但假定要读取的文件是一个文本文件,并显示其内容。它还可以保存文件( 包括在文本框中对文本进行的修改) 。它将以Unicode 格式保存文件。
图30-9 所示的ReadWriteText 用于显示前面的NewFile.aspx 文件。但这次读取内容会更容易一些。
这里不打算介绍给打开文件对话框添加事件处理程序的详细内容,因为它们基本上与前面的BinaryFileReader 示例相同。与这个示例相同,打开一个新文件,将调用DisplayFile() 方法。其惟一的区别是DisplayFile 的执行方式,本例有一个保存文件的选项。这由另一个菜单项save 来表示,这个选项的处理程序调用我们添加到代码中的另一个方法SaveFile()( 注意,这个新文件总是重写原来的文件—— 这个示例没有写入另一个文件的选项) 。
图 30-9
首先看看SaveFile()
,因为它是最简单的一个函数。首先利用StreamReader.
WriteLine()
方法把文本框中的每行文本依次写入StreamWriter
流,并在每行文本的最后加上回车换行符:
void SaveFile()
{
StreamWriter sw = new StreamWriter(chosenFile, false,
Encoding.Unicode);
foreach (string line in textBoxContents.Lines)
sw.WriteLine(line);
sw.Close();
}
chosenFile 是主窗体的一个字符串字段,它包含已经读取的文件的名称( 与前面的示例一样) 。注意在打开流时指定Unicode 编码方式。如果要以其他格式写入文件,则只需要改变该参数的值。如果要把文本追加到文件中,这个构造函数的第二个参数就设置为true ,但本例不是这样。在构造时必须为StreamWriter 设置编码方式,可以使用只读属性Encoding 。
下面介绍文件的读取方式。读取过程比较复杂,因为我们不知道要读取的文件中包含多少行文本( 换言之,文件中包含多少个(char)13 – (char)10 序列,因为char(13) –char(10) 是行末的回车换行符) 。解决这个问题的方式是,先把文件读入一个StringCollection 类的实例,该类在System.Collections.Specialized 命名空间中,主要用于保存可动态扩展的一组字符串。它的两个方法是我们感兴趣的:把字符串添加到集合中的Add() 和把字符串集合复制到一个数组( 一个System.Array 实例) 中的CopyTo() 。StringCollection 对象的每个元素包含文件中的一行文本。
DisplayFile() 方法调用另一个方法ReadFileIntoStringCollection() ,来读取文件。之后,就知道文件中有多少行文本了。把StringCollection 复制到大小固定的数组中,并把数组中的内容填充到文本框中。在进行复制时,只复制了字符串的引用,没有复制字符串本身,所以该过程的执行效率很高:
void DisplayFile()
{
StringCollection linesCollection = ReadFileIntoStringCollection();
string [] linesArray = new string[linesCollection.Count];
linesCollection.CopyTo(linesArray, 0);
this.textBoxContents.Lines = linesArray;
}
StringCollection.CopyTo() 的第二个参数表示目标数组中的下标,我们从该下标指定的位置开始复制集合。
下面看看ReadFileIntoStringCollection() 方法。使用StreamReader 读取每一行文本。编译时需要计算读取的字符数,以确保不超出文本框的范围:
StringCollection ReadFileIntoStringCollection()
{
const int MaxBytes = 65536;
StreamReader sr = new StreamReader(chosenFile);
StringCollection result = new StringCollection();
int nBytesRead = 0;
string nextLine;
while ( (nextLine = sr.ReadLine()) != null)
{
nBytesRead += nextLine.Length;
if (nBytesRead > MaxBytes)
break;
result.Add(nextLine);
}
sr.Close();
return result;
}
这就是该示例的完整代码。
如果运行ReadWriteText ,读取NewFile.aspx 文件,然后保存它,该文件的格式就是Unicode 。任何常用的Windows 应用程序(Notepand ,Wordpad) 都没有提供这种格式,甚至ReadWriteText 示例也只能在Windows NT/2000/XP/2003 下正确读取和显示文件。因为Windows 9x 不支持Unicode ,像Notepad 这样的应用程序不能识别其他平台上的Unicode 文件( 如果从Wrox Press 网站上下载了这个示例,就可以试试) 。但是,如果使用前面的ReadBinaryFile 示例显示文件,就会立即看出它们的区别,如图30-10 所示。最前面的两个字节表示文件的格式是Unicode ,之后,每个字符都用两个字节来表示。这是非常明显的,因为在这个文件中,每个字符的高位字节都是0 ,所以每隔一个字节就显示x00 。
void SaveFile()
{
StreamWriter sw = new StreamWriter(chosenFile, false,
Encoding.Unicode);
foreach (string line in textBoxContents.Lines)
sw.WriteLine(line);
sw.Close();
}
chosenFile 是主窗体的一个字符串字段,它包含已经读取的文件的名称( 与前面的示例一样) 。注意在打开流时指定Unicode 编码方式。如果要以其他格式写入文件,则只需要改变该参数的值。如果要把文本追加到文件中,这个构造函数的第二个参数就设置为true ,但本例不是这样。在构造时必须为StreamWriter 设置编码方式,可以使用只读属性Encoding 。
下面介绍文件的读取方式。读取过程比较复杂,因为我们不知道要读取的文件中包含多少行文本( 换言之,文件中包含多少个(char)13 – (char)10 序列,因为char(13) –char(10) 是行末的回车换行符) 。解决这个问题的方式是,先把文件读入一个StringCollection 类的实例,该类在System.Collections.Specialized 命名空间中,主要用于保存可动态扩展的一组字符串。它的两个方法是我们感兴趣的:把字符串添加到集合中的Add() 和把字符串集合复制到一个数组( 一个System.Array 实例) 中的CopyTo() 。StringCollection 对象的每个元素包含文件中的一行文本。
DisplayFile() 方法调用另一个方法ReadFileIntoStringCollection() ,来读取文件。之后,就知道文件中有多少行文本了。把StringCollection 复制到大小固定的数组中,并把数组中的内容填充到文本框中。在进行复制时,只复制了字符串的引用,没有复制字符串本身,所以该过程的执行效率很高:
void DisplayFile()
{
StringCollection linesCollection = ReadFileIntoStringCollection();
string [] linesArray = new string[linesCollection.Count];
linesCollection.CopyTo(linesArray, 0);
this.textBoxContents.Lines = linesArray;
}
StringCollection.CopyTo() 的第二个参数表示目标数组中的下标,我们从该下标指定的位置开始复制集合。
下面看看ReadFileIntoStringCollection() 方法。使用StreamReader 读取每一行文本。编译时需要计算读取的字符数,以确保不超出文本框的范围:
StringCollection ReadFileIntoStringCollection()
{
const int MaxBytes = 65536;
StreamReader sr = new StreamReader(chosenFile);
StringCollection result = new StringCollection();
int nBytesRead = 0;
string nextLine;
while ( (nextLine = sr.ReadLine()) != null)
{
nBytesRead += nextLine.Length;
if (nBytesRead > MaxBytes)
break;
result.Add(nextLine);
}
sr.Close();
return result;
}
这就是该示例的完整代码。
如果运行ReadWriteText ,读取NewFile.aspx 文件,然后保存它,该文件的格式就是Unicode 。任何常用的Windows 应用程序(Notepand ,Wordpad) 都没有提供这种格式,甚至ReadWriteText 示例也只能在Windows NT/2000/XP/2003 下正确读取和显示文件。因为Windows 9x 不支持Unicode ,像Notepad 这样的应用程序不能识别其他平台上的Unicode 文件( 如果从Wrox Press 网站上下载了这个示例,就可以试试) 。但是,如果使用前面的ReadBinaryFile 示例显示文件,就会立即看出它们的区别,如图30-10 所示。最前面的两个字节表示文件的格式是Unicode ,之后,每个字符都用两个字节来表示。这是非常明显的,因为在这个文件中,每个字符的高位字节都是0 ,所以每隔一个字节就显示x00 。
图 30-10