#region Copyright 2010-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.IO;
namespace CSharpTest.Net.IO
{
///
/// Creates a stream over an array of byte arrays in memory to reduce use of the LOH and array resizing operation.
///
public class SegmentedMemoryStream : Stream
{
#region Contents
class Contents
{
readonly int _segmentSize;
byte[][] _segments;
int _segmentCount;
long _length;
public Contents(int segmentSize)
{
_segmentSize = segmentSize;
_segments = new byte[16][];
}
public int SegmentSize { get { return _segmentSize; } }
public long Length { get { return _length; } set { _length = value; } }
public byte[] this[int index] { get { return _segments[index]; } }
public void GrowTo(int newSegmentCount)
{
lock (this)
{
while (_segments.Length < newSegmentCount)
Array.Resize(ref _segments, _segments.Length << 1);
while (_segmentCount < newSegmentCount)
{
_segments[_segmentCount] = new byte[_segmentSize];
_segmentCount++;
}
}
}
}
#endregion
readonly Contents _contents;
long _position;
///
/// Creates a memory stream that uses 32k segments for storage
///
public SegmentedMemoryStream()
: this(short.MaxValue)
{ }
///
/// Create a memory stream that uses the specified size of segments
///
public SegmentedMemoryStream(int segmentSize)
{
_contents = new Contents(segmentSize);
_position = 0;
}
/// Creates a 'clone' of the stream sharing the same contents
protected SegmentedMemoryStream(SegmentedMemoryStream from)
{
_contents = from._contents;
_position = 0;
}
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
///
public override bool CanRead { get { return _position >= 0; } }
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
///
public override bool CanSeek { get { return _position >= 0; } }
///
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
///
public override bool CanWrite { get { return _position >= 0; } }
///
/// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
///
public override void Flush()
{ }
///
/// Releases the unmanaged resources used by the and optionally releases the managed resources.
///
protected override void Dispose(bool disposing)
{
_position = -1;
base.Dispose(disposing);
}
///
/// When overridden in a derived class, gets the length in bytes of the stream.
///
public override long Length
{
get { AssertOpen(); return _contents.Length; }
}
private void AssertOpen()
{
if (_position < 0) throw new ObjectDisposedException(GetType().FullName);
}
private void OffsetToIndex(long offset, out int arrayIx, out int arrayOffset)
{
arrayIx = (int)(offset / _contents.SegmentSize);
arrayOffset = (int)(offset % _contents.SegmentSize);
}
///
/// When overridden in a derived class, sets the length of the current stream.
///
public override void SetLength(long value)
{
AssertOpen();
Check.InRange(value, 0L, int.MaxValue);
int arrayIx, arrayOffset;
OffsetToIndex(value, out arrayIx, out arrayOffset);
int chunksRequired = arrayIx + (arrayOffset > 0 ? 1 : 0);
_contents.GrowTo(chunksRequired);
_contents.Length = value;
}
///
/// When overridden in a derived class, gets or sets the position within the current stream.
///
public override long Position
{
get { return _position; }
set
{
AssertOpen();
Check.InRange(value, 0L, int.MaxValue);
if (value > _contents.Length)
SetLength(value);
_position = value;
}
}
///
/// When overridden in a derived class, sets the position within the current stream.
///
public override long Seek(long offset, SeekOrigin origin)
{
if (origin == SeekOrigin.End)
offset = _contents.Length + offset;
if (origin == SeekOrigin.Current)
offset = _position + offset;
return Position = offset;
}
///
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
///
public override int Read(byte[] buffer, int offset, int count)
{
AssertOpen();
int total = 0;
if ((_contents.Length - _position) < count)
count = (int)(_contents.Length - _position);
while (count > 0)
{
int arrayIx, arrayOffset;
OffsetToIndex(_position, out arrayIx, out arrayOffset);
int amt = Math.Min(_contents.SegmentSize - arrayOffset, count);
byte[] chunk = _contents[arrayIx];
Array.Copy(chunk, arrayOffset, buffer, offset, amt);
count -= amt;
offset += amt;
total += amt;
_position += amt;
}
return total;
}
///
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
///
public override void Write(byte[] buffer, int offset, int count)
{
AssertOpen();
if ((_position + count) > _contents.Length)
SetLength(_position + count);
while (count > 0)
{
int arrayIx, arrayOffset;
OffsetToIndex(_position, out arrayIx, out arrayOffset);
int amt = Math.Min(_contents.SegmentSize - arrayOffset, count);
byte[] chunk = _contents[arrayIx];
Array.Copy(buffer, offset, chunk, arrayOffset, amt);
count -= amt;
offset += amt;
_position += amt;
}
}
///
/// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
///
public override int ReadByte()
{
byte[] bytes = new byte[1];
return Read(bytes, 0, 1) == 1 ? bytes[0] : -1;
}
///
/// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
///
public override void WriteByte(byte value)
{
Write(new byte[] { value }, 0, 1);
}
}
}