/// <summary> /// <see cref="CachedPageStorage"/> constructor. /// </summary> /// <param name="baseStorage">The base <see cref="IPageStorage"/>.</param> /// <param name="cacheWriteMode">The <see cref="CacheWriteMode"/> that determines when write operations are /// sent to the <paramref name="baseStorage"/>.</param> /// <param name="cachedPageCapacity">The maximum number of pages to store in cache at once.</param> /// <param name="leaveBaseStorageOpen">Should the <paramref name="baseStorage"/> remain open (non-disposed) /// after this <see cref="CachedPageStorage"/> is disposed? If false, then when <see cref="Dispose"/> /// is called, the <paramref name="baseStorage"/> will also be disposed.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="baseStorage"/> is null.</exception> /// <exception cref="ArgumentException">Thrown if the <paramref name="baseStorage"/>'s <see cref="IPageStorage.PageSize"/> /// is greater than <see cref="int.MaxValue"/>, or if <paramref name="cacheWriteMode"/> is not equal to /// <see cref="CacheWriteMode.ReadOnly"/> when the <paramref name="baseStorage"/> is read-only.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="cachedPageCapacity"/> is less /// than zero.</exception> /// <remarks> /// The application should expect that <paramref name="cachedPageCapacity"/> full pages will be loaded into /// memory at any given moment. The amount of memory required depends on the <see cref="IPageStorage.PageSize"/> /// of the <paramref name="baseStorage"/>. If the page size is very high, and so is <paramref name="cachedPageCapacity"/>, /// then there may not be enough memory for the cache. If allocation fails due to an <see cref="OutOfMemoryException"/>, /// then some pages may be evicted from cache, or cache may be bypassed entirely (by writing to and reading from the /// base <see cref="PageStorage"/> directly, if necessary). /// </remarks> public CachedPageStorage(IPageStorage baseStorage, CacheWriteMode cacheWriteMode, int cachedPageCapacity, bool leaveBaseStorageOpen) { if (baseStorage == null) { throw new ArgumentNullException(nameof(baseStorage)); } if (baseStorage.PageSize > Int32.MaxValue) { throw new ArgumentException("Cannot use " + nameof(CachedPageStorage) + " for " + nameof(IPageStorage) + "s with page sizes greater than " + nameof(Int32) + "." + nameof(Int32.MaxValue) + ".", nameof(baseStorage)); } if (baseStorage.IsReadOnly && cacheWriteMode != CacheWriteMode.ReadOnly) { throw new ArgumentException("When the " + nameof(IPageStorage) + " is read-only, the " + nameof(CacheWriteMode) + " must be " + nameof(CacheWriteMode.ReadOnly) + ".", nameof(cacheWriteMode)); } if (cachedPageCapacity < 0) { throw new ArgumentOutOfRangeException(nameof(cachedPageCapacity), "The page cache capacity cannot be less than zero."); } this.PageStorage = baseStorage; this.CachedPageCapacity = cachedPageCapacity; this.cachedPages = new Dictionary <long, CachedPage>(cachedPageCapacity); this.Mode = cacheWriteMode; this.WillLeaveBasePageStorageOpen = leaveBaseStorageOpen; }
private void ResetInner(int currentSize, int currentOffset) { CountOfBackedDataSet = _countFetcher(); Size = Math.Min(currentSize, CountOfBackedDataSet); Offset = Math.Max(0, Math.Min(CountOfBackedDataSet - Size, currentOffset)); _pageStorage = _pageStoreFactory(CountOfBackedDataSet).AssignTo(_serialPageStore); }
/// <summary> /// Attempts to create an empty <see cref="StorageDictionary{TKey, TValue}"/> using an <see cref="IPageStorage"/>. /// </summary> /// <param name="pageStorage">The <see cref="IPageStorage"/> on which the <see cref="StorageDictionary{TKey, TValue}"/> /// will store its data.</param> /// <param name="keySerializer"><see cref="ISerializer{T}"/> that serializes and deserializes the keys.</param> /// <param name="valueSerializer"><see cref="ISerializer{T}"/> that serializes and deserializes the values.</param> /// <param name="pageIndex">Assigned to the index of the metadata page that the application must remember so that it /// may load the <see cref="StorageDictionary{TKey, TValue}"/> in the future. See <see cref="PageIndex"/>.</param> /// <returns>True if creation was successful, false if allocation failed.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="pageStorage"/>, <paramref name="keySerializer"/>, /// or <paramref name="valueSerializer"/> is null.</exception> /// <exception cref="InvalidOperationException">Thrown if <paramref name="pageStorage"/> is read-only.</exception> /// <exception cref="ArgumentException">Thrown if the <paramref name="pageStorage"/>'s <see cref="IPageStorage.PageSize"/> /// is less than the very minimum required size (see <see cref="GetVeryMinRequiredPageSize(long, long)"/>).</exception> /// <remarks> /// <para> /// This method will only initialize the data stored on the <paramref name="pageStorage"/>. To obtain the /// <see cref="StorageDictionary{TKey, TValue}"/> instance, use the <see cref="Load(IPageStorage, ISerializer{TKey}, ISerializer{TValue}, long, bool, int, long)"/> /// method (and remember to call <see cref="StorageDictionary{TKey, TValue}.Dispose()"/> when finished /// using it). /// </para> /// <para> /// Note that this method will inflate the <paramref name="pageStorage"/> if it is at full capacity and not fixed-capacity. /// If inflation is required but the capacity is fixed, or if inflation fails, false will be returned. /// </para> /// </remarks> /// <seealso cref="Load(IPageStorage, ISerializer{TKey}, ISerializer{TValue}, long, bool, int, long)"/> public static bool TryCreate(IPageStorage pageStorage, ISerializer <TKey> keySerializer, ISerializer <TValue> valueSerializer, out long pageIndex) { if (pageStorage == null) { throw new ArgumentNullException(nameof(pageStorage)); } if (keySerializer == null) { throw new ArgumentNullException(nameof(keySerializer)); } if (valueSerializer == null) { throw new ArgumentNullException(nameof(valueSerializer)); } if (pageStorage.IsReadOnly) { throw new InvalidOperationException("Cannot create a " + nameof(StorageDictionary <TKey, TValue>) + " on a read-only " + nameof(IPageStorage) + "."); } if (pageStorage.PageSize < GetVeryMinRequiredPageSize(keySerializer.DataSize, valueSerializer.DataSize)) { throw new ArgumentException("The page size of the specified " + nameof(IPageStorage) + " is too small.", nameof(pageStorage)); } if (pageStorage.AllocatedPageCount == pageStorage.PageCapacity) { if (!pageStorage.IsCapacityFixed) { long got = pageStorage.TryInflate(1, null, new CancellationToken(false)); if (got < 0) { //Cannot allocate, capacity remains full pageIndex = -1; return(false); } } else { //Capacity is full and we cannot inflate it pageIndex = -1; return(false); } } if (pageStorage.TryAllocatePage(out pageIndex)) { using (StorageDictionary <TKey, TValue> dict = new StorageDictionary <TKey, TValue>(pageStorage, keySerializer, valueSerializer, pageIndex, false, 0, 256 /*not used for creation, but must be >0*/)) { dict.BTree.InitializeEmpty(); dict.CachedPageStorage.Flush(); } return(true); } else { pageIndex = -1; return(false); } }
private StorageDictionary(IPageStorage pageStorage, ISerializer <TKey> keySerializer, ISerializer <TValue> valueSerializer, long pageIndex, bool isReadOnly, int cachePageCount, long maxMoveCount) { this.CachedPageStorage = new CachedPageStorage(pageStorage, isReadOnly ? CachedPageStorage.CacheWriteMode.ReadOnly : CachedPageStorage.CacheWriteMode.WriteBack, cachePageCount, true); this.KeySerializer = keySerializer ?? throw new ArgumentNullException(nameof(keySerializer)); this.ValueSerializer = valueSerializer ?? throw new ArgumentNullException(nameof(valueSerializer)); this.PageIndex = pageIndex; this.IsReadOnly = isReadOnly; this.MaxMovePairCount = maxMoveCount; this.BTree = new StorageDictionaryBTree <TKey, TValue>(this); }
public MockBTree(IPageStorage pageStorage, ISerializer <long> keySerializer, ISerializer <string> valueSerializer, long maxMovePairCount = 32) : base(pageStorage, keySerializer, valueSerializer, maxMovePairCount) { if (pageStorage.EntryPageIndex == null) { if (pageStorage.TryAllocatePage(out long index)) { pageStorage.EntryPageIndex = index; this.Count = 0; this.RootPageIndex = null; } else { throw new ArgumentException("Failed to allocate the entry page index on the " + nameof(IPageStorage) + ".", nameof(pageStorage)); } } }
/// <summary> /// <see cref="BTree{TKey, TValue}"/> constructor. /// </summary> /// <param name="pageStorage">The <see cref="IPageStorage"/> on which this <see cref="BTree{TKey, TValue}"/> is /// stored or will be stored.</param> /// <param name="keySerializer">The <see cref="ISerializer{T}"/> that will be used to serialize and /// deserialize the keys.</param> /// <param name="valueSerializer">The <see cref="ISerializer{T}"/> that will be used to serialize and /// deserialize the values.</param> /// <param name="maxMovePairCount">The maximum size of the internal buffer that may be used to move key-value /// pairs during certain operations such as insertions, measured in the number of key-value pairs. Must be /// at least one.</param> /// <remarks> /// <para> /// Note that the <paramref name="pageStorage"/>'s pages must be sufficiently large to store a <see cref="BTreeNode{TKey, TValue}"/> /// with several key-value pairs. The very minimum key-value pair capacity is <see cref="BTreeNode{TKey, TValue}.VeryMinKeyValuePairCapacity"/>, /// but most applications should use a significantly larger capacity. To determine the key-value pair capacity for a given /// page size, see <see cref="BTreeNode{TKey, TValue}.GetRequiredPageSize(long, long, long)"/>. The 'key size' and /// 'value size' are defined by the <see cref="ISerializer{T}.DataSize"/> of the <paramref name="keySerializer"/> /// and <paramref name="valueSerializer"/>. /// </para> /// <para> /// During some operations, such as insertions, some <see cref="BTreeNode{TKey, TValue}"/>s may have to move around some /// of their key-value pairs. In most cases, it will be more efficient if several key-value pairs are moved at once /// (rather than one at a time). The <paramref name="maxMovePairCount"/> argument defines the maximum number of /// key-value pairs that will be moved at once. A larger number should result in improved speed, but at the cost /// of increased runtime memory. The size, in bytes, of the internal buffer is approximately the size of the /// binary-serialized keys and values (see <see cref="ISerializer{T}.DataSize"/> for <paramref name="keySerializer"/> /// and <paramref name="valueSerializer"/>) plus eight bytes of overhead, all multiplied by the <paramref name="maxMovePairCount"/> /// argument. /// </para> /// </remarks> /// <exception cref="ArgumentNullException">Thrown if <paramref name="pageStorage"/>, <paramref name="keySerializer"/>, /// or <paramref name="valueSerializer"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="maxMovePairCount"/> is less than one.</exception> /// <exception cref="ArgumentException">Thrown if the <paramref name="pageStorage"/>'s pages are too small to contain /// a <see cref="BTreeNode{TKey, TValue}"/> with <see cref="BTreeNode{TKey, TValue}.VeryMinKeyValuePairCapacity"/> /// key-value pairs. See <see cref="BTreeNode{TKey, TValue}.GetKeyValuePairCapacityForPageSize(long, long, long)"/> /// and <see cref="BTreeNode{TKey, TValue}.GetRequiredPageSize(long, long, long)"/>.</exception> public BTree(IPageStorage pageStorage, ISerializer <TKey> keySerializer, ISerializer <TValue> valueSerializer, long maxMovePairCount = 32) { this.PageStorage = pageStorage ?? throw new ArgumentNullException(nameof(pageStorage)); this.KeySerializer = keySerializer ?? throw new ArgumentNullException(nameof(keySerializer)); this.ValueSerializer = valueSerializer ?? throw new ArgumentNullException(nameof(valueSerializer)); this.MaxMoveKeyValuePairCount = maxMovePairCount; long veryMinRequiredPageSize = BTreeNode <TKey, TValue> .GetRequiredPageSize(keySerializer.DataSize, valueSerializer.DataSize, BTreeNode <TKey, TValue> .VeryMinKeyValuePairCapacity); if (pageStorage.PageSize < veryMinRequiredPageSize) { throw new ArgumentException("The provided " + nameof(IPageStorage) + " has a page size that is insufficient to store a " + nameof(BTreeNode <TKey, TValue>) + " using the specified key and value serializers."); } if (maxMovePairCount < 1) { throw new ArgumentOutOfRangeException(nameof(maxMovePairCount)); } }
/// <summary> /// Loads a <see cref="StorageDictionary{TKey, TValue}"/> from an <see cref="IPageStorage"/>. /// </summary> /// <param name="pageStorage">The <see cref="IPageStorage"/> from which to load. This /// <see cref="StorageDictionary{TKey, TValue}"/> will <em>not</em> dispose it (because it /// is possible that other APIs may also be using the <see cref="IPageStorage"/>).</param> /// <param name="keySerializer"><see cref="ISerializer{T}"/> that serializes and deserializes the keys.</param> /// <param name="valueSerializer"><see cref="ISerializer{T}"/> that serializes and deserializes the values.</param> /// <param name="pageIndex">The index of the page on the <paramref name="pageStorage"/> that contains the /// metadata for the <see cref="StorageDictionary{TKey, TValue}"/>. This was determined during creation via /// the <see cref="TryCreate(IPageStorage, ISerializer{TKey}, ISerializer{TValue}, out long)"/> /// method.</param> /// <param name="isReadOnly">Should the <see cref="StorageDictionary{TKey, TValue}"/> be loaded as read-only? Must be true /// if the <paramref name="pageStorage"/> is read-only.</param> /// <param name="cachePageCount">The maximum number of pages to store in cache. May be zero, in which case caching /// will not be used. If <paramref name="pageStorage"/> already provides caching, then this should be zero.</param> /// <param name="maxMoveCount">The maximum number of key-value pairs to move at once during certain operations. /// Larger values may improve operation speed, but cost more memory. The memory cost is equivalent to the /// <see cref="ISerializer{T}.DataSize"/> of the <paramref name="keySerializer"/> and the <paramref name="valueSerializer"/>, /// plus 8-bytes of header data, all multiplied by this argument.</param> /// <returns>The loaded <see cref="StorageDictionary{TKey, TValue}"/>.</returns> /// <exception cref="ArgumentNullException">Thrown if any argument is null.</exception> /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="cachePageCount"/> is less than zero or /// <paramref name="maxMoveCount"/> is less than one.</exception> /// <exception cref="ArgumentException">Thrown if <paramref name="pageStorage"/> is read-only but /// <paramref name="isReadOnly"/> is false, or if the page on <paramref name="pageStorage"/> at /// <paramref name="pageIndex"/> is not allocated or does not exist, or if the size of the pages on /// the <paramref name="pageStorage"/> is less than the very minimum valid page size.</exception> public static StorageDictionary <TKey, TValue> Load(IPageStorage pageStorage, ISerializer <TKey> keySerializer, ISerializer <TValue> valueSerializer, long pageIndex, bool isReadOnly, int cachePageCount = 8, long maxMoveCount = 256) { if (pageStorage == null) { throw new ArgumentNullException(nameof(pageStorage)); } if (keySerializer == null) { throw new ArgumentNullException(nameof(keySerializer)); } if (valueSerializer == null) { throw new ArgumentNullException(nameof(valueSerializer)); } if (cachePageCount < 0) { throw new ArgumentOutOfRangeException(nameof(cachePageCount)); } if (maxMoveCount < 1) { throw new ArgumentOutOfRangeException(nameof(maxMoveCount), nameof(maxMoveCount) + " cannot be less than one."); } if (pageStorage.IsReadOnly && !isReadOnly) { throw new ArgumentException(nameof(isReadOnly), "If the " + nameof(IPageStorage) + " is read-only, the " + nameof(isReadOnly) + " argument must be true."); } if (!pageStorage.IsPageOnStorage(pageIndex)) { throw new ArgumentException("The specified index does not refer to a page that exists on the " + nameof(IPageStorage) + ".", nameof(pageIndex)); } if (!pageStorage.IsPageAllocated(pageIndex)) { throw new ArgumentException("The specified index does not refer to a page that is allocated on the " + nameof(IPageStorage) + ".", nameof(pageIndex)); } if (pageStorage.PageSize < GetVeryMinRequiredPageSize(keySerializer.DataSize, valueSerializer.DataSize)) { throw new ArgumentException("The page size of the specified " + nameof(IPageStorage) + " is too small.", nameof(pageStorage)); } return(new StorageDictionary <TKey, TValue>(pageStorage, keySerializer, valueSerializer, pageIndex, isReadOnly, cachePageCount, maxMoveCount)); }