被MemoryStream狠狠地坑了一把

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就要注意一下了:)

原文地址:https://www.cnblogs.com/smark/p/3105557.html