diff --git a/CatLib.Core.sln.DotSettings.user b/CatLib.Core.sln.DotSettings.user new file mode 100644 index 0000000..4997d71 --- /dev/null +++ b/CatLib.Core.sln.DotSettings.user @@ -0,0 +1,4 @@ + + <AssemblyExplorer> + <Assembly Path="D:\Software\VS2017\Common7\IDE\ReferenceAssemblies\v2.0\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll" /> +</AssemblyExplorer> \ No newline at end of file diff --git a/src/CatLib.Core.NetStandard/CatLib.Core.NetStandard.csproj b/src/CatLib.Core.NetStandard/CatLib.Core.NetStandard.csproj index e622e81..5c0b3ab 100644 --- a/src/CatLib.Core.NetStandard/CatLib.Core.NetStandard.csproj +++ b/src/CatLib.Core.NetStandard/CatLib.Core.NetStandard.csproj @@ -104,6 +104,9 @@ + + + @@ -132,8 +135,10 @@ + + diff --git a/src/CatLib.Core.Tests/CatLib.Core.Tests.csproj b/src/CatLib.Core.Tests/CatLib.Core.Tests.csproj index e13b715..3fb9c76 100644 --- a/src/CatLib.Core.Tests/CatLib.Core.Tests.csproj +++ b/src/CatLib.Core.Tests/CatLib.Core.Tests.csproj @@ -51,11 +51,14 @@ + + + diff --git a/src/CatLib.Core.Tests/Properties/AssemblyInfo.cs b/src/CatLib.Core.Tests/Properties/AssemblyInfo.cs index cb1810d..f5b6759 100644 --- a/src/CatLib.Core.Tests/Properties/AssemblyInfo.cs +++ b/src/CatLib.Core.Tests/Properties/AssemblyInfo.cs @@ -25,5 +25,5 @@ [assembly: Guid("3c9f4024-910c-4881-a04d-34a6c3a09019")] -[assembly: AssemblyVersion("1.2.7.0")] -[assembly: AssemblyFileVersion("1.2.7.0")] +[assembly: AssemblyVersion("1.2.8.0")] +[assembly: AssemblyFileVersion("1.2.8.0")] diff --git a/src/CatLib.Core.Tests/Support/SortSet/SortSetTests.cs b/src/CatLib.Core.Tests/Support/SortSet/SortSetTests.cs index 8b33683..68702d3 100644 --- a/src/CatLib.Core.Tests/Support/SortSet/SortSetTests.cs +++ b/src/CatLib.Core.Tests/Support/SortSet/SortSetTests.cs @@ -58,25 +58,36 @@ public void TestRandValue() } [TestMethod] + public void TestRank() { - var sortSets = new SortSet(); + var n = 100; + while (n-- > 0) + { + var sortSets = new SortSet(); + + sortSets.Add(1000, 85); + sortSets.Add(999, 75); + sortSets.Add(998, 185); + sortSets.Add(997, 85); + sortSets.Add(996, 185); + sortSets.Add(995, 85); + + Assert.AreEqual(1, sortSets.GetRank(995)); + Assert.AreEqual(995, sortSets.GetElementByRank(1)); + Assert.AreEqual(997, sortSets.GetElementByRank(2)); + Assert.AreEqual(1000, sortSets.GetElementByRank(3)); + Assert.AreEqual(996, sortSets.GetElementByRank(4)); + Assert.AreEqual(998, sortSets.GetElementByRank(5)); - sortSets.Add(1000, 85); - sortSets.Add(999, 75); - sortSets.Add(998, 185); - sortSets.Add(997, 85); - sortSets.Add(996, 185); - sortSets.Add(995, 85); - - Assert.AreEqual(1, sortSets.GetRank(995)); - Assert.AreEqual(995, sortSets.GetElementByRank(1)); - Assert.AreEqual(997, sortSets.GetElementByRank(2)); - Assert.AreEqual(1000, sortSets.GetElementByRank(3)); - Assert.AreEqual(996, sortSets.GetElementByRank(4)); - Assert.AreEqual(998, sortSets.GetElementByRank(5)); - - Assert.AreEqual(3, sortSets.GetRangeCount(80, 90)); + var i = 100; + var faild = 0; + while (i-- > 0) + { + Assert.AreEqual(3, sortSets.GetRangeCount(80, 90)); + } + Console.WriteLine(faild); + } } [TestMethod] diff --git a/src/CatLib.Core.Tests/Support/Storage/MemoryStorageTests.cs b/src/CatLib.Core.Tests/Support/Storage/MemoryStorageTests.cs new file mode 100644 index 0000000..4aa4493 --- /dev/null +++ b/src/CatLib.Core.Tests/Support/Storage/MemoryStorageTests.cs @@ -0,0 +1,139 @@ +/* + * This file is part of the CatLib package. + * + * (c) Yu Bin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Document: http://catlib.io/ + */ + +using System; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CatLib +{ + [TestClass] + public class MemoryStorageTests + { + [TestMethod] + public void TestWrite() + { + // Append() is alias as Write + var storage = new MemoryStorage(4); + storage.Append(Encoding.Default.GetBytes("hello world"), 0, 11); + var data = new byte[16]; + Assert.AreEqual(11, storage.Read(data, 0, data.Length)); + Assert.AreEqual("hello world", Encoding.Default.GetString(Arr.Slice(data, 0, 11))); + } + + [TestMethod] + public void TestAppend() + { + var storage = new MemoryStorage(4); + storage.Append(Encoding.Default.GetBytes("hello world"), 0, 11); + storage.Append(Encoding.Default.GetBytes("1"), 0, 1); + var data = new byte[16]; + Assert.AreEqual(12, storage.Read(data, 0, data.Length)); + Assert.AreEqual("hello world1", Encoding.Default.GetString(Arr.Slice(data, 0, 12))); + + storage.Append(Encoding.Default.GetBytes("2"), 0, 1); + Assert.AreEqual(13, storage.Read(data, 0, data.Length)); + Assert.AreEqual("hello world12", Encoding.Default.GetString(Arr.Slice(data, 0, 13))); + } + + [TestMethod] + public void TestBlockAutomaticExpansion() + { + var storage = new MemoryStorage(4, 2); + storage.Append(Encoding.Default.GetBytes("12345678"), 0, 8); + storage.Append(Encoding.Default.GetBytes("9"), 0, 1); + + var data = new byte[16]; + Assert.AreEqual(9, storage.Read(data, 0, data.Length)); + Assert.AreEqual("123456789", Encoding.Default.GetString(Arr.Slice(data, 0, 9))); + } + + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public void TestDispose() + { + MemoryStorage storage; + using (storage = new MemoryStorage(4, 2)) + { + storage.Append(Encoding.Default.GetBytes("12345678"), 0, 8); + } + + var data = new byte[16]; + storage.Read(data, 0, data.Length); + } + + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public void TestDispose2() + { + MemoryStorage storage; + using (storage = new MemoryStorage(4, 2)) + { + + } + storage.Append(Encoding.Default.GetBytes("12345678"), 0, 8); + } + + [TestMethod] + public void TestJumpWrite() + { + var storage = new MemoryStorage(4, 2); + storage.Append(Encoding.Default.GetBytes("1234"), 0, 4); + storage.Write(Encoding.Default.GetBytes("5678"), 0, 4, 13); + Assert.AreEqual(17, storage.Length); + + var data = new byte[] + { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + }; + Assert.AreEqual(17, storage.Read(data, 0, data.Length)); + Assert.AreEqual("1234\0\0\0\0\0\0\0\0\05678", Encoding.Default.GetString(Arr.Slice(data, 0, 17))); + } + + [TestMethod] + [ExpectedException(typeof(OutOfMemoryException))] + public void TestOutOfMemory() + { + var storage = new MemoryStorage(8, 4, 2); + try + { + storage.Append(Encoding.Default.GetBytes("12345678"), 0, 8); + } + catch (OutOfMemoryException) + { + + } + + storage.Append(Encoding.Default.GetBytes("1"), 0, 1); + } + + [TestMethod] + public void TestDoubleDispose() + { + var storage = new MemoryStorage(8, 4, 2); + Assert.AreEqual(false, storage.Disabled); + storage.Dispose(); + Assert.AreEqual(true, storage.Disabled); + storage.Dispose(); + Assert.AreEqual(true, storage.Disabled); + } + + [TestMethod] + public void TestGetLocker() + { + using (var storage = new MemoryStorage(8, 4, 2)) + { + Assert.AreNotEqual(null, storage.Locker); + } + } + } +} diff --git a/src/CatLib.Core.Tests/Support/Stream/StorageStreamTests.cs b/src/CatLib.Core.Tests/Support/Stream/StorageStreamTests.cs new file mode 100644 index 0000000..23f8fea --- /dev/null +++ b/src/CatLib.Core.Tests/Support/Stream/StorageStreamTests.cs @@ -0,0 +1,295 @@ +/* + * This file is part of the CatLib package. + * + * (c) Yu Bin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Document: http://catlib.io/ + */ + +using System; +using System.IO; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CatLib +{ + [TestClass] + public class StorageStreamTests + { + [TestMethod] + public void TestWrite() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + using (var readStream = new StorageStream(storage, false)) + { + var data = new byte[16]; + Assert.AreEqual(9, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("123456789", Encoding.Default.GetString(data, 0, 9)); + } + } + + [TestMethod] + public void TestReadMult() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + + StorageStream readStream1 = null; + StorageStream readStream2 = null; + + try + { + readStream1 = new StorageStream(storage, false); + readStream2 = new StorageStream(storage, false); + + var data = new byte[16]; + Assert.AreEqual(9, readStream1.Read(data, 0, data.Length)); + Assert.AreEqual("123456789", Encoding.Default.GetString(data, 0, 9)); + + Assert.AreEqual(9, readStream2.Read(data, 0, data.Length)); + Assert.AreEqual("123456789", Encoding.Default.GetString(data, 0, 9)); + } + finally + { + if (readStream1 != null) + { + readStream1.Dispose(); + } + + if (readStream2 != null) + { + readStream2.Dispose(); + } + } + } + + [TestMethod] + public void TestAttr() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + Assert.AreEqual(true, stream.CanWrite); + } + + var readStream = new StorageStream(storage, false); + Assert.AreEqual(9, readStream.Length); + Assert.AreEqual(false, readStream.CanWrite); + Assert.AreEqual(true, readStream.CanRead); + Assert.AreEqual(true, readStream.CanSeek); + Assert.AreEqual(false, readStream.CanTimeout); + + readStream.Dispose(); + Assert.AreEqual(false, readStream.CanWrite); + Assert.AreEqual(false, readStream.CanRead); + Assert.AreEqual(false, readStream.CanSeek); + Assert.AreEqual(false, readStream.CanTimeout); + } + + [TestMethod] + public void TestSeek() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + + using (var readStream = new StorageStream(storage, false)) + { + var data = new byte[16]; + Assert.AreEqual(9, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("123456789", Encoding.Default.GetString(data, 0, 9)); + Assert.AreEqual(0, readStream.Read(data, 0, data.Length)); + + readStream.Seek(1, SeekOrigin.Begin); + Assert.AreEqual(8, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("23456789", Encoding.Default.GetString(data, 0, 8)); + + readStream.Seek(-1, SeekOrigin.Current); + Assert.AreEqual(1, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("9", Encoding.Default.GetString(data, 0, 1)); + + readStream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(9, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("123456789", Encoding.Default.GetString(data, 0, 9)); + + readStream.Seek(-1, SeekOrigin.End); + Assert.AreEqual(1, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("9", Encoding.Default.GetString(data, 0, 1)); + } + } + + [TestMethod] + public void TestSetPosition() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + + using (var readStream = new StorageStream(storage, false)) + { + var data = new byte[16]; + Assert.AreEqual(9, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("123456789", Encoding.Default.GetString(data, 0, 9)); + Assert.AreEqual(0, readStream.Read(data, 0, data.Length)); + + readStream.Position = 1; + Assert.AreEqual(8, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("23456789", Encoding.Default.GetString(data, 0, 8)); + } + } + + [TestMethod] + public void TestGetPosition() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + + using (var readStream = new StorageStream(storage, false)) + { + var data = new byte[16]; + Assert.AreEqual(9, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("123456789", Encoding.Default.GetString(data, 0, 9)); + Assert.AreEqual(0, readStream.Read(data, 0, data.Length)); + Assert.AreEqual(9, readStream.Position); + } + } + + [TestMethod] + [ExpectedException(typeof(IOException))] + public void TestSeekSmallZero() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + + using (var readStream = new StorageStream(storage, false)) + { + readStream.Seek(-1, SeekOrigin.Begin); + } + } + + [TestMethod] + [ExpectedException(typeof(IOException))] + public void TestSeekLargeLength() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + + using (var readStream = new StorageStream(storage, false)) + { + readStream.Seek(1, SeekOrigin.End); + } + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void TestSetLength() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.SetLength(0); + } + } + + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public void TestStorageDispose() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + Assert.AreEqual(true, stream.CanWrite); + } + storage.Dispose(); + var a = storage.Length; + } + + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public void TestDisposeStorage() + { + var storage = new MemoryStorage(); + storage.Dispose(); + var stream = new StorageStream(storage); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void TestCannotWriteable() + { + var storage = new MemoryStorage(); + using (var stream = new StorageStream(storage, false)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + } + + [TestMethod] + public void TestDoubleDispose() + { + var storage = new MemoryStorage(); + StorageStream stream; + using (stream = new StorageStream(storage)) + { + stream.Write(Encoding.Default.GetBytes("123456789"), 0, 9); + } + stream.Dispose(); + } + + [TestMethod] + [ExpectedException(typeof(ObjectDisposedException))] + public void TestDisposeGetPosition() + { + var storage = new MemoryStorage(); + var stream = new StorageStream(storage, false); + stream.Dispose(); + var a = stream.Position; + } + + [TestMethod] + public void TestNotAppend() + { + var storage = new MemoryStorage(4, 4); + + var stream = new StorageStream(storage); + stream.Write(Encoding.Default.GetBytes("hello world"), 0, 11); + stream.Dispose(); + + stream = new StorageStream(storage); + stream.Write(Encoding.Default.GetBytes("hello 12345"), 0, 11); + + using (var readStream = new StorageStream(storage, false)) + { + var data = new byte[32]; + Assert.AreEqual(11, readStream.Read(data, 0, data.Length)); + Assert.AreEqual("hello 12345", Encoding.Default.GetString(data, 0, 11)); + Assert.AreEqual(0, readStream.Read(data, 0, data.Length)); + } + } + } +} diff --git a/src/CatLib.Core.Tests/Support/Util/StreamExtensionTests.cs b/src/CatLib.Core.Tests/Support/Util/StreamExtensionTests.cs new file mode 100644 index 0000000..ed5ea25 --- /dev/null +++ b/src/CatLib.Core.Tests/Support/Util/StreamExtensionTests.cs @@ -0,0 +1,32 @@ +/* + * This file is part of the CatLib package. + * + * (c) Yu Bin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Document: http://catlib.io/ + */ + +using System.IO; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace CatLib.Core.Tests.Support.Util +{ + [TestClass] + public class StreamExtensionTests + { + [TestMethod] + public void TestAppendTo() + { + var stream1 = new MemoryStream(Encoding.Default.GetBytes("Hello world")); + var stream2 = new MemoryStream(16); + + var count = stream1.AppendTo(stream2); + Assert.AreEqual(11, count); + Assert.AreEqual("Hello world", Encoding.Default.GetString(stream2.GetBuffer(), 0, (int)stream2.Length)); + } + } +} diff --git a/src/CatLib.Core/CatLib.Core.csproj b/src/CatLib.Core/CatLib.Core.csproj index 2fb2a97..ff2c583 100644 --- a/src/CatLib.Core/CatLib.Core.csproj +++ b/src/CatLib.Core/CatLib.Core.csproj @@ -85,6 +85,9 @@ + + + @@ -100,6 +103,7 @@ + diff --git a/src/CatLib.Core/CatLib/Facade.cs b/src/CatLib.Core/CatLib/Facade.cs index 4382351..7ff6f4b 100644 --- a/src/CatLib.Core/CatLib/Facade.cs +++ b/src/CatLib.Core/CatLib/Facade.cs @@ -62,7 +62,7 @@ static Facade() /// public static TService Instance { - get { return Make(); } + get { return HasInstance ? instance : Resolve(); } } /// diff --git a/src/CatLib.Core/Properties/AssemblyInfo.cs b/src/CatLib.Core/Properties/AssemblyInfo.cs index 604d6bc..a4a8b9b 100644 --- a/src/CatLib.Core/Properties/AssemblyInfo.cs +++ b/src/CatLib.Core/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ [assembly: Guid("4204658e-81fd-4106-a347-890cd369c8a4")] -[assembly: AssemblyVersion("1.2.7.0")] -[assembly: AssemblyFileVersion("1.2.7.0")] +[assembly: AssemblyVersion("1.2.8.0")] +[assembly: AssemblyFileVersion("1.2.8.0")] [assembly: InternalsVisibleTo("Assembly-CSharp-Editor"), InternalsVisibleTo("Assembly-CSharp-Editor-firstpass"), diff --git a/src/CatLib.Core/Support/SortSet/SortSet.cs b/src/CatLib.Core/Support/SortSet/SortSet.cs index 5b27a71..1a1e17f 100644 --- a/src/CatLib.Core/Support/SortSet/SortSet.cs +++ b/src/CatLib.Core/Support/SortSet/SortSet.cs @@ -472,8 +472,8 @@ public int GetRangeCount(TScore start, TScore end) Guard.Requires(end != null); Guard.Requires(Compare(start, end) <= 0); - int rank = 0, bakRank = 0; - SkipNode bakCursor = null; + int rank = 0, leftRank = 0; + SkipNode leftCursor = null; var isRight = false; var cursor = header; @@ -490,13 +490,15 @@ public int GetRangeCount(TScore start, TScore end) cursor = cursor.Level[i].Forward; } - if (bakCursor != null) + if (leftCursor != null) { continue; } - bakCursor = cursor; - bakRank = rank; + // 设定最左边最高层的跳跃游标和排名 + // 之后将由这个游标来开始向下查找 + leftCursor = cursor; + leftRank = rank; } if (isRight) @@ -504,11 +506,12 @@ public int GetRangeCount(TScore start, TScore end) continue; } - cursor = bakCursor; - rank ^= bakRank ^= rank ^= bakRank; + cursor = leftCursor; + leftRank ^= (rank ^= leftRank); + rank ^= leftRank; } while (isRight = !isRight); - return Math.Max(0, rank - bakRank); + return Math.Max(0, rank - leftRank); } /// diff --git a/src/CatLib.Core/Support/Storage/IStorage.cs b/src/CatLib.Core/Support/Storage/IStorage.cs new file mode 100644 index 0000000..2a277b2 --- /dev/null +++ b/src/CatLib.Core/Support/Storage/IStorage.cs @@ -0,0 +1,64 @@ +/* + * This file is part of the CatLib package. + * + * (c) Yu Bin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Document: http://catlib.io/ + */ + +using System; +using System.Threading; + +namespace CatLib +{ + /// + /// 基础存储 + /// + public interface IStorage : IDisposable + { + /// + /// 存储数据的长度 + /// + long Length { get; } + + /// + /// 读写锁 + /// + ReaderWriterLockSlim Locker { get; } + + /// + /// 是否应被释放 + /// + bool Disabled { get; } + + /// + /// 将指定缓冲区的数据写入到内存存储 + /// + /// 指定缓冲区 + /// 缓冲区的起始位置 + /// 缓冲区的长度 + /// 存储的起始位置 + void Write(byte[] buffer, int offset, int count, long index = 0); + + /// + /// 将指定缓冲区的数据追加到内存存储 + /// + /// 指定缓冲区 + /// 缓冲区的起始位置 + /// 写入的长度 + void Append(byte[] buffer, int offset, int count); + + /// + /// 读取数据到指定缓冲区 + /// + /// 指定缓冲区 + /// 缓冲区的起始位置 + /// 读取的长度 + /// 存储的起始位置 + /// 实际读取的长度 + int Read(byte[] buffer, int offset, int count, long index = 0); + } +} diff --git a/src/CatLib.Core/Support/Storage/MemoryStorage.cs b/src/CatLib.Core/Support/Storage/MemoryStorage.cs new file mode 100644 index 0000000..f439416 --- /dev/null +++ b/src/CatLib.Core/Support/Storage/MemoryStorage.cs @@ -0,0 +1,458 @@ +/* + * This file is part of the CatLib package. + * + * (c) Yu Bin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Document: http://catlib.io/ + */ + +using System; +using System.Threading; + +namespace CatLib +{ + /// + /// 内存存储 + /// + public class MemoryStorage : IStorage + { + /// + /// 区块元数据 + /// + private class BlockMeta + { + /// + /// 区块下标 + /// + public int BlockIndex; + + /// + /// 起始偏移量 + /// + public long StartOffset; + + /// + /// 终止偏移量 + /// + public long EndOffset; + + /// + /// 存储数据 + /// + public ArraySegment Storage; + + /// + /// 获取相对偏移量 + /// + /// 全局偏移量 + /// 相对偏移量 + public int GetRelativeOffset(long position) + { + return (int)(position - StartOffset); + } + + /// + /// 获取当前区块剩余量 + /// + /// 全局偏移量 + /// 剩余量 + public int GetFreeSize(long position) + { + return (int)(EndOffset - position); + } + } + + /// + /// 当前存储最大内存使用量 + /// + public long MaxMemoryUsable { get; private set; } + + /// + /// 单个内存块的大小 + /// + public int BlockSize { get; private set; } + + /// + /// 数据长度 + /// + public long Length + { + get + { + AssertDisabled(); + return length; + } + } + + /// + /// 存储的数据 + /// + private BlockMeta[] storage; + + /// + /// 实际存储的长度 + /// + private long length; + + /// + /// 读写锁 + /// + private ReaderWriterLockSlim locker; + + /// + /// 读写锁 + /// + public ReaderWriterLockSlim Locker + { + get + { + AssertDisabled(); + return locker ?? (locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)); + } + } + + /// + /// 是否已经被释放的 + /// + public bool Disabled { get; private set; } + + /// + /// 构建一个新的内存存储 + /// + /// 块缓冲区大小 + /// 起始块数量 + public MemoryStorage(int blockBuffer = 4096, int capacity = 16) + : this(long.MaxValue, blockBuffer, capacity) + { + + } + + /// + /// 构建一个新的内存存储 + /// + /// 最大内存使用量 + /// 块缓冲区大小 + /// 起始块数量 + public MemoryStorage(long maxMemoryUsable, int blockBuffer = 4096, int capacity = 16) + { + MaxMemoryUsable = maxMemoryUsable; + BlockSize = blockBuffer; + length = 0; + storage = new BlockMeta[GetPrime(capacity)]; + } + + /// + /// 将指定缓冲区的数据写入到内存存储 + /// + /// 指定缓冲区 + /// 缓冲区的起始位置 + /// 缓冲区的长度 + /// 内存存储的起始位置 + public void Write(byte[] buffer, int offset, int count, long index = 0) + { + Guard.Requires(buffer != null); + Guard.Requires(index >= 0); + Guard.Requires(offset >= 0); + Guard.Requires(count >= 0); + Guard.Requires(buffer.Length - offset >= count); + AssertDisabled(); + + EnsureStorageBlock(index + count); + var blockMeta = GetBlockByPosition(index); + + do + { + if (count <= blockMeta.EndOffset - index) + { + // 如果当前区块可以写入全部的数据 + Buffer.BlockCopy(buffer, offset, blockMeta.Storage.Array, blockMeta.GetRelativeOffset(index), + count); + index += count; + count = 0; + } + else + { + var blockFreeSize = blockMeta.GetFreeSize(index); + Buffer.BlockCopy(buffer, offset, blockMeta.Storage.Array, blockMeta.GetRelativeOffset(index), + blockFreeSize); + index += blockFreeSize; + offset += blockFreeSize; + count -= blockFreeSize; + blockMeta = GetBlockByIndex(blockMeta.BlockIndex + 1); + } + } while (count > 0); + + length = Math.Max(length, index + count); + } + + /// + /// 将指定缓冲区的数据追加到内存存储 + /// + /// 指定缓冲区 + /// 缓冲区的起始位置 + /// 缓冲区的长度 + public void Append(byte[] buffer, int offset, int count) + { + Write(buffer, offset, count, length); + } + + /// + /// 读取数据到指定缓冲区 + /// + /// 指定缓冲区 + /// 缓冲区的起始位置 + /// 读取的长度 + /// 存储的起始位置 + /// 实际读取的长度 + public int Read(byte[] buffer, int offset, int count, long index = 0) + { + Guard.Requires(buffer != null); + Guard.Requires(index >= 0); + Guard.Requires(offset >= 0); + Guard.Requires(count >= 0); + Guard.Requires(buffer.Length - offset >= count); + AssertDisabled(); + + var read = 0; + var blockMeta = GetBlockByPosition(index, true); + + if (count + index > length) + { + count = (int)(length - index); + } + + if (count <= 0) + { + return 0; + } + + do + { + if (blockMeta == null) + { + // 如果是跳空块那么直接给定数据 + Array.Clear(buffer, offset, BlockSize); + read += BlockSize; + index += BlockSize; + offset += BlockSize; + count -= BlockSize; + blockMeta = GetBlockByPosition(index, true); + continue; + } + + var startIndex = blockMeta.GetRelativeOffset(index); + var blockFreeSize = blockMeta.GetFreeSize(index); + + if (count <= blockFreeSize) + { + Buffer.BlockCopy(blockMeta.Storage.Array, startIndex, buffer, offset, count); + read += count; + count = 0; + } + else + { + Buffer.BlockCopy(blockMeta.Storage.Array, startIndex, buffer, offset, blockFreeSize); + read += blockFreeSize; + index += blockFreeSize; + offset += blockFreeSize; + count -= blockFreeSize; + blockMeta = GetBlockByIndex(blockMeta.BlockIndex + 1, true); + } + } while (count > 0); + + return read; + } + + /// + /// 获取指定位置的区块下标 + /// + /// 指定位置 + /// 是否允许为空 + /// 区块数据 + private BlockMeta GetBlockByPosition(long position, bool allowNull = false) + { + return GetBlockByIndex((int)(position / BlockSize)); + } + + /// + /// 获取指定区块下标的区块数据 + /// + /// 区块下标 + /// 是否允许为空 + /// 区块数据 + private BlockMeta GetBlockByIndex(int index, bool allowNull = false) + { + Guard.Requires(index < storage.Length); + return storage[index] ?? (allowNull ? null : (storage[index] = CreateBlock(BlockSize, index))); + } + + /// + /// GC回收时 + /// + ~MemoryStorage() + { + Dispose(!Disabled); + } + + /// + /// 释放内存存储 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 创建缓冲区 + /// + /// 缓冲区 + private BlockMeta CreateBlock(int blockSize, int index) + { + return new BlockMeta + { + BlockIndex = index, + StartOffset = index * blockSize, + EndOffset = (index + 1) * blockSize, + Storage = CreateBuffer(blockSize) + }; + } + + /// + /// 创建缓冲区 + /// + /// 缓冲区 + protected virtual ArraySegment CreateBuffer(int blockSize) + { + return new ArraySegment(new byte[blockSize]); + } + + /// + /// 释放缓冲区 + /// + /// 区块元数据 + private void ReleaseBlock(BlockMeta blockMeta) + { + if (blockMeta != null) + { + ReleaseBuffer(blockMeta.Storage); + } + } + + /// + /// 释放缓冲区 + /// + /// 缓冲大小 + protected virtual void ReleaseBuffer(ArraySegment buffer) + { + + } + + /// + /// 保障存储块满足存储条件 + /// + /// 总共需要占用的空间 + private void EnsureStorageBlock(long value) + { + AssertMemoryUseable(value); + + if (value <= 0) + { + return; + } + + var minBlockCount = (int) ((value / BlockSize) + ((value % BlockSize) == 0 ? 0 : 1)); + if (storage.Length >= minBlockCount) + { + return; + } + + var newStorage = new BlockMeta[GetPrime(minBlockCount)]; + Array.Copy(storage, 0, newStorage, 0, storage.Length); + storage = newStorage; + } + + /// + /// 释放内存存储 + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!disposing || Disabled) + { + return; + } + + if (locker != null) + { + locker.Dispose(); + locker = null; + } + + Disabled = true; + foreach (var block in storage) + { + ReleaseBlock(block); + } + storage = null; + } + + /// + /// 计算规定值最近的二的次幂的容量 + /// + /// 规定值 + /// 容量 + private static int GetPrime(int min) + { + min = Math.Max(0, min); + + var result = 8192; + for (var i = 2; i < int.MaxValue; i = i << 1) + { + if (i >= min) + { + result = i; + break; + } + } + + return result; + } + + /// + /// 断言是否已经被释放 + /// + private void AssertDisabled() + { + if (Disabled) + { + throw new ObjectDisposedException(null, "[" + GetType() + "] Stream is closed."); + } + } + + /// + /// 断言内存使用 + /// + /// 内存占用 + private void AssertMemoryUseable(long value) + { + if (value > MaxMemoryUsable) + { + if (MaxMemoryUsable >= 1048576) + { + throw new OutOfMemoryException("Memory exceeds usage limit " + (MaxMemoryUsable / 1048576) + " MB"); + } + + if (MaxMemoryUsable >= 1024) + { + throw new OutOfMemoryException("Memory exceeds usage limit " + (MaxMemoryUsable / 1024) + " KB"); + } + + throw new OutOfMemoryException("Memory exceeds usage limit " + MaxMemoryUsable + " bit"); + } + } + } +} diff --git a/src/CatLib.Core/Support/Stream/StorageStream.cs b/src/CatLib.Core/Support/Stream/StorageStream.cs new file mode 100644 index 0000000..d3c5794 --- /dev/null +++ b/src/CatLib.Core/Support/Stream/StorageStream.cs @@ -0,0 +1,301 @@ +/* + * This file is part of the CatLib package. + * + * (c) Yu Bin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Document: http://catlib.io/ + */ + +using System; +using System.IO; + +namespace CatLib +{ + /// + /// 存储数据流 + /// + public class StorageStream : Stream + { + /// + /// 当前游标所处的位置 + /// + private long position; + + /// + /// 是否已经被释放了 + /// + private bool disabled; + + /// + /// 是否是可写的 + /// + private readonly bool writable; + + /// + /// 存储数据 + /// + private readonly IStorage storage; + + /// + /// 偏移量 + /// + public override long Position + { + get + { + AssertDisabled(); + return position; + } + set + { + Seek(value, SeekOrigin.Begin); + } + } + + /// + /// 数据的长度 + /// + public override long Length + { + get + { + AssertDisabled(); + return storage.Length; + } + } + + /// + /// 是否是可以写入数据的 + /// + public override bool CanWrite + { + get { return !Disposed && writable; } + } + + /// + /// 是否可以进行游标偏移 + /// + public override bool CanSeek + { + get { return !Disposed; } + } + + /// + /// 是否可以读取数据 + /// + public override bool CanRead + { + get { return !Disposed; } + } + + /// + /// 是否已经被释放 + /// + protected bool Disposed + { + get { return disabled || storage.Disabled; } + } + + /// + /// 存储数据流 + /// + /// 单个内存块的分块 + /// 是否是可写的 + /// 锁超时时间 + public StorageStream(IStorage storage, bool writable = true, int timeout = 1000) + { + Guard.Requires(storage != null); + + if (storage.Disabled) + { + throw new ObjectDisposedException("storage", "Storage is disposed"); + } + + this.storage = storage; + this.writable = writable; + position = 0; + disabled = false; + + if (writable) + { + if (!storage.Locker.TryEnterWriteLock(timeout)) + { + throw GetOccupyException(); + } + } + else + { + if (!storage.Locker.TryEnterReadLock(timeout)) + { + throw GetOccupyException(); + } + } + } + + /// + /// GC回收时 + /// + ~StorageStream() + { + Dispose(!disabled); + } + + /// + /// 偏移游标到指定位置 + /// + /// 偏移量 + /// 偏移方向 + /// 新的位置 + public override long Seek(long offset, SeekOrigin origin) + { + AssertDisabled(); + + long tempPosition; + switch (origin) + { + case SeekOrigin.Begin: + { + tempPosition = offset; + break; + } + case SeekOrigin.Current: + { + tempPosition = unchecked(position + offset); + + break; + } + case SeekOrigin.End: + { + tempPosition = unchecked(Length + offset); + break; + } + default: + throw new ArgumentException("Unknow SeekOrigin"); + } + + if (tempPosition < 0) + { + throw new IOException("seek position less than 0"); + } + + if (tempPosition > Length) + { + throw new IOException("seek position is large then length(" + Length + ")"); + } + + position = tempPosition; + return position; + } + + /// + /// 写入数据 + /// + /// 需要写入的字节流 + /// 字节流的起始位置 + /// 需要写入的长度 + public override void Write(byte[] buffer, int offset, int count) + { + AssertWritable(); + AssertDisabled(); + storage.Write(buffer, offset, count, position); + position += count; + } + + /// + /// 读取数据到指定缓冲区 + /// + /// 指定缓冲区 + /// 缓冲区的起始位置 + /// 需要读取的长度 + /// 实际读取的长度 + public override int Read(byte[] buffer, int offset, int count) + { + AssertDisabled(); + var read = storage.Read(buffer, offset, count, position); + position += read; + return read; + } + + /// + /// 设定长度 + /// + /// 新的长度值 + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// 清除当前流的缓冲区 + /// + public override void Flush() + { + // 只有存在数据落地或者转移的情况下此函数才有效 + // 由于是内存缓存,所以这里我们忽略这个函数 + } + + /// + /// 获取线程占用异常 + /// + /// 异常 + protected IOException GetOccupyException() + { + return new IOException("The resource is already occupied by other threads"); + } + + /// + /// 释放资源 + /// + /// 是否进行释放 + protected override void Dispose(bool disposing) + { + try + { + if (!disposing || disabled) + { + return; + } + + disabled = true; + + if (writable) + { + storage.Locker.ExitWriteLock(); + } + else + { + storage.Locker.ExitReadLock(); + } + } + finally + { + base.Dispose(disposing); + } + } + + /// + /// 断言是否已经被释放 + /// + private void AssertDisabled() + { + if (Disposed) + { + throw new ObjectDisposedException(null, "[" + GetType() + "] Stream is closed."); + } + } + + /// + /// 断言是否能够写入 + /// + private void AssertWritable() + { + if (!writable) + { + throw new NotSupportedException("Not supported writable"); + } + } + } +} \ No newline at end of file diff --git a/src/CatLib.Core/Support/Util/StreamExtension.cs b/src/CatLib.Core/Support/Util/StreamExtension.cs new file mode 100644 index 0000000..c0632bd --- /dev/null +++ b/src/CatLib.Core/Support/Util/StreamExtension.cs @@ -0,0 +1,62 @@ +/* + * This file is part of the CatLib package. + * + * (c) Yu Bin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * Document: http://catlib.io/ + */ + +using System; +using System.IO; + +namespace CatLib +{ + /// + /// Stream扩展函数 + /// + public static class StreamExtension + { + /// + /// 默认的缓冲区 + /// + [ThreadStatic] + private static readonly byte[] buffer = new byte[4096]; + + /// + /// 将当前流追加到目标流中 + /// + /// 源数据流 + /// 目标数据流 + /// 总共传输了多少数据 + public static long AppendTo(this Stream source, Stream destination) + { + return source.AppendTo(destination, buffer); + } + + /// + /// 将当前流追加到目标流中 + /// + /// 源数据流 + /// 目标数据流 + /// 所使用的缓冲区 + /// 总共传输了多少数据 + public static long AppendTo(this Stream source, Stream destination, byte[] buffer) + { + Guard.Requires(source != null); + Guard.Requires(destination != null); + + long result = 0; + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + { + destination.Write(buffer, 0, read); + result += read; + } + + return result; + } + } +}