读写二进制文件(转载:来自http://www.tzwhx.com)
读写二进制文件通常要使用FileStream类。
1. FileStream类
FileStream实例用于读写文件中的数据。要构造FileStream实例,需要以下4条信息:
● 要访问的文件。
● 表示如何打开文件的模式。例如,创建一个新文件或打开一个现有的文件。如果打开一个现有的文件,写入操作是应改写文件原来的内容,还是添加到文件的末尾?
● 表示如何访问文件的访问方式—— 是只读、只写,还是读写?
● 共享访问—— 换言之,是独占访问文件,还是允许其他流同时访问文件?如果允许其他流同时访问文件,则这些流是只读、只写,还是读写文件?
第一条信息通常用一个包含文件完整路径名的字符串来表示,本章只考虑需要该字符串的构造函数。除了这些构造函数外,其他的构造函数用老式的Windows-API文件句柄来处理文件。其余3条信息分别由3个.NET枚举FileMode、FileAccess和 FileShare来表示,这些枚举的值应是自我解释性的,如表30-3所示。
枚 举 |
值 |
FileMode |
Append、Create、CreateNew、Open、OpenOrCreate和Truncate |
FileAccess |
Read、ReadWrite和Write |
FileShare |
Inheritable、None、Read、ReadWrite和Write |
注意,对于FileMode,如果要求的模式与文件的现有状态不一致,就会抛出一个异常。如果文件已经不存在,Append、Open和Truncate会抛出一个异常,如果文件存在,CreateNew会抛出一个异常。Create和OpenOrCreate可以处理这两种情况,但Create会删除现有的文件,创建一个新的空文件。FileAccess 和FileShare枚举是按位标志,所以这些值可以与C#的按位OR运算符|合并使用。
FileStream有许多构造函数,其中3个最简单的构造函数如下所示。
// creates file with read-write access and allows other streams read access
FileStream fs = new FileStream(@"C:"C# Projects"Project.doc",
FileMode.Create);
// as above, but we only get write access to the file
FileStream fs2 = new FileStream(@"C:"C# Projects"Project2.doc",
FileMode.Create, FileAccess.Write);
// as above but other streams don~t get any access to the file while
// fs3 is open
FileStream fs3 = new FileStream(@"C:"C# Projects"Project3.doc",
FileMode.Create, FileAccess.Write, FileShare.None);
从这段代码中可以看出,构造函数的这些重载方法会把FileAccess.ReadWrite 和 FileShare.Read的默认值作为第3、4个参数,也可以以多种方式从FileInfo实例中创建一个文件流:
FileInfo myFile4 = new FileInfo(@"C:"C# Projects"Project4.doc");
FileStream fs4 = myFile4.OpenRead();
FileInfo myFile5= new FileInfo(@"C:"C# Projects"Project5doc");
FileStream fs5 = myFile5.OpenWrite();
FileInfo myFile6= new FileInfo(@"C:"C# Projects"Project6doc");
FileStream fs6 = myFile6.Open(FileMode.Append, FileAccess.Write,
FileShare.None);
FileInfo myFile7 = new FileInfo(@"C:"C# Projects"Project7.doc");
FileStream fs7 = myFile7.Create();
FileInfo.OpenRead()提供的流只能读取现有的文件,而FileInfo ().OpenWrite()可以进行读写访问。FileInfo.Open()允许显式指定模式、访问方式和文件共享参数。
使用完一个流后,就应关闭它:
fs.Close();
关闭流会释放与它相关的资源,允许其他应用程序为同一个文件设置流。在打开和关闭流之间,可以读写其中的数据。FileStream有许多方法可以进行这样的读写。
ReadByte()是读取数据的最简单的方式,它从流中读取一个字节,把结果转换为一个0~255之间的一个整数。如果到达该流的末尾,就返回–1:
int NextByte = fs.ReadByte();
如果喜欢一次读取多个字节,可以调用Read()方法,它可以把特定数量的字节读入到一个数组中。Read()方法返回实际读取的字节数—— 如果这个值是0,就表示到达了流的尾端。下面是读入Byte数组ByteArray的一个示例:
int nBytesRead = fs.Read(ByteArray, 0, nBytes);
Read()的第二个参数是一个偏移值,使用它可以要求Read读取的数据从数组的某个元素开始填充,而不是从第一个元素开始。第三个参数是要读入数字的字节数。
如果要给文件写入数据,可以使用两个并行方法WriteByte() 和 Write()。WriteByte()方法把一个字节写入流:
fs.WriteByte(NextByte);
另外,Write()则写入一个字节数组。例如,如果用一些值初始化前面的ByteArray,就可以使用下面的代码写入数组的前nBytes 个字节:
fs.Write(ByteArray, 0, nBytes);
与Read()一样,第二个参数可以从数组的某个元素开始写入,而不是从第一个元素开始。WriteByte() 和 Write()都没有返回值。
除了这些方法以外,FileStream还有其他方法和属性可以处理簿记任务,例如确定流中有多少字节,锁定流或刷新缓冲区等。其他方法通常不是基本读写数据所必须的,如果您需要使用它们,可以参阅SDK文档说明书。
2. BinaryFileReader示例
下面编写一个示例BinaryFileReader说明FileStream类的用法。这个示例可以读取和显示任何文件。在Visual Studio .NET中创建一个Windows应用程序,添加一个菜单项,它可以打开一个标准的OpenFileDialog,要求用户指定要读取的文件,然后把该文件显示为二进制码。在读取二进制文件时,需要显示非打印(non-printable)字符。此时可以在多行文本框中逐个显示文件中的每个字节,每行显示16个字节。如果字节表示一个可显示的ASCII字符,就显示该字符,否则就以16进制的格式显示该字节的值。在这两种情况下,显示的文本之间都用空格隔开,这样每个显示的字节都占用4列,显得它们的排列非常整齐。
图30-8是BinaryFileReader应用程序查看文本文件时所显示的情况(因为BinaryFileReader可以查看任何文件,所以可以在文本文件和二进制文件中使用它)。本示例读取一个基本的ASP.NET页面(.aspx)。
图 30-8
显然,这个格式更适合于查看单个字节的值,而不是显示文本。本章的后面会开发一个专门用于读取文本文件的示例—— 然后就可以查看文件中的内容了。另一方面这个示例的优点是可以查看任何文件的内容。
这个示例没有说明如何写入文件。这是因为我们不希望把文本框中的内容转换为二进制流,增加程序的复杂性。在后面开发可读写文本文件的示例中,将介绍如何写入文件。
下面看看获得这些结果的代码。首先,需要一个额外的using语句,因为除了System.IO之外,这个示例还要使用System.Text 命名空间中的StringBuilder类,以构造文本框中的字符串:
using System.IO;
using System.Text;
接着,给主窗体类添加两个字段,一个字段表示文件对话框,另一个是表示当前文件的路径的字符串:
public class Form1 : System.Windows.Forms.Form
{
private OpenFileDialog chooseOpenFileDialog = new OpenFileDialog();
private string chosenFile;
还需要添加一些标准的Windows窗体代码,来处理菜单和文件对话框的处理程序:
public Form1()
{
InitializeComponent();
menuFileOpen.Click += new EventHandler(OnFileOpen);
chooseOpenFileDialog.FileOk += new
CancelEventHandler(OnOpenFileDialogOK);
}
void OnFileOpen(object Sender, EventArgs e)
{
chooseOpenFileDialog.ShowDialog();
}
void OnOpenFileDialogOK(object Sender, CancelEventArgs e)
{
chosenFile = chooseOpenFileDialog.FileName;
this.Text = Path.GetFileName(chosenFile);
DisplayFile();
}
可以看出,用户单击OK,在文件对话框中选择一个文件后,就会调用方法DisplayFile(),该方法读取所选中的文件:
void DisplayFile()
{
int nCols = 16;
FileStream inStream = new FileStream(chosenFile, FileMode.Open,
FileAccess.Read);
long nBytesToRead = inStream.Length;
if (nBytesToRead > 65536/4)
nBytesToRead = 65536/4;
int nLines = (int)(nBytesToRead/nCols) + 1;
string [] lines = new string[nLines];
int nBytesRead = 0;
for (int i=0 ; i
{
StringBuilder nextLine = new StringBuilder();
nextLine.Capacity = 4*nCols;
for (int j = 0 ; j
{
int nextByte = inStream.ReadByte();
nBytesRead++;
if (nextByte < 0 || nBytesRead > 65536)
break;
char nextChar = (char)nextByte;
if (nextChar < 16)
nextLine.Append(" x0" + string.Format("{0,1:X}",
(int)nextChar));
else if
(char.IsLetterOrDigit(nextChar) ||
char.IsPunctuation(nextChar))
nextLine.Append(" " + nextChar + " ");
else
nextLine.Append(" x" + string.Format("{0,2:X}",
(int)nextChar));
}
lines[i] = nextLine.ToString();
}
inStream.Close();
this.textBoxContents.Lines = lines;
}
在这个方法中进行了许多工作,首先为所选文件实例化一个FileStream,指定要打开一个现有文件进行读取。然后确定要读取多少个字节,和显示多少行。这个字节数一般是文件中的字节数。文本框最多只能显示65 536个字符,但以我们选择的格式,文件中的每个字节可以显示4个字符,因此如果文件的字节数大于65 536/4 = 16 384,就需要覆盖一些已显示的字节。
注意:
如果要在这种环境下显示较长的文件,就需要使用System.Windows.Forms命名空间中的 RichTextBox类。RichTextBox类似于文本框,但它有更高级的格式化功能,在显示的文本大小方面没有限制,此处使用TextBox,是为了让示例比较简单,用户可以只考虑读取文件的过程。
在该方法中,有相当多的部分是两个嵌套的循环,它们构造出每个要显示的文本行。我们使用StringBuilder类来构造每一文本行,其性能方面的原因是:可以把每个字节的合适文本追加到表示每行文本的字符串上。如果一行上有一个新字符串,并复制构造该行时的一半字符,则不仅要花很多时间分配字符串,还会浪费堆上的许多内存。注意,可显示的字符是指字母、字或标点符号,这与相关的静态方法System.Char指定的一样。值小于16的字符都不是可显示的字符,因此回车符(13)和换行符(10)都是二进制字符(如果这些字符单独占一行,多行文本框不能正确显示它们)。
另外要注意的两点是:使用属性窗口,把文本框的字体改为固定宽度的字体——我们选择Courier New 9pt常规字体,并把文本框设置为有水平和垂直滚动条。
最后,选择流,把文本框的内容设置为已建立的字符串数组。