关于二进制和文本文件的读写操作

朱旻喆

2007-11-18

于北京信息工程学院

 

二进制文件的读写操作使用BinaryReader和BinaryWriter。文本文件的读写操作使用StreamReader和StreamWriter。

这里首先不得不提到的一个概念是“流”(Stream)。流是操作系统中的一种实体,他是系统与具体设备交换数据的一个介质,包换文件流,内存流等等,对设备进行随机访问,并且封装了具体设备的流特性,使得各种设备在操作系统中都以统一的流形式存在。流文件是相对于记录型文件来讲的。流文件的访问是用文件指针进行随机访问的,而记录型文件是使用记录指针进行记录式访问的。流都是二进制序列,.net中使用File类对流进行了封装,让程序员可以不了解流就可以进行文件操作。

对同一个文件,其实只有一种流,而不分为读和写流。但是大多数语言都使用了流的读和写工具(如.net使用StreamReader和StreamWriter,C++使用IStream和OStream),而不是使用统一的一个类(我们可以命名为StreamTool。实际上C++里是有IOStream类来统一对流进行管理的。)来对流进行读和写。这使我们会误认为,流有两种――用来读取文件和用来向文件中写数据。实际上,流在设备连接上之后就已经存在了。当我们声明一个文件流对象的时候,他总是绑定在一个文件上,你不可以使用FileStream filestream = new FileStream();来实例化一个流,而要使用如文件地址作为参数来实例化一个流。流一但存在,我们就可以对这个流进行读和写。我们定义流对象的过程实际上是对操作系统维护的流实体进行连接,而且这个流实体是临界资源,独占式访问的。所以当你使用Reader连接到一个已经用别的Reader或者Writer连接过的文件上的时候,运行无法正常进行。只有当你把一个Reader (或者Writer)Close的时候,另一个流对象才能使用这个文件。

另外,流的使用分几种模式--只读,覆盖式写入,创建新文件,读和写,向流末尾追加内容等等。使用流的时候一定要先指定,否则,各种语言会使用自己默认的模式来操作流。如果一不小心把原来的文件给重写了,可能会让读者哭笑不得的。

下面先讲关于文件写操作。

BinaryWriter是把输入的参数按照字节为单位,写到流里去。而StreamWriter是把输入的参数先得到他的相应编码,然后把编码写到流里去。编码是几个字节为单位的,写进去就是以几个字节为单位。

 

例如:

StreamWriter writer2 = File.AppendText("try.txt");

writer2.Write(2);

writer2.Flush();

writer2会先把参数2转换成“2”,他的编码是32。所以,我们用二进制编辑器打开try.txt,得到的是

00000000     32                                               2

 

 

BinaryWriter writer = new BinaryWriter(new FileStream("try.txt", FileMode.Append));

writer.Write(26);

writer.Close();

得到的是

00000000     1A 00 00 00                                   ....

 

 

BinaryWriter直接把十进制26的2进制码输出到流中。输出的字节单位,要看输入的参数类型而定,这里输入(26)是整型,4个字节。如输入参数为byte[],则输出字节数与byte数组长度相同

byte[] output = new byte[] { 0x00000001, 0x00000010 };

BinaryWriter writer = new BinaryWriter(new FileStream("try.txt", FileMode.Append));

writer.Write(output);

writer.Close();

得到的是

00000000     01 10                                           ..

 

 

注意,用StreamWriter和BinaryWriter的时候,系统会先缓存流,要用Flush()方法才能输出到文件

 

    文件的读操作和写操作的原理是一样的。需要注意的是使用编码如果和文件存储使用的编码方式不同,得到的也会是乱码,或者完全不同的正常文本(如果真的这么巧的话)。

 

 


 

附:UTF8编码原理

UTF8其实和Unicode是同类,就是在编码方式上不同!

首先UTF8编码后的大小是不一定,不像Unicode编码后的大小是一样的!

 

我们先来看Unicode的编码:一个英文字母 “a” 和 一个汉字 “好”,编码后都是占用的空间大小是一样的,都是两个字节!

 

而UTF8编码:一个英文字母“a” 和 一个汉字 “好”,编码后占用的空间大小就不样了,前者是一个字节,后者是三个字节!

 

现在就让我们来看看UTF8编码的原理吧:

因为一个字母还有一些键盘上的符号加起来只用二进制七位就可以表示出来,而一个字节就是八位,所以UTF8就用一个字节来表式字母和一些键盘上的符号。然而当我们拿到被编码后的一个字节后怎么知道它的组成?它有可能是英文字母的一个字节,也有可能是汉字的三个字节中的一个字节!所以,UTF8是有标志位的!

 

当要表示的内容是 7位 的时候就用一个字节:0******* 第一个0为标志位,剩下的空间正好可以表示ASCII 0-127 的内容。

 

当要表示的内容在 8 到 11 位的时候就用两个字节:110***** 10****** 第一个字节的110和第二个字节的10为标志位。

 

当要表示的内容在 12 到 16 位的时候就用三个字节:1110***** 10****** 10****** 和上面一样,第一个字节的1110和第二、三个字节的10都是标志位,剩下的占湔每梢员硎竞鹤帧?BR>

以此类推:

四个字节:11110**** 10****** 10****** 10******

五个字节:111110*** 10****** 10****** 10****** 10******

六个字节:1111110** 10****** 10****** 10****** 10****** 10******

 

windows自带的记事本(Notepad)的时候,会在文件头增加几个字节的标记位来区分所使用的编码,但是需要明确的是这几个新增加上的几个字节内容并不是编码的内容。文本编辑器都能识别这几个标记位,所以他们打开这样的文本文件的时候,不会出现乱码。但是,如果你使用二进制编辑器把标记位改成别的值,用记事本打开的时候就会变成乱码了。另外,即使你把标识删掉,记事本也是可以自动识别他的编码的。在vs2005中自带一个文本编辑器就不具有这样的功能,他只能通过标记位来识别编码种类。