u本节学习目标:
n了解读写内存流MemoryStream的特点
n学习如何建立内存流MemoryStream
n了解读写缓存流BufferedStream
n学习如何建立缓存流BufferedStream
前面第二节,介绍了文件流类FileStream,本节要继续介绍其他流。那么什么是流?在.net程序中,涉及的输入和输出都是通过流来实现的。流是串行化设备的抽象表示,流以读/写字节的方式从存储器读/写数据。存储器是存储媒介,磁盘或内存都是存储器。正如除磁盘外还存在着多种存储器,除文件流之外也存在多种流,例如:网络流、内存流、缓存流等。类Stream及其派生类组成流的家族。如图3-12所示:
图3-12 流家族类关系图
所有流的类都是从类Stream派生出来的。类Stream是所有流的抽象基类,所以它不能被实例化为对象,只能通过变量引用派生类的对象。Stream变量所引用对象具有一下一种或多种功能:
n读:通过Read()或ReadByte()方法实现读数据;
n写:通过Write()或WriteByte()方法实现写数据;
n定位:通过Position属性和Seek()方法实现定位。
注意:
派生的流对象只能支持这些功能中的一部分。例如:NetworkStream不支持定位。可以利用从Stream派生出来的对象的CanRead、CanWrite和CanSeek属性判断流对象支持哪些操作。 |
---|
另外,对于类MemoryStream,有两点需要说明:
n对内存而不是对磁盘进行数据读写;
n减少了对临时缓冲区和文件的需要。
而对于类BufferedStream,有四点需要说明:
n对缓冲区进行数据读写;
n允许操作系统创建自己的缓冲区;
n输入/输出效率高且速度更快;
n在网络通讯的时候经常会使用到。
类MemoryStream创建这样的流,该流以内存而不是磁盘或网络连接作为支持存储区。类MemoryStream封装以无符号字节数组形式存储的数据,该数组在创建MemoryStream对象时被初始化,或者该数组可创建为空数组。可在内存中直接访问这些封装的数据。内存流可降低应用程序中对临时缓冲区和临时文件的需要。
FileStream对象与MemoryStream对象有很大区别,主要体现在以下方面:
nFileStream对象的数据来自文件,而MemoryStream对象的数据来自内存缓冲区。这两个类都继承自Stream类。
nMemoryStream的数据来自内存中的一块连续区域,这块区域称为“缓冲区(Buffer)”。可以把缓冲区看成一个数组,每个数组元素可以存放一个字节的数据。
n在创建MemoryStream对象时,可以指定缓冲区的大小,并且可以在需要的时候更改。
类MemoryStream的构造函数有7种重载,我们这里重点介绍三种,如表3-16所示:
表3-16 类MemoryStream的常用构造函数
名称 | 说明 |
---|---|
MemoryStream () | 使用初始化为零的可扩展容量初始化 MemoryStream 类的新实例。 |
MemoryStream (byte[]) | 基于指定的字节数组初始化 MemoryStream 类的无法调整大小的新实例。 |
MemoryStream (byte[], Boolean) | 使用按指定要求设置的 CanWrite 属性基于指定的字节数组初始化 MemoryStream 类的无法调整大小的新实例。 |
内存流对象还有一些重要的属性。其中Length属性代表了内存流对象存放的数据的真实长度,而Capacity属性则代表了分配给内存流的内存空间大小。可以使用字节数组创建一个固定大小的MemoryStream。
n小实验1:
MemoryStream mem = new MemoryStream(buffer); //这时,无法再设置Capacity属性的大小。 |
---|
n小实验2:
MemoryStream mem = new MemoryStream(buffer, false); //这时,CanWrite属性就被设置为false 。 |
---|
这样在内存流对象被实例化时,一些属性就被影响了。
本案例您将学习到:如何通过使用内存流的属性、方法来获取内存流的占用空间信息及改变内存流空间大小。
u实验步骤(1):
由图3-13所示,从工具箱之中拖拽五个Label控件到Form窗体上,拖拽一个Button控件。
图3-13 MemoryStream类案例界面图
u实验步骤(2):
用鼠标双击所有Button控件,进入.cs文件编辑状态准备进行开发。代码加下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; namespace FileOptionApplication { public partial class Form14 : Form { public Form14() { InitializeComponent(); } //建立字节数组 byte[] buffer = new byte[600]; /// <summary> /// 获取测试性数据 /// </summary> private void GetTestData() { for (int i = 0; i < 600; i++) { buffer[i] = (byte)(i % 256); //byte类型的数最大不能超过255,用256取模实现 } } /// <summary> /// button1按钮的鼠标单击Click事件 /// </summary> private void button1_Click(object sender, EventArgs e) { //创建测试数据 GetTestData(); //创建内存流对象,初始分配50字节的缓冲区 MemoryStream mem = new MemoryStream(50); //向内存流中写入字节数组的所有数据 mem.Write(buffer,0,buffer.GetLength(0)); //使用从缓冲区读取的数据将字节块写入当前流。 //参数: //1、buffer从中写入数据的缓冲区。 //2、offset buffer中的字节偏移量,从此处开始写入。 //3、count最多写入的字节数。 //GetLength(0) 为 GetLength 的一个示例,它返回 Array 的第一维中的元素个数。 label1.Text = "写入数据后的内存流长度是:"+mem.Length.ToString(); label2.Text = "分配给内存流的缓冲区大小:"+mem.Capacity.ToString(); mem.SetLength(500); label3.Text = "调用SetLength方法后的内存流长度:" + mem.Length.ToString(); mem.Capacity = 620;//注意:此值不能小于Length属性 label4.Text = "调用Capacity方法后缓冲区大小:" + mem.Capacity.ToString(); //将读写指针移到距流开头10个字节的位置 mem.Seek(45, SeekOrigin.Begin); label5.Text = "内存中的信息是:"+mem.ReadByte().ToString(); } } } |
---|
u实验步骤(3):
调试、运行程序,得到效果如图3-14所示:
图3-14 MemoryStream类案例运行效果图
类BufferedStream就是给另一流上的读写操作添加一个缓冲区。缓冲区是内存中的字节块,用于缓存数据,从而减少对操作系统的调用次数。因此,缓冲区可提高读取和写入性能。使用缓冲区可进行读取或写入,但不能同时进行这两种操作。BufferedStream 的Read和Write方法自动维护缓冲区的读写过程。
BufferedStream可写在某些类型的流周围。它提供从基础数据源或储存库读取字节以及将字节写入基础数据源或储存库的实现。使用BinaryReader和BinaryWriter读取和写入其他数据类型。BufferedStream用于在不需要缓冲区时防止缓冲区降低输入和输出速度。如果您读取和写入的大小始终大于内部缓冲区的大小,那么BufferedStream可能甚至无法分配内部缓冲区。
BufferedStream也在共享缓冲区中缓冲读取和写入。假设您几乎始终执行一系列读取或写入操作,而很少在读取和写入之间切换。类BufferedStream的构造函数有2种重载,如表3-17所示:
表3-17 类BufferedStream的常用构造函数
名称 | 说明 |
---|---|
BufferedStream (Stream) | 使用默认的缓冲区大小 4096 字节初始化 BufferedStream 类的新实例。 |
BufferedStream (Stream, Int32) | 使用指定的缓冲区大小初始化 BufferedStream 类的新实例。 |