亲宝软件园·资讯

展开

C# 基础知识系列- 14 IO篇之入门IO

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

加载全部内容

相关教程
猜你喜欢
用户评论