Stream是.net数据流操作的一个封装,它提供统一的流读写规则,为后期开发这方面的功能提供了很大的便利性.有些场景下是直接操作byte[]比较灵活所以Stream派生出MemoryStream从byte[]构建一个stream来方便开发人员使用.但在使用的时候碰到了一个非常坑爹事情.一个非常意想不到的结果...
应用代码
string value = "111111111"; string value1 = "2222222222222222222222"; System.IO.MemoryStream stream = new System.IO.MemoryStream(mBuffer); int count = Encoding.UTF8.GetBytes(value, 0, value.Length, mBuffer, 0); stream.Position = 0; stream.SetLength(count); Console.WriteLine("Length:"+count); Console.WriteLine(Encoding.UTF8.GetString(mBuffer, 0, count)); count = Encoding.UTF8.GetBytes(value1, 0, value1.Length, mBuffer, 0); stream.Position = 0; stream.SetLength(count); Console.WriteLine("Length:" + count); ; Console.WriteLine(Encoding.UTF8.GetString(mBuffer, 0, count)); Console.Read();
以上代码是把不同长度的字符编码到buffer中,然后再设置对应stream的开始位置和长度,从而让stream提供给其他功能使用,比较常见的就是对象反序列化等工作.从代码来看结果输出内容分别value和value1,但最终的运行结果确是
value1对应的内容少了一截...当出现这问题的时候排查了很久数据跟踪但没发现有任何环节有异常,因为buffer在后期根本没有地方对它进行修改,但数据确发生改变.
MemoryStream的一个坑
在跟踪日志来看buffer在经过stream.setlength之前都是好的,但经过这个方法后buffer内容就改变了,后面的代码也没对stream进行任何的操作;所以马上想到地扩容的问题,但由于buffer的长度比较大对应setlength的值也不可能大于buffer分配的长度问题应该不是扩容导致的;无耐之下只好反编译MomeryStream的代码看下,仔细查看MomeryStream的setlength后终于找到问题的根源...
int num = this._origin + (int)value; if (!this.EnsureCapacity(num) && num > this._length) { Array.Clear(this._buffer, this._length, num - this._length); }
这代码说明了一切问题,在setlength里如果没有导致扩容和大于之前的长度,则会增长部分进行一个清除操作...
实际应用中使用的代码
1 namespace MSMQNode.Agent 2 { 3 public class ProtoBufFormater : IObjectFormater 4 { 5 public object Deserialize(ByteArraySegment content) 6 { 7 object result; 8 try 9 {10 content.SetPostion(0);11 string text = content.ReadShortString(Encoding.UTF8);12 Type type = Type.GetType(text);13 if (type == null)14 {15 throw MQError.TYPE_NOTFOUND(text);16 }17 object obj = Activator.CreateInstance(type);18 Stream stream = content.GetStream();19 stream.Position = (long)content.Postion;20 stream.SetLength((long)content.Count);21 obj = RuntimeTypeModel.Default.Deserialize(stream, null, type);22 result = obj;23 }24 catch (Exception error)25 {26 throw new MQMessageFormatError("Deserialize Error!", error);27 }28 return result;29 }30 public ByteArraySegment Serialize(object message)31 {32 ByteArraySegment byteArraySegment = HttpDataPackage.BufferPool.Pop();33 try34 {35 Type type = message.GetType();36 string typeName = Utils.GetTypeName(type);37 byteArraySegment.WriteShortString(typeName, Encoding.UTF8);38 Stream stream = byteArraySegment.GetStream();39 stream.Position = (long)byteArraySegment.Postion;40 stream.SetLength((long)byteArraySegment.Postion);41 RuntimeTypeModel.Default.Serialize(stream, message);42 byteArraySegment.SetInfo(0, (int)stream.Length);43 }44 catch (Exception error)45 {46 HttpDataPackage.BufferPool.Push(byteArraySegment);47 throw new MQMessageFormatError("Serialize Error!", error);48 }49 return byteArraySegment;50 }51 }52 }
解决方法
解决以上问题的办法有几种:
1)在每次使用的时候都针对buffer创建新的MemoryStream
2)由MemoryStream重新写入流数据
3)实现一个简单的Stream,调整一下SetLength代码
针对自己的情况选择第三种方式是最简单,代码调整范围小又能达到不重复创建Stream的目的.直接反编译MemoryStream抄一把:)
1 public class ArraySegmentStream:System.IO.Stream 2 { 3 public ArraySegmentStream(byte[] data) 4 { 5 _buffer = data; 6 _length = 0; 7 _position = 0; 8 _origin = 0; 9 } 10 11 private byte[] _buffer; 12 13 private int _origin; 14 15 private int _position; 16 17 public override bool CanRead 18 { 19 get { return true; } 20 } 21 22 public override bool CanSeek 23 { 24 get { return true; } 25 } 26 27 public override bool CanWrite 28 { 29 get { return true; } 30 } 31 32 public override void Flush() 33 { 34 35 } 36 37 private int _length = 0; 38 39 public override long Length 40 { 41 get { return _length; } 42 } 43 44 public override long Position 45 { 46 get 47 { 48 return _position; 49 } 50 set 51 { 52 _position = (int)value; 53 } 54 } 55 56 public override int Read(byte[] buffer, int offset, int count) 57 { 58 59 int num = this._length - this._position; 60 if (num > count) 61 { 62 num = count; 63 } 64 if (num <= 0) 65 { 66 return 0; 67 } 68 if (num <= 8) 69 { 70 int num2 = num; 71 while (--num2 >= 0) 72 { 73 buffer[offset + num2] = this._buffer[this._position + num2]; 74 } 75 } 76 else 77 { 78 Buffer.BlockCopy(this._buffer, this._position, buffer, offset, num); 79 } 80 this._position += num; 81 return num; 82 } 83 84 public override long Seek(long offset, System.IO.SeekOrigin origin) 85 { 86 switch (origin) 87 { 88 case SeekOrigin.Begin: 89 { 90 int num = this._origin + (int)offset; 91 92 this._position = num; 93 break; 94 } 95 case SeekOrigin.Current: 96 { 97 int num2 = this._position + (int)offset; 98 this._position = num2; 99 break;100 }101 case SeekOrigin.End:102 {103 int num3 = this._length + (int)offset;104 105 this._position = num3;106 break;107 }108 109 }110 return (long)this._position;111 }112 113 public override void SetLength(long value)114 {115 int num = this._origin + (int)value;116 this._length = num;117 if (this._position > num)118 {119 this._position = num;120 }121 }122 123 public override void Write(byte[] buffer, int offset, int count)124 {125 126 int num = this._position + count;127 if (num > this._length)128 {129 this._length = num;130 }131 if (count <= 8 && buffer != this._buffer)132 {133 int num2 = count;134 while (--num2 >= 0)135 {136 this._buffer[this._position + num2] = buffer[offset + num2];137 }138 }139 else140 {141 Buffer.BlockCopy(buffer, offset, this._buffer, this._position, count);142 }143 this._position = num;144 }145 }
总结
真的搞不明白为什么要这样设计,既然Length是可设置的即说明可以由开发人员指定现在流的内容长度,开发人员在设置之也会意识到相应buffer的数据信息.更何况Length的改变并不会更改postion位置,在后面对Stream的写入自然会把之前的内容代替.
如果那位同学以后要这样使用MemoryStream就要注意一下了:)