C# MemoryStream源代码剖析

来源:互联网 发布:房产证ps软件 编辑:程序博客网 时间:2024/06/05 18:40

MemoryStream是C#处理IO流中很常用的一个类 MS官方的解释文字为:创建其支持存储区为内存的流。


它的类声明为 class System.IO.MemoryStream : System.IO.Stream 需要C#基础库mscorlib的引用

源代码有500多行 于是不贴了 毫无意义
总体来说 MemoryStream的工作方式和它字面的意思一样 就是对一段内存进行流读写控制
它的基本存储单位是byte[]
通过咱的上一篇文章可以知道 byte[]为.net的托管数组 同时也可以获得它的内存指针进行操作
所以 说MemoryStream是操作的一段内存区间并不过分

MemoryStream的构造函数有7个重载
它们可以大致分成两部分:带byte[]参数和不带byte[]参数的
下面分别进行详细说明:

public MemoryStream();public MemoryStream(int capacity);public MemoryStream(byte[] buffer);public MemoryStream(byte[] buffer, bool writable);public MemoryStream(byte[] buffer, int index, int count);public MemoryStream(byte[] buffer, int index, int count, bool writable);public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible);

MemoryStream内部的私有字段如下:

private byte[] _buffer;private int _capacity;private bool _expandable;private bool _exposable;private bool _isOpen;private int _length;private int _origin;private int _position;private bool _writable;private const int MemStreamMaxLength = 0x7fffffff;

_buffer:MemoryStream操作的byte[]引用
_capacity:MemoryStream操作的数组容量 实际值为_buffer的终值索引
_expandable:表示该MemoryStream是否可以自动扩展 和构造函数相关
_exposable:表示_Buffer是否可以公开 和构造函数相关
_isOpen:表示MemoryStream是否尚未Close或DIspose
_length:MemoryStream当前可Read的数据段长度 实际值为_buffer的终值索引
_origin:MemoryStream操作的_buffer的起始索引
_position:MemoryStream当前操作的读写指针 实际值为_buffer的索引
_writable:表示MemoryStream是否可写 和构造函数相关
MemStreamMaxLength:这常量好像没啥解释的=△=

不带byte[]参数的构造函数 初始化时会在内部创建一个新的buffer 类型为byte[] 容量为capacity
如果不提供capacity参数则默认为0 基本初始化设置如下:

this._buffer = new byte[capacity];this._capacity = capacity; //缓冲区容量的终止索引为0this._expandable = true; //可以因SetLength Write Capacity{Set;}等操作 对_buffer进行自动扩展this._writable = true;this._exposable = true; //可以进行GetBuffer()操作 公开获取_buffer的引用this._origin = 0; //操作起始索引为0this._length = 0; //当前没有数据 read的终止索引为0

带byte[]参数但是不提供起始索引和长度的两个构造函数 和上面的初始化过程基本相同 
有区别的部分如下:

this._buffer = buffer; //直接获取缓冲区引用this._length = this._capacity = buffer.Length; //数据长度为整个数组长度this._writable = writable; //自定义可写性 默认为truethis._exposable = false; //不公开对构造数组的访问 this._expandable = false; //不可自动扩展

带byte[]参数并且提供索引和数据长度的3个构造函数则比较复杂
他们的构造参数设定如下:

this._buffer = buffer; //直接获取源数据的数组引用this._origin = this._position = index; //设置ms的起始指针和读写指针this._length = this._capacity = index + count; //设置ms的数据段终止指针和缓冲区终止指针this._writable = writable; //自定义可写性 默认为truethis._exposable = publiclyVisible; //自定义_buffer的公开性 默认为falsethis._expandable = false; //不可自动扩展

显然 对于预先申请好内存空间作为缓冲区构造MemoryStream 自身是无法进行扩展的
而对于MemoryStream自行申请的缓冲区 它有权进行自动扩展以容纳足够多的数据
扩展过程也是简单的重新申请缓冲区-复制原数据到新的缓冲区-设置_length指针为数据长度
因为byte[]为托管资源 旧的缓冲区就被简单的舍弃 交给GC自行回收
所以 常识性的 如果让MemoryStream自行管理缓冲区的话 最好预先提供一个足够大的capacity 以避免内存重新申请造成的性能损失

至此 我们知道了 MemoryStream管理一段byte数组 并且内部维护了4个指针 从前到后大体是_origin, _position, _length, _capacity
而MS重写其基类Stream的属性Positon = _position - _origin; Length = _length - _origin;
自定义虚属性Capacity = _capacity - _origin;

对Position属性进行set操作只会简单的移动读写指针 没有正向的越界检查
执行SetLength(int value)函数只会简单的移动长度指针 在必要的时候会对新增的长度至0(内部使用Array.Clear()函数) 以及自动扩展缓冲区
对Capacity属性进行set操作 会先检查_expandable及value>length 然后重新申请byte[] 进行数据复制...

 

读写操作

MemoryStream继承并重写了基类的Write WriteByte Read ReadByte函数
因为内部维护了读写指针和长度指针 Read执行很容易 只需要把缓冲区的字节复制到输出数组的字节 并且移动读写指针即可
Write操作之前会检查_position _length和_capacity的距离 必要的时候先扩展缓冲区再进行复制

有意思的是 源代码中对读写也进行了一个小的优化
在数据长度小于等于8字节时 使用while循环逐字节复制 反之则使用Buffer.InternalBlockCopy内部函数进行字节区段复制

Flush()没有执行过程 所以你不需要对MemoryStream执行Flush 它的读写是直接体现在缓冲区数组中的

 

销毁操作

MemoryStream重写了基类的void Dispose(bool disposing)函数 执行了如下过程:

this._isOpen = false;this._writable = false;this._expandable = false;

它没有真正的销毁缓冲区 只是加上了读写限制
另外byte[]是托管资源 也无法显式的销毁 最后依然是交给GC

Close()是执行的Dispose(true) 所以它和(IDisposable)MemoryStream.Dispose()的行为是一致的

综上所述 MemoryStream是通过自行申请或传入byte[]参数作为缓冲区操作对象 使用抽象的流读写方式进行操作
并且由GC控制自动销毁的高效内存流处理对象
它常用做高效率的缓冲流 或者一般的内存存储单位使用
结合BinaryReader StreamReader等读写器可以操作的更为方便
原创粉丝点击