From 8baf7c9f4985978944b6728341f6d8923bb447a3 Mon Sep 17 00:00:00 2001 From: haraldwolff Date: Fri, 1 Jul 2022 21:12:51 +0200 Subject: [PATCH] Redesigned Cache, Added KeyedCache --- ln.collections.test/CacheTests.cs | 46 ++++ .../ln.collections.test.csproj | 4 +- ln.collections/Cache.cs | 254 +++++++++++++----- ln.collections/ln.collections.csproj | 5 +- 4 files changed, 243 insertions(+), 66 deletions(-) create mode 100644 ln.collections.test/CacheTests.cs diff --git a/ln.collections.test/CacheTests.cs b/ln.collections.test/CacheTests.cs new file mode 100644 index 0000000..b8477b8 --- /dev/null +++ b/ln.collections.test/CacheTests.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; +using NUnit.Framework; + +namespace ln.collections.test +{ + [TestFixture] + public class CacheTests + { + + [Test] + public void Test_Cache_1() + { + Cache cache = new Cache(TimeSpan.FromSeconds(1)); + + for (int n = 0; n < 10; n++) + cache.Add(n, n.ToString()); + + cache.Cleanup(); + Assert.AreEqual(10, cache.Count); + + Thread.Sleep(1000); + cache.Cleanup(); + Assert.AreEqual(0, cache.Count); + + for (int n = 0; n < 10; n++) + { + cache.Add(n, n.ToString()); + Thread.Sleep(90); + } + + Thread.Sleep(10); + + for (int n = 10; n > 0; n--) + { + cache.Cleanup(); + Assert.AreEqual(n, cache.Count); + Thread.Sleep(90); + } + + + Assert.Pass(); + } + + } +} \ No newline at end of file diff --git a/ln.collections.test/ln.collections.test.csproj b/ln.collections.test/ln.collections.test.csproj index d363dd0..241071c 100644 --- a/ln.collections.test/ln.collections.test.csproj +++ b/ln.collections.test/ln.collections.test.csproj @@ -4,7 +4,9 @@ false - net5.0 + net5.0;net6.0 + + default diff --git a/ln.collections/Cache.cs b/ln.collections/Cache.cs index 685626a..20e5d00 100644 --- a/ln.collections/Cache.cs +++ b/ln.collections/Cache.cs @@ -1,95 +1,223 @@ using System; +using System.Collections.Generic; +using System.Linq; + namespace ln.collections { - public class Cache + public class Cache { - public int Count => values.Count; + public TimeSpan DefaultLifeTime { get; set; } + private readonly Dictionary _cache = new Dictionary(); - BTree values; - BTree> itemTree; - LinkedList items = new LinkedList(); - - int maxCacheSize = 4096; + public Cache(TimeSpan defaultLifeTime) + { + DefaultLifeTime = defaultLifeTime; + } public Cache() + : this(TimeSpan.FromMinutes(10)) { - values = new BTree(); - itemTree = new BTree>(); - } - public Cache(Comparison compare) - { - values = new BTree(compare); - itemTree = new BTree>(compare); } - public int MaxCacheSize + public void CopyTo(KeyValuePair[] array, int arrayIndex) { - get => maxCacheSize; - set { - maxCacheSize = value; - while (maxCacheSize < values.Count) + lock (this) + { + foreach (var cacheEntry in _cache) { - Forget(); + if (cacheEntry.Value.IsValid) + array[arrayIndex++] = new KeyValuePair(cacheEntry.Key, cacheEntry.Value.Value); } } } - public void Clear() - { - values.Clear(); - itemTree.Clear(); - items.Clear(); - } - - public bool TryGet(K key,out V value) - { - return values.TryGet(key, out value); - } - - public void Forget() - { - lock (this) + public int Count { + get { - Forget(items.FirstItem); - } - } - public void Forget(K key) - { - lock (this) - { - if (itemTree.TryGet(key,out LinkedListItem item)) - Forget(item); + lock (this) { return _cache.Count; } } } - private void Forget(LinkedListItem item) - { - if (item == null) - return; - - values.Remove(item.Value); - itemTree.Remove(item.Value); - items.Remove(item); - } - - public void Ensure(K key,V value) + public bool IsReadOnly => false; + public void Clear() { lock (this) _cache.Clear(); } + public void Cleanup() { lock (this) { - if (!itemTree.TryGet(key, out LinkedListItem item)) + foreach (var key in _cache.Keys.ToArray()) { - item = new LinkedListItem(key); - values.Add(key,value); + if (!_cache[key].IsValid) + _cache.Remove(key); } - Ensure(item); } } - private void Ensure(LinkedListItem item) + public bool Contains(KeyValuePair item) { lock (this) return TryGetValue(item.Key, out TValue value) && object.Equals(item.Value, value); } + public void Add(KeyValuePair item) => Add(item.Key, item.Value); + + public bool Remove(KeyValuePair item) { - if (!item.IsLinked) - itemTree.Add(item.Value, item); - items.Add(item); + lock (this) + { + if (_cache.TryGetValue(item.Key, out Entry entry) && object.Equals(item.Value, entry.Value)) + { + _cache.Remove(item.Key); + return entry.IsValid; + } + + return false; + } + } + + + public void Add(TKey key, TValue value) + { + lock (this) + { + _cache.Add(key, new Entry(this, value)); + } + } + + public bool ContainsKey(TKey key) + { + lock (this) return _cache.ContainsKey(key) && _cache[key].IsValid; + } + public bool Remove(TKey key) + { + lock (this) return _cache.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + lock (this) + { + if (_cache.TryGetValue(key, out Entry entry)) + { + if (entry.IsValid) + { + value = entry.Value; + return true; + } + else + { + _cache.Remove(key); + } + } + } + + value = default(TValue); + return false; + } + + public TValue this[TKey key] + { + get + { + lock (this) + { + if (TryGetValue(key, out TValue value)) + return value; + } + + throw new KeyNotFoundException(); + } + set + { + lock (this) + { + if (_cache.TryGetValue(key, out Entry entry)) + entry.Reset(value); + else + _cache.Add(key, new Entry(this, value)); + } + } + } + + public bool ReValidate(TKey key) + { + lock (this) + { + if (_cache.TryGetValue(key, out Entry entry)) + { + if (entry.IsValid) + { + entry.UpdateTimestamp(); + return true; + } + else + { + _cache.Remove(key); + } + } + + return false; + } + } + + public TValue GetOrCreate(TKey key, Func factory) + { + lock (this) + { + if (!TryGetValue(key, out TValue value)) + { + value = factory(key); + Add(key, value); + } + return value; + } + } + + class Entry + { + private Cache _cache; + public DateTime TimeOut { get; private set; } + public TValue Value { get; private set; } + + public Entry(Cache cache, TValue value) + { + _cache = cache; + Value = value; + UpdateTimestamp(); + } + + public void UpdateTimestamp() => UpdateTimestamp(_cache.DefaultLifeTime); + public void UpdateTimestamp(TimeSpan lifeTime) => TimeOut = DateTime.Now + lifeTime; + public bool IsValid => TimeOut > DateTime.Now; + + public void Reset(TValue newValue) + { + Value = newValue; + UpdateTimestamp(); + } + } + + } + + public abstract class KeyedCache : Cache + { + protected KeyedCache() + { + } + protected KeyedCache(TimeSpan defaultTimeOut) + :base(defaultTimeOut) + { + } + + protected abstract TKey GetKey(TValue value); + + public void Add(TValue value) => Add(GetKey(value), value); + public bool RemoveValue(TValue value) + { + TKey key = GetKey(value); + lock (this) + { + if (TryGetValue(key, out TValue cachedValue) && object.Equals(cachedValue, value)) + { + return Remove(key); + } + } + + return false; } } diff --git a/ln.collections/ln.collections.csproj b/ln.collections/ln.collections.csproj index 086d267..781a9d9 100644 --- a/ln.collections/ln.collections.csproj +++ b/ln.collections/ln.collections.csproj @@ -7,8 +7,9 @@ l--n.de 0.0.1.1 0.0.1.1 - 0.1.7 - net5.0 + 0.2.0 + net5.0;net6.0 + default