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 try 34 { 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 else 140 { 141 Buffer.BlockCopy(buffer, offset, this._buffer, this._position, count); 142 } 143 this._position = num; 144 } 145 }
總結
真的搞不明白為什么要這樣設計,既然Length是可設置的即說明可以由開發人員指定現在流的內容長度,開發人員在設置之也會意識到相應buffer的數據信息.更何況Length的改變並不會更改postion位置,在后面對Stream的寫入自然會把之前的內容代替.
如果那位同學以后要這樣使用MemoryStream就要注意一下了:)