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;
+ }
+ }
+}