{"id":17030031,"url":"https://github.com/rob-blackbourn/jetblack.caching","last_synced_at":"2026-02-28T00:40:27.863Z","repository":{"id":81915622,"uuid":"41902541","full_name":"rob-blackbourn/JetBlack.Caching","owner":"rob-blackbourn","description":"Some caching code in C# including a circular buffer and a persistent dictionary","archived":false,"fork":false,"pushed_at":"2015-09-12T13:42:00.000Z","size":228,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-09T20:16:03.506Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rob-blackbourn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-04T07:06:37.000Z","updated_at":"2020-06-04T15:25:37.000Z","dependencies_parsed_at":"2023-04-01T07:47:27.377Z","dependency_job_id":null,"html_url":"https://github.com/rob-blackbourn/JetBlack.Caching","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rob-blackbourn/JetBlack.Caching","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rob-blackbourn%2FJetBlack.Caching","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rob-blackbourn%2FJetBlack.Caching/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rob-blackbourn%2FJetBlack.Caching/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rob-blackbourn%2FJetBlack.Caching/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rob-blackbourn","download_url":"https://codeload.github.com/rob-blackbourn/JetBlack.Caching/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rob-blackbourn%2FJetBlack.Caching/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275779903,"owners_count":25527345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-18T02:00:09.552Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-14T08:03:33.775Z","updated_at":"2025-09-18T13:53:10.528Z","avatar_url":"https://github.com/rob-blackbourn.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JetBlack.Caching\n\nThis project contains code I found useful for caching problems. It includes\ncircular buffers, timout dictionaries, and persistent dictionaries.\n\n## Circular Buffer\n\nA circular buffer is buffer of fixed length. When the buffer is full, subsequent\nwrites wrap, overwriting previous values. It is useful when you are only\ninterested in the most recent values.\n\n### Usage Example\n\nTo whet your appetite, here are some examples.\n\n```cs\n// Create a buffer with a capacity of 5 items.\nvar buffer = new CircularBuffer\u003clong\u003e(5);\n\n// Add three.\nforeach (var i in Enumerable.Range(1, 3))\n    buffer.Enqueue(i);\nDebug.WriteLine(buffer);\n// Capacity=5, Count=3, Buffer=[1,2,3]\n\n// Add three more.\nforeach (var i in Enumerable.Range(4, 3))\n    buffer.Enqueue(i);\nDebug.WriteLine(buffer);\n// Capacity=5, Count=5, Buffer=[2,3,4,5,6]\n\n// Remove the third.\nvar value = buffer[3];\nbuffer.RemoveAt(3);\nDebug.WriteLine(buffer);\n// Capacity=5, Count=4, Buffer=[2,3,4,6]\n\n// Re-insert it.\nbuffer.Insert(3, value);\nDebug.WriteLine(buffer);\n// Capacity=5, Count=5, Buffer=[2,3,4,5,6]\n\n// Dequeue.\nDebug.Print(\"Value = {0}\", buffer.Dequeue());\n// Value = 2\nDebug.WriteLine(buffer);\n// Capacity=5, Count=4, Buffer=[3,4,5,6]\n\n// Increase the capacity.\nbuffer.Capacity = 6;\nDebug.WriteLine(buffer);\n// Capacity=6, Count=4, Buffer=[3,4,5,6]\n\n// Add three more.\nforeach (var i in Enumerable.Range(7, 3))\n    buffer.Enqueue(i);\nDebug.WriteLine(buffer);\n// Capacity=6, Count=6, Buffer=[4,5,6,7,8,9]\n\n// Reduce the capacity.\nbuffer.Capacity = 4;\nDebug.WriteLine(buffer);\n// Capacity=4, Count=4, Buffer=[4,5,6,7]\n\n// Clear the buffer.\nbuffer.Clear();\nDebug.WriteLine(buffer);\n// Capacity=4, Count=0, Buffer=[]\n```\n\n### Design Goal\n\nThis is a circular buffer I needed as a component for a caching layer. It is\nlargely a blatant ripoff of the many implementations previously published on\nthe web, with a few changes which might prove useful to those with similar\nobjectives.\n\nMy first specific requirement was to model the structure as a queue, so the\nprimary interaction is `Enqueue` and `Dequeue`. As my caching layer has an in\nmemory cache and a persistent cache, I needed the `Enqueue` to return the\noverwritten value (if there was one). Lastly I needed to be able to arbitrarily\nmove things around in the queue, so I could control the order and contents.\n\n### The Interface\n\nAll the obvious candidates are in the interface [`ICircularBuffer\u003cT\u003e`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Generic/ICircularBuffer.cs).\nYou can see the queue style interaction. Also note the enqueue returns the\noverwritten value if one exists (otherwise it will be `default(T)`). The\nindexer methods, `IndexOf`, `InsertAt`, and `RemoveAt` provide the\nmechanism to manipulate the queue directly.\n\nI could have provided item lookups by value rather than index, but these would\nhave still required the indexing operators and I wanted to keep the class small.\nIt should be clear how a derived class, (possibly implementing `IList\u003cT\u003e`)\ncould be trivially implemented.\n\n### The Implementation\n\nThe code for [`CircularBuffer\u003cT\u003e`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Generic/CircularBuffer.cs)\nfollows the traditional circular buffer pattern of declaring a fixed\nlength array, then maintaining an index to the head and tail of the array.\nTypically the size of the buffer is defined by the constructor, but I have\nincluded a Capacity property, to allow more sympathetic subclassing.\n\nThough not strictley necessary it seemed convenient to implement `IEnumerable\u003cT\u003e`.\nThe amount of code required is small, and it provides Linq compatibility at\nlittle extra cost.\n\n### Tests\n\nHere are some simple [`tests`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching.Test/Collections/Generic/CircularBufferFixture.cs) which also demonstrate how to use the class.\n\n## Heap\n\nThis section describes a heap data structure. In this implementation a heap\nmanages a sequential array of data which grows upwards from the bottom.\nBlocks of this array can be allocated, freed, read and written to through\nhandles. The heap attempts to keep itself small by managing a list of free\nblocks that can be reallocated.\n\n### Design\n\nThe primary operations handled by the heap will be memory management: `Allocate`\nand `Deallocate`, and reading and writing: `Read` and `Write`. As will become\nclear later on it is also useful to be able to discover allocations, so one\nless obvious operation `GetAllocatedBlock` is included.\n\nBecause the heap manages its data internally, we need some utility classes. First\na `Handle` through which we can refer to a block that has been allocated. Second\nwe define the `Block` which defines the area in the heap to which the `Handle`\nrefers.\n\n#### Handle\n\nThe [`Handle`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/Handle.cs)\nis irritatingly large for such a trivial data structure. This is\nbecause it implements a couple of interfaces for equality, and a factory class\nfor generating new handles.\n\n#### Block\n\nThe [`Block`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/Block.cs)\nis rather more terse. It has the index offset into the heap, and a\nlength. Both the handle and the block are immutable.\n\n### The Heap Interface\n\nNow we have our basic data structures we can define the interface [`IHeap\u003cT\u003e`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/IHeap.cs).\nAt this stage we can be agnostic to the type of items in the array, although\nmost typically they will be bytes.\n\n### The Heap Manager Interface\n\nThe tricky bit in a heap is managing the free list. As allocations are requested\nand released, discarded blocks are gathered together to be re-allocated. With\nthe interface used here (where access is managed through a handle) the data\nitself may be completely reorganised. This stops the heap growing unnecessarily.\nThere are many algorithms available for this, so I decided to create an\n[`IHeapManager`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/IHeapManager.cs)\nto represent allocation, and allow this part too be pluggable.\n\n### The HeapManager implementation\n\nMy implementation of the heap manager ([`LightweightHeapManager`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/LightweightHeapManager.cs))\nis fairly trivial, but it was sufficient for my purpose. The only optimisation\nit performs is to merge adjacent freed blocks.\n\n### The Heap Implementation\n\nWith most of the heavy lifting done, the [`Heap\u003cT\u003e`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/Heap.cs)\nimplementation looks pretty trivial. The class is abstract, as we have yet to\ndecide how to represent the array.\n\n### The StreamHeap\n\nAs my goal is to create a persistent cache the first step is to model the heap\nwith a byte stream ([`StreamHeap`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/StreamHeap.cs)).\nThere a couple of non-obvious choices here. \n\nThe first is the constructor. There is some confusion over the ownership of the\nstream. Should this class dispose of the stream or not? Is it the owner? I\ncould have passed a flag in the public constructor, but it seemed more natural\nthat the class would own the stream if it created it. This is why I have a\nfactory method for stream construction.\n\nSecond we can see the reason for `GetAllocatedBlock` and `CreateFreeBlock`.\nThis class does the actual reading and writing, so it needs the information\nprovided by the heap to fulfil these duties. It also needs to know about the\nfree block creation, so it can manage the physical space.\n\n### The FileStreamHeap\n\nFinally we can save it to disc with the [`FileStreamHeap`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/FileStreamHeap.cs).\n\nThe same strategy (this time over file ownership) is used, with a factory class\nindicating ownership. \n\n### Tests For the Stream Heap\n\nThere follow some [tests](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching.Test/Collections/Specialized/StreamHeapFixture.cs)\nfor the stream heap. They also demonstrate its usage.\n\n### Test For File Streams\n\nApart from the construction the file stream heap has exactly the same\nfunctionality as the stream heap, so all I [test](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching.Test/Collections/Specialized/FileStreamHeapFixture.cs) here is the ownership.\n\n## Persistent Dictionary\n\nThis describes the implementation of a persistent dictionary. There are a\nnumber of excellent solutions to this online, but most were highly complex.\nThis is a simple implementation which was sufficient for my purpose. It uses\nthe classes described in the Heap section.\n\n### The Cache\n\nFirst we defined the interface for the persistent cache, [`ICache\u003cT\u003e`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/ICache.cs).\nThis follows the traditional CRUD pattern.\n\n### A Cache Implementation\n\nNow we can implement a fairly straightforward cache [`SerializingCache`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/SerializingCache.cs),\nwithout making too many decisions about how it will be used. All we need \nto provide is a heap, a serializer, and a deserializer.\n\nThe `Update` method has some complexity. If the size of the object has changed\nit will need to deallocate the old block and allocate a new one.\n\n### An Example String Cache\n\nA trivial implementation of the serializers could be the following.\n\n```cs\nusing System.Text;\nusing JetBlack.Patterns.Heaps;\n\nnamespace JetBlack.Patterns.Caching\n{\n    public class StringCache : SerializingCache\u003cstring,byte\u003e\n    {\n        public StringCache(IHeap\u003cbyte\u003e heap, Encoding encoding)\n            : base(heap, encoding.GetBytes, encoding.GetString)\n        {\n        }\n\n        public StringCache(IHeap\u003cbyte\u003e heap)\n            : this(heap, Encoding.Default)\n        {           \n        }\n    }\n}\n```\n\n### A Persistant Dictionary\n\nAll we need to do to create a [`PersistentDictionary`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/PersistentDictionary.cs)\nis to wrap a cache with a dictionary implementation. We keep and\nindex of keys to handles to map to the persistent cache.\n\nThe factory class at the top generates the dictionary with binary serializers\nso we can support a large population of possible objects.\n\n## Caching Dictionary\n\nThis section describes an implementation of a local/persistent caching\ndictionary bringing together the classes discussed in the Heap,\nPersistentDictionary, and CircularBuffer sections.\n\n### Design\n\nThe implementation uses an in memory dictionary and a persistent dictionary to\ncreate the [`CachingDictionary\u003cTKey,TValue\u003e`](https://github.com/rob-blackbourn/JetBlack.Caching/blob/master/JetBlack.Caching/Collections/Specialized/CachingDictionary.cs).\nThe recently accessed items remain in the local dictionary, while the less\nused are moved to the persistent store. As older values are accessed they\nare moved back into the local store.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frob-blackbourn%2Fjetblack.caching","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frob-blackbourn%2Fjetblack.caching","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frob-blackbourn%2Fjetblack.caching/lists"}