C#OOP 之九 文件输入输出流
流的概念
流是一个抽象的概念,它相当于日常生活中“流”的概念,什么意思呢?比如说有河里流的水,管道中的石油,电网中的电流等。
C#采用流模型读写文件里的数据。这是一种非常经典的文件读取方式,在很多高级语言中都有使用。具体来数就是把文件看做是数据源,然后建立一条管道让这些数据流入流出。加假如程序运行时需要这些数据,我们可以利用这条管道从文件里吸取数据放入内存,如果我们需要吧数据保存,那么我们也是通过这条管道让数据流入文件(图9.1),如何建立这条管道,如何控制数据的流入流出,这需要用到流的对象,请往下看。
|
在介绍文件的读写时,先来介绍一下要使用的类:
类 名 |
作 用 |
Stream类 |
流的基类,定义流的基本操作,如Read和Write等方法. |
FileStream类 |
以文件作为数据源的流,可以用来读写文件. |
NetworkStream类 |
以网络作为数据源的流,可以通过此流发送或接受网络数据. |
MemoryStream类 |
该流以内存而不是文件或网络连接作为数据源. |
TextReader类 |
字符读取的基类,定义基本字符读取操作. |
TextReader类 |
字符写入的基类,定义基本字符写入的操作. |
StreamReader类 |
实现从流读取字符的操作类. |
StreamWriter类 |
实现将字符写入六的操作类. |
BinaryReader类 |
以二进制的形式从流读取数据. |
BinaryWriter类 |
以二进制的形式将数据写入流.. |
每一种流都可以独立操作数据,当然系统为我们提供了简单易用的流操作器,使我们可以更简单的操作流,从而更简单的完成数据的读写,上面类中凡是以“Writer”和“Reader”结尾的类都是流操作器。他们需要一个流为参数,从而操作这个流,就整个流体系来说使非常复杂的,流的数据源可以使文件、网络、内存、等,不同的流操作方法也不尽相同。但从另以方面讲,流的操作又并不复杂,因为系统为我们提供了非常简单的流操作器,我们几乎只需要把流创建出来,然后交给流操作器,就可以操作这个流进行数据的输入输出了。
|
从前面的介绍我们知道,读写文件要用到的文件流对象。我们又知道可以通过流读写器来操作流对象从而也能完成文件的读写操作。所以通常读写文件的方法有两种:一种主要是利用FileStream的读写功能直接读写文件,一种使利用StreamReader和StreamWriter这两个流操作类读写文件。这两种方法还用到系统的其他相关类,详述如下:
(1) 通过FileStream读写文件
● File类,用来创建文件流FileStream对象。
●FileStream类,创建和操作文件流FileStream对象。
(2) 通过流读写器读写文件
●StreamReader类,用来读取文件。
●StreamWriter类,用来写入文件。
在讨论读写文件之前我们先来看一下文件里数据的样子,一般来说文件里的数据只存在两种形式。一种是二进制的,一种是文本的。存放二进制数据的叫二进制文件,存放文本的叫文本文件。从这个角度来说计算机里的文件也就两种,一种是二进制文件,一种是文本文件。我们平成见到的图片、音频、可执行文件(以EXE结尾的文件)、动态链接库(以DLL结尾的文件)等都是二进制文件。文本文件我们也经常见到。比如纯文本文件(以TXT结尾的文件)、网页文件(以HTM或XML结尾的文件)等都是文本文件。二进制文件数据存放的最小单位是字节,文本文件存放的最小数据单元是字符。对于文本文件来说可以直接打开查看其内容,而二进制文件无法读懂它的数据,用相关软件查看会发现里面存放的都是十六进制数字。
|
对于计算机来讲,事实上只能认识机器码,也就是二进制数据。所以字符如果要存放进计算机中,我们需要对其进行编码。编码是将一个或一组字符转化为一个字节序列的过程。解码是一个反向操作的过程,即将以一编码字节序列转换为一个或一组字符。
比如最常见的ASCII编码就是把英文字符和一些其他常用的控制字符进行了编码。如图 7.31所示,如字符A的ASCII码值是65,那么我们说A这个字符在计算机内的处理和存放都是用数字65来实现的。你可以认为数字65就是字符A在计算机世界的代号,你从一个世界进入另一个世界总要换一下身份是不,换了身份但人没换,就是这样。所以说字符A和数字65完全等价,因为他们只是以个字符两种表示方式而已。我们在程序里也能看出这一点,假如你在程序里比较数字65和字符A,那么他们一定是相等的,比如计算"64= ='A'"这个条件表达式,返回结果一定为真。
ASCII码在计算机内存放时占一个字节(ASCII码只占一个字节,可允许128个字符,普通使用的ISO8859-1扩展ASCII码集也只允许256个字符),所以最多存放256个字符,,而汉字有上万个,ASCII码是无法编码的。因此汉字要在计算机内存放和表示还需要编码,况且小日本、小韩国的子也要编码等。为了解决世界上存在这么多文字编码问题,于是Unicode编码诞生了,Unicode有两套标准,一套UCS-2(Unicode-16),用2个字节为字符编码,另一套骄傲UCS-4(Unicode-32),用4个字节为字符编码,以目前常用的UCS-2为例,它可以表示的字符数为65535,基本上可以容纳所有欧美字符和绝大部分的亚洲字符。
程序要读写文本文件就不得不跟这些编码方式打交道,因此.NET框架里有一套类专门来处理各种文本编码以及各种编码之间的转换。这些类存放在命名空间System.Text下,在这些类里最重要的就是Encoding类,这个类提供了不同编码方式的编码和解码操作,比如我们可以使用Encoding类的静态方法对字符串进行Unicode编码和解码:
解码:byte[]data=System.Text.Encoding.Unicode.GetBytes("Hellow");
编码:stringsvalue=System.Text.Encoding.Unicode.GetString(data);
对于我们来讲最常用的是利用系统默认的编码方式,要做到这一点可以使用Encoding里面的Default属性,使用这个属性可以自动获取当前系统编码方式进行编/解码:
解码:byte[]data=System.Text.Encoding.Default.GetBytes("Hellow");
编码:stringsvalue=System.Text.Encoding.Default.GetString(data);
另外汉字还有自己的编码方式,最常用的有两种,一种是国标码,令一种就是区位码。国标码是一个四位十六进制数,区位码是一个四位的十进制数,每个国标码或区位码都对应这一个唯一的汉字或符号,但因为十六进制数我们很少用到,所以大家常用的是区位码,它的前两位叫做去吗,后两位叫做位码。我们这里着重讨论一下区位码,使用区位码不仅可以表示汉字,如“3479”代表“嘛”字,“3482”代表“买”字等,而且区位码还可以表示特殊符号,比如“0172”表示“¤”,“0264”代表“⑧”等。
但这里要注意的是区位码在计算机内存储是通过两个ASCII码是“160+位码数字”,同样用相反的规则也可以得到区位码。我们来看下面的这段代码,这段代码演示如何提取汉字的区位码:
Strig s="啊";
Byte[]b =System.Text.Encoding.Default.GetBytes(s);
System.Console.WriteLine(b[0]-160);
System.Console.WriteLine(b[1]-160);
输出结果是1601,这个编码就是“啊”字区位码。转换过程首先是把字符“啊”转换成其对应的字节数组,这个字节数组就是该字在计算机内表示。然后取第一个字节转换成区码,取第二个字节转换成位码,区码和位码合起来就是区位码。
|
二进制数据或普通文本都可以使用文件流FileStream对象直接读取.在使用文件流读写文件前,我们需要先了解一下文件流FileStream了.使用FileStream可以对任何文件进行读取、写入、打开和关闭等操作,下面是该类常用的属性和方法:
属 性 |
说 明 |
CanRead |
判断当前流是否支持读取,返回bool值,true表示可以读取 |
CanWrite |
判断当前流是否支持写入,返回bool值,true表示可以写入 |
方 法 |
说 明 |
Read |
从流中读取数据,返回字节数组 |
Write |
将字节块(字节数组)写入流 |
Seek |
设置文件读取或写入的起始位置 |
Flush |
清除该流缓冲区,使得所有缓冲的数据都被写入的文件中 |
Close |
关闭当前流并释放与之关联的所有系统资源(文件句柄等) |
在使用流操作文件时设计到文件的操作方式,比如你是以独占方式打开文件还是以共享方式打开文件,你是以写方式还是度方式打开文件等,在读写文件时,这些打开文件的方式需要制定。这些文件的打开方式由三个系统枚举来决定,这些打开方式需要在创建文件操作流时制定,这三个枚举也是在System.IO命名空间下:
● FileAccess
文件访问方式,告诉文件操作者该文件的访问方式,包括三个枚举项:Read(对文件读访问)、ReadWrite(对文件读或写访问)、Write(对文件进行写操作)。文件访问方式必须在构造FileStream对象时指定,因为对于不同的访问方式系统创建文件流对象时会对其进行不同的初始化。
● FileMode
文件打开模式,告诉操作系统你要以何种方式打开文件。文件访问方式必须与文件打卡模式兼容。文件的访问方式(FileAccess)是用来指定文件流的操作能力,而FileMode是用来告诉操作系统在读写文件前操作系统应该做些什么工作。文件打开模式一共包括六个枚举项,分别如下:
Append:打开现有文件准备向文件追加数据,只能同FileAccess.Write一起使用。
Create:只是操作系统应该创建新文件,如果文件已存在,它将被覆盖。
CreateNew:只是操作系统应该创建新文件,如果文件已存在,将引发异常。
Open:只是操作系统应打开现有文件,打开文件的能力取决与FileAccess所指定的值。
OpenOrCreate:只是操作系统应打开文件,如果文件不存在则创建新文件。
Truncate:只是操作系统应打开现有文件,并清空文件内容。
● FileShare
文件共享方式,只是别的用户以什么权限访问该文件,FileShare方式是为了避免几个程序同时访问同一个文件会造成异常的情况。文件共享方式一共包括四个,分别是None(谢绝共享当前文件)、Read(允许别的程序读取当前文件)、ReadWrite(允许别的程序读写当前文件)、Write(允许别的程序写当前文件)。
到时候看看如何创建文件流对象了,我们在上面也提到过创建文件流可以使用文件流类自身的构造方法也可以使用File文件操作类。下面我们就来详细介绍这两种方式:
通过FileStream类创建文件流对象,FileStream类的构造方法已经被重载,按照构造方法参数的多少一次有下列方式:
FileStream(String 文件路径,FileMode 文件打开模式)
FileStream(String 文件路径,FileMode文件打开模式,FileAccess 文件访问方式)
FileStream(String 文件路径,FileMode 文件打开模式,FileAccess文件访问方式,FileShare 共享方式)
示例如下:
FileStream(“C:\\Log.txt”,FileMode.Create);
FileStream(“C:\\Log.txt”,FileMode.Create,FileAccess.Write);
FileStream(“C:\\Log.txt”,FileMode.Create,FileAccess.Write,FileShare.None);
从上面的示例不难看出,双肩以个FileStream对象需要指定FileMode、FileAccess、FileShare这些创建方式。当然,有时也可以省略某些创建方式,如果构造方法没有提供FileShare方式将使用默认的FileShare.Read方式;如果没有提供FileAccess那么系统默认的访问方式将根据打开方式而默认;比如你是FileMode.Create打开方式,那么访问方式就是FileAccess.Write,如果你的发开方式是OpenOrWrite,那么访问方式就是ReadWrite等。
第二种方式是通过以前介绍的Filel类来创建文件流对象,用File类可以方便的创建的创建流对象,这种方式甚至比上一种方式更方便更常用。用File类来创建文件流对象也有几种情况:
自定义打开文件的方式:File.Open(String,FileMode)
打开文件进行读取:File.OpenRead(String)
打开文件进行写入:File.OpenWrite(String)
示例如下:
FileStreamfs = File.Open(“C:\\Log.txt”,FileMode.Append);
FileStreamfs = File.OpenRead(“C:\\Log.txt”);
FileStreamfs = File.OpenWrite(“C:\\Log.txt”);
一旦创建了文件流对象我们就可使用文件流进行读写了,不过在使用文件流对象时一定要助其其打开模式和读写方式。比如以读写方式FileAccess.Read打开的文件就不能进行写操作,以FileMode.Append模式打开的文件可以进行写操作但不能进行读操作,否则都将发生程序异常。如果以FileMode.Open打开文件,若文件不存在将发生异常。这里推荐几种常用的做法,如果你是写文件那么就用FileMode.Open打开方法打开文件,这样如果该文件不存在会自动创建,同时这个模式允许你不删除文件旧内容而是继续添加新内容。如果是读文件那么就用File.OpenRead方法,这个方法既简单又实用。
用FileStream对于文件的操作不管代码有多复杂基本上来说要经过三个步骤:
1. 创建文件读写流对象.
2. 写或读操作。
3. 关闭文件流。
创建文件流的方法我们前面已经介绍过,对于写和读等操作,在这里也做一下简单的介绍.对于文件流对象只能进行字节级的读写操作。跟读写相关的方法除了读(Read)和写(Write)之外还有清空缓冲(Flush)和读写定位(Seek)等。首先我们来看一下这四个方法的原型:
写操作: void Write(byte[]array,int offset,int count)
读操作:int Read(byte[]array,int offset,int count)
读写定位: long Seek(longoffset,SeekOrigin origin)
清空缓冲:<void>Flush()
对于写操作来讲 array 是要写入六的字节叔祖,offset指定从数组的第几个开始写入流,count试药写入流的字节个数,对于读操作,从流中读取字节并存放在array数组里,offset只读到的数据从数组的哪个下标开始存,count表示读取多少个字节。而Seek方法可以指定从流的哪个位置开始读取,SeekOrigin是个枚举,用来指定从流的哪个位置开始,它有三个值分别是Begin、Current、End。剩下的Flush方法,主要用来清空该流的所有缓冲区,使得所有缓冲中的数据都被写入到目标文件中。
|
我们现在要建立一个 程序将“Hello World!”字符串写进文件,然后从文件里读出。在写入文件时如果发现该文件不存在那么就建立这个文件。
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingSystem.IO;
namespaceTest
{
class Program
{
static void Main(string[]args)
{
UseFileStreamWrite();//写入文件
UseFileStreamRead();//读取文件
}
public static void UseFileStreamWrite()
{
using (FileStream fs = File.Open("C:\\Log.txt",FileMode.Append))
{
byte[] buffer =System.Text.Encoding.Default.GetBytes("Hello World");
fs.Write(buffer,0,buffer.Length);
fs.Close();
}
}
public static void UseFileStreamRead()
{
using (FileStream fs = File.OpenRead("C:\\Log.txt"))
{
byte[] buffer = new byte[50];
fs.Read(buffer,0,buffer.Length);
fs.Close();
string svalue = System.Text.Encoding.Default.GetString(buffer);
Console.WriteLine(svalue);
}
}
}
}
|
我们在来看一个离子,我们将建立一个Windows记事本。这个简单Windows记事本将拥有打开文件、编辑内容和保存文件的功能,效果图如下:
实现步骤如下:
1. 首先建立一个Windows应用程序,项目名为Notepad。
2. 然后改Form窗体的Name属性为FrmEdit,Text属性为“记事本示例”。
3. 在FrmEdit窗体中添加几个空间,空间属性设置如下表:
控 件 类 型 |
控 件 名 称 |
描 述 |
文本框 |
TxtPath |
用来填入打开或保存文件的路径 |
按钮 |
btnOpen |
点击后可打开并读取文件 |
按钮 |
btnSave |
点击后可保存编辑内容 |
文本框 |
txtEdit |
设置多行文本框 |
4. 双击btnSave按钮在其点击时间对应的方法中添加如下代码:
private void btnSave_Click(object sender,EventArgs e)
{
using(FileStream fs =File.Open(this.txtPath.Text,FileMode.Append))
{
byte[] buffer =System.Text.Encoding.Default.GetBytes(this.txtEdit.Text);
fs.Write(buffer,0,byffer.Length);
fs.Close();
}
}
5. 双击btnOpen按钮添加如下代码:
private void btnOpen_Click(objectsender,EventArgs e)
{
using(FileStream fs =File.OpenRead(this.txtPath.Text))
{
byte[] buffer = new byte[fs.Length];
fs.Read(buffer,0,buffer.Length)
fs.Close();
this.txtEdit.Text = System.Text.Encoding.Default.GetString(buffer);
}
}
6. 运行后我们可以通过这个记事本编辑文字并按照指定文件保存。
同时打开功能可以打开指定路径的文本类型的文件进行阅读和编辑。整个程序用到的知识和上一个示例没多少区别。这里要注意的是,在读取的时候我们是一次性从流中读取了所有的数据。实际上我们还可以使用循环逐步从流中读取数据,这对于操作大的文件比较使用。循环的判断条件是Read的返回值,这个返回值表示真是读取到的字节数,如果此字节数小于等于0,我们认为文件已经读完了。我们可以把上面读取的代码替换成下面的代码:
privatevoid btnOpen_Click(object sender,EventArgs e)
{
using(FileStream fs =File.OpenRead(this.txtPath.Text))
{
byte[] buffer = new byte[255];
while(fs.Read(buffer,0,buffer.Length)>0)
{
this.txtEdit.Text +=System.Text.Encoding.Default.GetString(buffer);
}
fs.Close();
}
}
|
文件流对象操作的是最底层的字节级数据。它可以读取任何文件,但是因为它比较底层的原因,操作起普通文本文件显得很笨拙。下面我们将介绍另外一种读取文本文件的方式,我们在上面也提到过,就是使用流读写器StreamReader和StreamWriter来读写文本文件。流读写器从底层封装了文件流,它提供更便捷的读写文件方式,有了它你再也不需要麻烦的编解码操作(它默认使用UTF8编解码方式)。
我们先来看一下流读写器常用的属性和方法。
StreamWriter |
说 明 |
BaseStream |
属性,获取其内部的文件流对象 |
WriteLine |
向流写入一行,效果是向文件写入一行 |
Flush |
清空缓冲区 |
Close |
关闭StreamWriter内置的文件流 |
StreamReader |
说 明 |
BaseStream |
属性,获取其内部文件流的对象 |
ReadLine |
向流读取一行,效果是向文件读取一行 |
ReadToEnd |
从流中读取所有数据,效果是读取文件的所有内容 |
Close |
关闭StreamReader内置的文件流 |
流读写器操作非常灵活,不必拘泥于直接读写。比如写入时参数可以是字符串类型,读取返回的也可以是字符串类型。具体方法原型如下:
StreamWriter写方法:
Public void WriteLine(Char)
Public void WriteLine(Double)
Public void WriteLine(String)
….参数可以是任何常见的数据类型
StreamWriter读方法:
Public string ReadLine();
Public override string ReadToEnd()
使用流读写器读写文件之前也需要创建读写器实例。创建一个写入器实例最简单的方法是只需要提供一个文件的路径即可,其他的打开模式、访问方式、共享方式、编码方式都可系统默认。我们来看一下创建流写入器常用的构造方式:
StreamWriter(Stream 操作的文件流对象)
StreamWriter(String 写入的文件)
StreamWriter(String 写入的文件,Boolean是否追加)
StreamWriter(Stream 写入的文件,Encoding编码方式)
通过第一种构造方式我们可以给写入器提供一个自定义的文件流对象让写入器对其操作。第二种方式只需要一个文件名,这种方式会创建新文件,并写入。但是这种方式如果要写入的文件存在将会毫不留情的覆盖。假如要向存在的文件里面追加数据则要用第三种方式。第三种方式可以制定目标文件是否追加数据,如果使用true将不覆盖原有文件而是向里面追加数据,当然这种方式如果目标文件没有也会创建这个文件。第四种方式可以给写入器制定写入流和编码方式。
具体创建流读写器的示例如下;
StreamWriter sw = newStreamWriter(File.openWrite(“C:\\Log.txt”));
StreamWriter sw = new StreamWriter(“C:\\Log.txt”);
StreamWriter sw = new StreamWriter(“C:\\Log.txt”,false);
另外和文件流类一样,流读写器可以由File类来创建其对象:
StreamWriter sw = File.AppendText(“C:\\Log.txt”);
StreamWriter sw = File. CreateText (“C:\\Log.txt”);
用File类来创建流读写器的方式比较方便,File.CreateText方法相当于上面的“newStreamWriter(String)”方法。而File.AppendText方法最常用,它相当于
“new StreamWriter(“C:\\Log.txt”,false)”创建方式,它会检测目标文件是否存在,如果不存在则创建,存在则追加数据。
创建流读取器就没有创建流写入器那么麻烦了,当然它也有集中形式,它也可以通过File来创建,对于这几种形式我们列举如下:
StreamReader(Stream 读取的文件流)
StreamReader (Stream 读取的文件流,Encoding 解码方式)
StreamReader ( String读取的文件)
File.OpenText(String 读取的文件)
示例如下:
StreamReader sr = new StreamReader(File.OpenRead(“C:\\log.txt”));
StreamReader sr = new StreamReader(“C:\\Log.txt”);
StreamReader sr = File.OpenText(“C:\\Log.txt”);
介绍了这么多流读写器的内容,我们在说流读写器操作文件是多么的简单。现在让我们小试牛刀,我们吧上面一节使用文件流读写文件的例子再用流读写器重写一遍。首先我们重写第一个例子,实现向文件里写入字符串数据,并读出和显示出来。新建一个控制台应用程序添加代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace Test
{
class Program
{
static void Main(string [] args)
{
UseStreamWriter();
UseStreamReader();
}
public voidUseStreamWriter()
{
StreamWritersw = new StreamWriter("C:\\Log.txt");
sw.WriteLine("HelloC#");
sw.Closer()
}
public voidUseStreamReader()
{
StreamReadersr = new StreamReader("C:\\Log.txt");
Console.WriteLine(sr.ReadToEnd());
sr.close();
}
}
}
我们要用流读写器来实现上面那个记事本程序,只需要将保存和打开按钮对应的代码换成下面的代码即可:
private voidbtnSave_Click(object sender, EventArgs e)
{
using (StreamWriter sw = new StreamWriter(this.txtPath.Text))
{
sw.WriteLine(this.txtEdit.Text);
sw.Close();
}
}
private void brnOpen_Click(object sender, EventArgs e)
{
using (StreamReader sr = new StreamReader(this.txtPath.Text))
{
this.txtEdit.Text = sr.ReadToEnd();
sr.Close();
}
}
|
我们人可以通过大脑记住一些事物,在必要的时候可以提取这些事物进行逻辑判断。对于程序也需要存储一些数据,在必要的时候会提取这些数据进行显示或计算。程序保存数据从古到今只有三种方式,一种是通过文件来保存数据,一种是通过注册表,最后一种就是通过数据库。这三种方式,在本站我们介绍了两种,它们非常重要。当然,企业级软件一般使用数据库来存储数据,但使用文件和注册表来保存一些辅助数据也是经常的,比如使用日志文件保存程序运行的一些错误信息,使用注册表保存程序的版本信息等。
|
1.使用C#编写DES加密程序。
文章来源: aaaedu.blog.csdn.net,作者:tea_year,版权归原作者所有,如需转载,请联系作者。
原文链接:aaaedu.blog.csdn.net/article/details/51276921
- 点赞
- 收藏
- 关注作者
评论(0)