读写文件(二进制文件、文本文件、ini文件)
1. 文件和流
文件(file)和流(stream)即有区别又有联系。文件是在各种媒质上(可移动磁盘、硬盘、CD 等)永久存储的数据的有序集合。它是一种进行数据读写操作的基本对象。通常情况下,文件按照树状目录进行组织,每个文件都有文件名、文件所在路径、创建时间、访问权限等属性。
1) 流是字节序列的抽象概念,如文件、输入输出设备、内部进程通信管道或者TCP/IP套接字等均可以看成流。流提供一种向后备存储器写入字节和从后备存储器读取字节的方式。
2) 除了和磁盘文件直接相关的文件流以外,还有多种其它类型的流,如分布在网络中、内存中和磁带中的流,分别称为网络流、内存流和磁带流。
3) 所有表示流的类都是从抽象基类Stream继承的。
流的操作有三类:
A. 读取:从流中读取数据到变量中。
B. 写入:把变量中的数据写入到流中。
C. 定位:重新设置流的当前位置,以便随机读写。
File类的静态方法主要是用于创建FileStream类。一个FileStream类的实例实际上代表一个磁盘文件,使用FileStream类对文件系统上的文件进行读取、写入、打开和关闭操作,并对其它与文件相关的操作系统句柄进行操作,如管道、标准输入和标准输出。读写操作可以指定为同步或异步操作。FileStream对输入输出进行缓冲,从而提高了系统的性能。
图1 与流相关类层次结构
对于文件的读写,最常用的类如下:
A. FileStream(文件流):这个类主要用于在二进制文件中读写二进制数据——也可以读写任何文件。
B. StreamReader(流读取器)和StreamWriter(流写入器):这两个类是专门用于读写文本文件的。
C. BinaryReader和BinaryWrite:这两个类本身不执行流,而是提供其他对象的包装器以及对二进制数据进行额外的格式化。
2. 文件的基本操作
2.1. 二进制文件
2.1.1. 打开
读写二进制数据通常使用FileStream类。要构造其实例,需要以下4条信息:
A. 要访问的文件。
B. 表示如何打开文件的模式——指定在文件不存在时是否创建该文件,并确定是保留还是改写现有文件的内容。
C. 表示访问文件的方式——对文件执行的操作(只读、只写或者读写)。
D. 共享访问——其它线程所具有的对该文件的访问类型(独占文件、共享读或者共享写)。
打开指定路径上的FileStream,可以使用File类的Open方法或OpenRead方法或OpenText方法。
其中Open打开文件的方式有三种,如下表所示:
名称 |
说明 |
File.Open (String, FileMode) |
打开指定路径上的 FileStream,具有读/写访问权限。 |
File.Open (String, FileMode, FileAccess) |
以指定的模式和访问权限打开指定路径上的 FileStream。 |
File.Open (String, FileMode, FileAccess, FileShare) |
打开指定路径上的 FileStream,具有指定的读、写或读/写访问模式以及指定的共享选项。 |
FileStream(String, FileMode, FileAccess) 等各种FileStream构造函数 |
FileStream构造函数与file.Open打开效果一致。 |
FileAccess默认值为FileAccess.ReadWrite,FileShare默认值为FileShare.Read。
文件操作方式说明(.Net2.0 SDK定义):
public enum FileMode // 指定操作系统打开文件的方式
{
// 指定操作系统应创建新文件。此操作需要FileIOPermissionAccess.Write权限如果文件已存在,
/ / 则将引发System.IO.IOException
CreateNew = 1,
// 指定操作系统应创建新文件。如果文件已存在,它将被改写。这要求FileIOPermissionAccess.Write权限。
// System.IO.FileMode.Create等效于这样的请求:如果文件不存在,则使用 FileMode.CreateNew;
// 否则使用FileMode.Truncate
Create = 2,
// 指定操作系统应打开现有文件。打开文件的能力取决于 System.IO.FileAccess 所指定的值。
// 如果该文件不存在,则引发 System.IO.FileNotFoundException
Open = 3,
// 指定操作系统应打开文件(如果文件存在);否则,应创建新文件。如果用 FileAccess.Read 打开文件,
// 则需要 System.Security.Permissions.FileIOPermissionAccess.Read。如果文件访问为FileAccess.Write 或
// FileAccess.ReadWrite,则需要 System.Security.Permissions.FileIOPermissionAccess.Write。如果文件访问为
// FileAccess.Append,则需要 System.Security.Permissions.FileIOPermissionAccess.Append
OpenOrCreate = 4,
// 指定操作系统应打开现有文件。文件一旦打开,就将被截断为零字节大小。此操作需要
// FileIOPermissionAccess.Write。试图从使用Truncate 打开的文件中进行读取将导致异常
Truncate = 5,
// 打开现有文件并查找到文件尾,或创建新文件。FileMode.Append 只能同 FileAccess.Write 一起使用。
// 任何读尝试都将失败并引发System.ArgumentException
Append = 6,
}
public enum FileAccess // 定义用于控制对文件的读访问、写访问或读/写访问的常数
{
// 对文件的读访问。可从文件中读取数据。同 Write 组合即构成读写访问权
Read = 1,
// 文件的写访问。可将数据写入文件。同 Read 组合即构成读/写访问权
Write = 2,
// 对文件的读访问和写访问。可从文件读取数据和将数据写入文件
ReadWrite = 3,
}
public enum FileShare // 包含用于控制其他 System.IO.FileStream 对象对同一文件可以具有的访问类型的常数
{
// 谢绝共享当前文件。文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败
None = 0,
// 允许随后打开文件读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程
// 或另一进程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件
Read = 1,
// 允许随后打开文件写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程
// 或另一进过程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件
Write = 2,
//允许随后打开文件读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请
//求(由此进程或另一进程发出)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件
ReadWrite = 3,
// 允许随后删除文件
Delete = 4,
// 使文件句柄可由子进程继承。Win32 不直接支持此功能
Inheritable = 16,
}
2.1.2. 移动
// 摘要: 将该流的当前位置设置为给定值。
// offset: 相对于 origin 的点,从此处开始查找。
// origin: 使用 System.IO.SeekOrigin 类型的值,将开始位置、结束位置或当前位置指定为 origin 的参考点。
// 返回结果: 流中的新位置。
public override long Seek(long offset, SeekOrigin origin);
public enum SeekOrigin // 提供表示流中的参考点以供进行查找的字段
{
Begin = 0, // 指定流的开头
Current = 1, // 指定流内的当前位置
End = 2, // 指定流的结尾
}
2.1.3. 读
// 摘要: 从文件中读取一个字节,并将读取位置提升一个字节。
// 返回结果: 转换为 int 的字节,或者如果从流的末尾读取则为 -1。
public override int ReadByte();
// 摘要: 从流中读取字节块并将该数据写入给定缓冲区中。
// offset: rray 中的字节偏移量,从此处开始读取。
// array: 当此方法返回时,包含指定的字节数组,数组中 offset 和 (offset + count - 1) 之间的值被从当前源中读取的 // 字节替count: 最多读取的字节数。
// 返回结果: 读入 buffer 中的总字节数。如果当前的字节数没有所请求那么多,则总字节数可能小于所请求的字节
// 数;或者如果已到达流的末尾,则为零。
public override int Read(byte[] array, int offset, int count);
2.1.4. 写
// 摘要: 将一个字节写入文件流的当前位置
// value: 要写入流的字节
public override void WriteByte(byte value);
// 使用从缓冲区读取的数据将字节块写入该流
// offset: array 中的从零开始的字节偏移量,从此处开始将字节复制到当前流
// array: 包含要写入该流的数据的缓冲区
// count: 要写入当前流的最大字节数
public override void Write(byte[] array, int offset, int count);
2.1.5. 关闭
// 释放由Stream使用的所有资源
public void Stream.Dispose ();
// 关闭当前流并释放与之关联的所有资源
public void Stream.Close();
// 释放由FileStream占用的非托管资源,还可以另外再释放托管资源
// disposing: 为true则释放托管资源和非托管资源;为fals 则仅释放非托管资源
protected override void FileStream.Dispose(bool disposing)
2.2. 文本文件
理论上,可以使用FileStream类读写显示文本文件。通常使用StreamReader和StreamWrite类更方便的读写文本,因为它们执行的方法可以根据流的内容,自动检测出停止读取文本较方便的位置。特别是:
A. 可以一次读写一行文本(StreamReader.ReadLine()和StreamWriter.WriteLine())。
B. 支持文本文件中使用的各种编码方式。
2.2.1. 读StreamReader
读文本文件一般使用StreamReader,构造实例可通过完整文件名、stream系列对象,可指定编码
格式。默认XX编码格式。
名称 |
说明 |
StreamReader(string path) |
为指定的文件名初始化 StreamReader 类的新实例。 |
StreamReader(Stream stream) |
为指定的流初始化 StreamReader 类的新实例 |
StreamReader(string path, Encoding encoding) |
用指定的字符编码,为指定的文件名初始化 StreamReader 类的一个新实例。 |
StreamReader(Stream stream, Encoding encoding) |
用指定的字符编码为指定的流初始化 StreamReader 类的一个新实例。 |
类Encoding包含静态属性:
ü ASCII
ü Unicode
ü UTF7
ü UTF8
ü UTF32
ü BigEndianUnicode
ü Default
a> 从一个FileInfo实例中获得StreamReader:
FileInfo info = new FileInfo(@”C:\ReadMe.txt”);
StreamReader sr = info.OpenText(); //创建使用 UTF8 编码、从现有文本文件中进行读取StreamReader
b> 从一个FileStream实例创建StreamReader:
FileStream fs = new FileStream(@“C:\ReadMe.txt”,FileMode.Open,FileAccess.Read, FileShare.None)
StreamReader sr = new StreamReader(fs);
// 摘要: 从当前流中读取一行字符并将数据作为字符串返回。
// 返回结果: 输入流中的下一行;如果到达了输入流的末尾,则为 null。
public override string ReadLine();
// 摘要: 从流的当前位置到末尾读取流。
// 返回结果: 字符串形式的流的其余部分(从当前位置到末尾)。如果当前位置位于流的末尾,则返回空字符串 ("")。
public override string ReadToEnd();
// 摘要: 读取输入流中的下一个字符并使该字符的位置提升一个字符。
// 返回结果: 输入流中表示为 System.Int32 对象的下一个字符。如果不再有可用的字符,则为 -1。
public override int Read();
// 摘要: 返回下一个可用的字符,但不使用它。
// 返回结果: 下一个要读取的字符,或者如果没有更多的可用字符或此流不支持查找,则为 -1。
public override int Peek();
// 摘要: 从 index 开始,从当前流中将最多的 count 个字符读入 buffer。
// count: 最多读取的字符数。
// buffer: 此方法返回时,包含指定的字符数组,该数组的 index 和 (index + count - 1) 之间的值由从当前源中读取的
// 字符替换。
// index: 开始写入的 buffer 的索引。
// 返回结果: 已读取的字符数,或者如果已到达流的末尾并且未读取任何数据,则为 0。该数小于或等于 count 参数,
// 具体取决于流中是否有可用的数据。
public override int Read(char[] buffer, int index, int count);
// 摘要: 关闭 System.IO.StreamReader 对象和基础流,并释放与读取器关联的所有系统资源。
public override void Close();
2.2.2. 写StreamWriter
写文本文件一般使用StreamWriter,构造实例可通过完整文件名、stream系列对象,可指定编码
格式。
a> 直接构造StreamWriter:
StreamWriter sw = new StreamWriter(@”C:\ReadMe.txt”); //默认使用 UTF8 编码
StreamWriter sw = new StreamWriter(@”C:\ReadMe.txt”, true, Encoding.ASCII);
b> 从一个FileStream实例中获得StreamWriter:
FileStream fs = new FileStream(@”C:\R.txt”,FileMode.CreateNew,FileAccess.Write, FileShare.Read);
StreamWriter sw = new StreamWriter(fs);
c> 从一个FileInfo实例中获得StreamReader,创建一个新文件,并开始写数据:
FileInfo info = new FileInfo(@”C:\ReadMe.txt”);
StreamWriter sw = infoCreateText(); //创建使用 UTF8 编码、从现有文本文件中进行读取StreamReader
// 摘要: 用指定的编码及默认缓冲区大小,为指定的流初始化 System.IO.StreamWriter 类的新实例。
// encoding: 要使用的字符编码。
// stream: 要写入的流。
public StreamWriter(Stream stream, Encoding encoding);
// 摘要: 使用默认编码和缓冲区大小,为指定路径上的指定文件初始化 System.IO.StreamWriter 类的新实例。如果该
// 文件存在,则可以将其改写或向其追加。如果该文件不存在,则此构造函数将创建一个新文件。
// append: 确定是否将数据追加到文件。如果该文件存在,并且 append 为 false,则该文件被改写。如果该文件存在,
// 并且 append 为 true,则数据被追加到该文件中。否则,将创建新文件。
// path: 要写入的完整文件路径。
public StreamWriter(string path, bool append);
public StreamWriter(Stream stream, Encoding encoding, int bufferSize);
StreamWriter(string path, bool append, Encoding encoding);
public StreamWriter(string path, bool append, Encoding encoding, int bufferSize);
// 摘要: 将字符写入流。
// value: 要写入文本流中的字符。
public override void Write(char value);
// 摘要: 将字符数组写入流。
// buffer: 包含要写入的数据的字符数组。如果 buffer 为 null,则不写入任何内容。
public override void Write(char[] buffer)
public override void Write(string value);
public override void Write(char[] buffer, int index, int count);
// 摘要: 关闭当前的 StreamWriter 对象和基础流。
public override void Close();
2.3. Ini文件
操作ini文件,需要特殊的winApi,下面介绍:
// replaces the keys and values for the specified section in an initialization file.
// If the function succeeds, the return value is nonzero.
[DllImport("kernel32")]
public static extern bool WritePrivateProfileSection(
LPCTSTR lpAppName, // [in] section name
LPCTSTR lpString, // [in] data
LPCTSTR lpFileName // [in] file name
);
// retrieves all the keys and values for the specified section of an initialization file.
// return the number of characters copied to the buffer, not including the terminating null character
[DllImport("kernel32", CharSet = CharSet.Unicode)]
public static extern int GetPrivateProfileSection(
LPCTSTR lpAppName, // [in] section name
LPTSTR lpReturnedString, // [out] return buffer
DWORD nSize, // [in] size of return buffer
LPCTSTR lpFileName // [in] initialization file name
);
// retrieves the names of all sections in an initialization file.
// return the number of characters copied to the specified buffer, not including the terminating null character
[DllImport("kernel32", CharSet = CharSet.Unicode)]
public static extern int GetPrivateProfileSectionNames(
LPTSTR lpszReturnBuffer, // [out]return buffer
DWORD nSize, // [in]size of return buffer
LPCTSTR lpFileName // [in] initialization file name
);
// copies a string into the specified section of an initialization file.
// If the function successfully copies the string to the initialization file, the return value is nonzero.
[DllImport("Kernel32", CharSet = CharSet.Unicode)]
public static extern bool WritePrivateProfileString (
LPCTSTR lpAppName, // [in] section name
LPCTSTR lpKeyName, // [in] key name
LPCTSTR lpString, // [in] string to add
LPCTSTR lpFileName // [in] initialization file
);
// retrieves a string from the specified section in an initialization file
// The return value is the number of characters copied to the buffer, not including the terminating null character.
[DllImport("kernel32", CharSet = CharSet.Unicode)]
public static extern int GetPrivateProfileString (
LPCTSTR lpAppName, // [in] section name
LPCTSTR lpKeyName, // [in] key name
LPCTSTR lpDefault, // [in] default string
LPTSTR lpReturnedString, // [out] destination buffer
DWORD nSize, // [in] size of destination buffer
LPCTSTR lpFileName // [in] initialization file name
);
// retrieves an integer associated with a key in the specified section of an initialization file.
// The return value is the integer equivalent of the string following the specified key name in the specified initialization file.
[DllImport("kernel32", CharSet = CharSet.Unicode)]
public static extern int GetPrivateProfileInt(
LPCTSTR lpAppName, // [in] section name
LPCTSTR lpKeyName, // [in] key name
INT nDefault, // [in] return value if key name not found
LPCTSTR lpFileName // initialization file name
);