前一章大致介绍了C#中的一些基本概念。我们将介绍这篇文章C#的I/O操作,这也将是一部小连续剧。这是第一集。让我们简单了解一下C#中的I/O框架。
I/O 的全称是input/output,翻译是输入/输出。键盘,U盘、网络接口、显示器、音响、摄像头等IO设备。所以,对于一个程序I/O又是什么?
对程序而言,I/O是与外界进行数据交换的方式。借用句广告词,程序不生产数据,只是数据的搬用工。当然,正如XX还需要对水进行过滤、消毒等工序,程序还需要对数据进行操作,所以也不完全是搬工,严格来说是加工厂。那么,I/O是工厂的原材料供应商和成品销售商。
在C# 中,I/O整个系统分为三部分:后台存储流、装饰流、流适配器,具体划分如下图所示:
字节数据用于流与流之间的交换,因此可以得出简单的结论,I/O在程序中表现为字节流,换句话说I/O将各种数据转换为字节的工具。
3. Stream 基类C所有的流都是从#中继承下来的Stream类,Stream类别定义了流程的行为和属性,使开发人员能够忽略底层操作系统和基础设备的具体细节。C#对流处理忽略了读流和写流的区别,使其更像是一条便于数据通信的管道。流涉及三个基本操作:
读取 - 将数据从流中传输到数据结构 - 从数据源中搜索数据 - 搜索和修改对流中操作的当前位置由于流量的特性,并非所有流量都支持这三种操作,因此Stream提供三个属性,便于确认流是否支持这三种操作:
public abstract bool CanRead{ get; }// 获取指示当前流是否支持读取值public abstract bool CanWrite{ get; }// 获取指示当前流是否支持写入功能的值public abstract bool CanSeek{ get; }// 获取指示当前流是否支持搜索功能值
以上三个属性由子类根据自身特点确认是否支持读取、写入和搜索。也许三个属性不会都是true,但绝对不是全部false。
以下是一些常见的流:
FileStream 用于操作文件流MemoryStream 操作内存流BufferedStream 缓存流,用于增强其他流的操作性能NetworkStream 使用网络套接字操作流PipeStream 通过匿名和命名管道读写CryptoStream 将数据流链接流链接C# 中I/O所有的操作都属于System.IO命名空间,在这个命名空间C# 定义文件相关类别、各种流量、装饰流量、适配器等相关结构。在以System.IO在命名空间的开头,C#对IO进一步扩展,提供流压缩和解压缩(System.IO.Compression),对枚举文件系统元素进行搜索(System.IO.Enumeration),提供使用内存映射文件的类别(System.IO.MemoryMappedFiles)等内容。
我们先省略后篇幅会介绍的内容,先看看Stream类别中的重要属性和方法:
1. 流量数据的长度
public abstract long Length{ get; }
当Stream对象的CanSeek为true时,也就是说,当流支持搜索时,流的长度可以通过这个属性来确认,即有多少字节数据。
2. 流的位置
public abstract long Position{ get; set; }
当长度相同的前提条件相同时,Stream当对象支持搜索时,流的位置可以通过该属性确认或修改。
3. 读取流中的数据
public abstract int Read (byte[]buffer, int offset, int count);public virtual int ReadByte ();
这是两种不同的阅读方法,一是每次读取多个字节的数据,二是每次只读一个字节的数据。这里详细说明区别:
public abstract int Read (byte[]buffer, int offset, int count);
每次最多读取表示流count字节数据,然后将数据放入buffer中间,位置从下标为offset开始,并返回实际读取的字节数,如果流已读完,则返回0。在这个过程中,Position如果流支持搜索,则可以在程序中调用此属性。在这个过程中,Position如果流支持搜索,则可以在程序中调用此属性。
所以这里会有这样的限制:offset count <= buffer.Length,换句话说,偏移量 最大读取量不得大于缓存数组的长度。
因为这种方法返回到实际读取长度,有些人可能会根据返回的结果来判断是否读完:count例如,如果返回长度小于count认为流已读完;否则流还没读完。
有些流可能会达到这样的效果,但许多流不能根据这个基础来判断流是否读完。也许一次读取的长度小于count,然后再读一遍,发现又有数据了。这是因为IO在大多数情况下,系统是高耗时操作IO性能与程序的操作速度相差甚远。因此,这种情况经常发生:流长为100,缓存字节数组为100,然后第一次读10个字节,第二次读5个字节,一点一点地读100个字节。
因此,读完判断必须以返回值为0。
public virtual int ReadByte ();
如果读取完成返回-1,这种方法非常简单。有些人可能会想,这种方法显然是读一个字节,也就是说byte,那为什么回类型是int呢?因为byte没有负数int有。因此,当返回值不等于-1时,可以放心的类型可以转换为byte。
4. 将数据写入流
public abstract void Write (byte[]buffer, int offset, int count);public virtual void WriteByte (byte value);
流的写入比读取简单得多,至少我们不需要判断流的位置。简单分析一下:
public abstract void Write (byte[]buffer, int offset, int count);
表示从buffer的offset下标开始,取count流中写入字节。所以,对offset、count限制仍然不大于数组。所以,对offset、count限制仍然不大于数组的长度。如果写入成功,流动位置会移动,否则现有位置会保持。
public virtual void WriteByte (byte value);
这种方法更简单,直接写字节给流。
5. 关闭或销毁流
操作完成后,需要关闭以释放流所持有的文件或IO设备等资源。许多人在使用电脑时不能使用电脑QQ发送已在本地打开的excel它会提示文件被占用,无法传输。这就是因为Excel打开文件后,持有与文件相关的流量,因此QQ无法发送。解决方案很简单,关掉excel软件即可。回到现在,我们必须在使用后关闭流量。
那怎样才能关闭流呢?调用以下方法:
public virtual void Close ();
C#虽然设置了Close方法,但不支持开发者手动调用程序Close方法,推荐使用:
public void Dispose ();
该方法将释放流所持有的资源并关闭流。
需要注意的是,在关闭或释放流量之前,将流量中的数据推送到基础设备,即调用:
public abstract void Flush ();
有些流设置了自动推送功能,如果遇到这种流,则不需要手动调用该方法。
对于流量,一旦销毁或关闭,流量就不能再次使用,因此被调用Close、Dispose再试试读/写入流会报错
本文大致介绍了内容C#的IO系统和一些基本操作,下一篇文章将介绍如何操作文件。
请关注我的博客《高先生小屋》