1. 什么是流
数据流(Stream)是对串行传输数据的一种抽象表示,是对输入/输出的一种抽象。数据有来源和目的地,衔接两者的就是串流对象。用比喻的方式来说或,数据就好比水,串流对象就好比水管,通过水管的衔接,水由一端流向另一端,如下图所示:
从应用程序的角度来说,如果将数据从来源取出,可以试用输入(读)串流,把数据储存在内存缓冲区;如果将数据写入目的地,可以使用输出(写)串流,把内存缓冲区的数据写入目的地:
当希望通过网络传输数据,或者对文件数据进行操作时,首先需要将数据转化为数据流。典型的数据流和某个外部数据源相关,数据源可以是文件、外部设备、内存、网络套接字等。根据数据源的不同,.Net提供了多个从Stream类派生的子类,每个类代表一种具体的数据流类型,如何磁盘文件直接相关的文件流类FileStream,和套接字相关的网络流类NetworkStream,和内存相关的内存流类MemoryStream等。
2. 流的基本操作
- 写入:将数据从内存缓冲区传输到外部源;
- 读取:将数据从外部源传输到内存缓冲区;
- 查找:重新设置流的当前位置,以便随机读写。但并不是所有的流类型都支持查找,如网络流类没有当前位置的概念,就不支持查找。
Stream是虚拟类,它以及它的派生类都提供了Read和Write方法,可以支持在字节级别上对数据进行读写。Read方法从当前字节流读取字节放至内存缓冲区,Write方法把内存缓冲区的字节写入当前流中。流的写入和读取操作都是基于字节的。
3. 流的种类
(1)文件流 FileStream
FileStream流继承与Stream类,一个FileStream类的实例实际上代表一个文件流,使用FileStream类可以对文件系统上是文件进行读取、写入、打开和关闭操作。
创建FileStream实例
1、.Net提供多种获取FileStream对象的方法,其中构造函数就有10多种,我们看下典型的构造函数顺便讲解下参数含义:
参数说明:
- public FileStream(string path, FileMode mode, FileAccess access);
path指明文件所在的路径信息;
mode是FileMode的枚举值,表示文件打开或创建的方式,含义如下:
- CreateNew:指定操作系统应创建新文件,如果文件已经存在,则引发IOException;
- Create:指定操作系统应创建新文件,如果文件已经存在,它将被覆盖;
- Open:指定操作系统应打开现有文件,如果文件不存在,则引发FileNotFoundException;
- OpenOrCreate:指定操作系统应打开文件,如果文件不存在,则创建新文件;
- Truncate:指定操作系统应打开现有文件,文件一旦打开,就将截断为零字节大小;
- Append:打开先有文件并把Position设置至文件尾,如果文件不存在将创建新文件。Append只能同FileAccess.Write一起使用;
access是FileAccess的枚举值,它控制对文件的访问权限,含义如下:
- Read:打开文件用于只读;
- Write:打开文件用于只写;
- ReadWrite:打开文件,用于读写;
2、除了FileStream类本身提供的构造函数外,System.IO命名空间下的File和FileInfo类也提供了创建FileStream对象的方法。其中OpenRead方法返回只读文件流,OpenWrite方法返回只写文件流。如:
FileStream的读写方法:
- FileStream fs=File.OpenRead(@"c:\file.txt");
FileStream实例
- public override int Read(
- byte[] array, //内存缓冲区,储存从文件流中读取的数据
- int offset, //array开始写入数据的下标值
- int count //从文件流中读取的字节大小
- );
- public override void Write(
- byte[] array, //内存缓冲区,存储了要写入流的字节数据
- int offset, // 从array的下标值开始取数据
- int count //要写入的字节数
- );
- try
- {
- //写入
- FileStream fileStream = new FileStream(@"d:\test.txt", FileMode.OpenOrCreate);
- byte[] content = Encoding.UTF8.GetBytes("我爱我家");
- fileStream.Write(content, 0, content.Length);
- fileStream.Position = 0; //设置当前位置
- content = Encoding.UTF8.GetBytes("我爱你家");
- fileStream.Write(content, 0, content.Length);
- fileStream.Close();
- //读取
- fileStream = new FileStream(@"d:\test.txt", FileMode.Open);
- content = new byte[fileStream.Length];
- fileStream.Read(content, 0, content.Length);
- Console.WriteLine(Encoding.UTF8.GetString(content));
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.Message);
- }
(2)内存流 MemoryStream
和文件流布同,MemoryStream类表示的是保存在内存中的数据流,由内存流封装的数据可以在内存中直接访问。内存一般用于暂时缓存数据以降低应用程序对临时缓冲区和临时文件的需要。
既然字节数据也在内存中存储,为什么还要引入内存流的概念呢?这是因为内存流和字节数组虽然都位于程序缓冲区,但是具有不同特性。内存流相对于字节数组而言,具有流特有的特性,并且容量可自动增长,在数据加密以及对长度不定的数据进行缓存等场合,使用内存流比较方便。
创建MemoryStream实例
MemoryStream有多种构造函数,部分举例如下:
- public MemoryStream();该构造函数初始分配的容量大小为0,随着数据的不断写入,其容量可以不断地自动扩展。
- public MemoryStream(byte[] buffer);根据字节数组buffer初始化,实例的容量大小规定为字节数组的长度。
- public MemoryStream(int capacity);容量固定为capacity。
MemoryStream实例
- MemoryStream mem = new MemoryStream();
- Console.WriteLine("初始分配的容量:"+mem.Capacity+" 初始使用量:"+mem.Length);
- byte[] content = Encoding.UTF8.GetBytes("我爱我家");
- mem.Write(content, 0, content.Length);
- Console.WriteLine("初始分配的容量:" + mem.Capacity + " 初始使用量:" + mem.Length);
(3)网络流NetWorkStream
网络流的意思是数据在网络的各个位置之间是以连续的字节形式传输的,NetWorkStream只能用于面向连接的套接字。
对于NetWorkStream流,写入操作是指从来源端内存缓冲区到网络上的数据传输;读取操作是从网络上到接收端内存缓冲区的数据传输。
4. 流的辅助操作 流的写入和读取操作都是基于字节的,很多时候不太方便,所以针对.NET提供了Stream的操作类,可以直接操作字符,而不用进行编码转换。
(1)StreamWriter和StreamReader
StreamWriter类主要完成一种特定的编码从流中读取字符的功能,它的构造函数和常用方法如下:
- StreamWriter(Stream stream),构造函数,StreamWriter不仅能对FileStream对象,而且能够对NetWorkStream、MemoryStream等继承了Stream类的流对象进行封装;
- StreamWriter(string path),构造函数,如需要处理的是文件流,则可直接利用文件路径创建以UTF8编码的StreamWriter对象;
- Write(string value),方法,向数据流写入数据;
- WriteLine(string value),方法,向数据流写入数据,并追加一个换行符(Unix)或回车换行符(Windows);
- Close(),方法,关闭流,释放资源;
StreamReader类主要以特定的编码向流中写入字符,它的构造函数和常用方法如下:
- StreamReader(Stream stream),构造函数,利用流对象创建StreamReader对象;
- StreamReader(string path),构造函数,如需要处理的是文件流,则可直接利用文件路径创建以UTF8编码的StreamReader对象;
- string ReadLine(),方法,读取数据直到遇到换行符(Unix)或回车换行符(Windows);
- string ReadToEnd(),方法,读取到文件尾的全部数据
- int Peek(),方法,返回数据中下一个可用字符的编码值,如到达文件末尾则返回-1;
- Close(),方法,关闭流,释放资源;
StreamWriter和StreamReader实例
- try
- {
- //写入
- StreamWriter sw = new StreamWriter(@"d:\abc.txt");
- sw.WriteLine("我爱我家");
- sw.Close();
- //读取
- StreamReader sr = new StreamReader(@"d:\abc.txt");
- Console.WriteLine(sr.ReadToEnd());
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex.Message);
- }
(2)BinaryReader和BinaryWriter
为了操作图像、压缩文件等二进制流文件,System.IO还提供了BinaryReader类和BinaryWriter类,用于二进制模式的读写流。
BinaryReader的每个读方法都有一个对应的写方法,比如针对不同的数据结构,BinaryReader类提供了ReadByte、ReadBoolean、ReadInt、ReadInt16、ReadString等,与之对应的BinaryReader类则提供了多个重载的Write方法,分别对应上面的读方法,所以使用起来非常方便。例如,当Write方法传递的参数是Int32类型时,利用BinaryWriter的Write方法可用将Int32类型数据转化为长度为4的字节数组,并将字节流传递给一个Stream对象。
BinaryReader和BinaryWriter实例
流辅助操作类本身不是流,它只是一种让流数据更容易操作的辅助工具。没有这些辅助类,流一样可以正常工作,只是稍微麻烦一些。流使用后必须及时关闭,可以手动刷新内容,也可以在关闭时自动刷新。一定要明确哪些是流,哪些是流操作类。
- byte[] sendData;
- using (MemoryStream mem = new MemoryStream()) {
- BinaryWriter writer = new BinaryWriter(mem, Encoding.UTF8);
- writer.Write(SocketTools.strConvertToHexByte(SocketTools.md5(packageHead.CarIdentifier, 32)));
- writer.Write(packageHead.ProjectIdentifier);
- writer.Write(packageHead.ModelIdentifier);
- writer.Write(packageHead.ProtocolVersion);
- writer.Write(SocketTools.reverseShort(packageHead.RequestSerial));
- writer.Write(SocketTools.reverseInt32(packageHead.PacketTimestamp));
- writer.Write(SocketTools.reverseShort(packageHead.FunctionNo));
- writer.Write(SocketTools.reverseShort(packageHead.DataLength));
- writer.Write(msg.ToCharArray());
- sendData = mem.ToArray();
- writer.Close();
- }
流辅助类在操作流之前,需要指定流的编码格式,只有指定正确的编码格式,才能从流中读取正确的数据。