Redesigned Cache<TKey,TValue>, Added KeyedCache<TKey, TValue>
parent
61a010c666
commit
8baf7c9f49
|
@ -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<int, string> cache = new Cache<int, string>(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,9 @@
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
<TargetFrameworks>net5.0</TargetFrameworks>
|
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||||
|
|
||||||
|
<LangVersion>default</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,95 +1,223 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace ln.collections
|
namespace ln.collections
|
||||||
{
|
{
|
||||||
public class Cache<K,V>
|
public class Cache<TKey, TValue>
|
||||||
{
|
{
|
||||||
public int Count => values.Count;
|
public TimeSpan DefaultLifeTime { get; set; }
|
||||||
|
private readonly Dictionary<TKey, Entry> _cache = new Dictionary<TKey, Entry>();
|
||||||
|
|
||||||
BTree<K, V> values;
|
public Cache(TimeSpan defaultLifeTime)
|
||||||
BTree<K, LinkedListItem<K>> itemTree;
|
{
|
||||||
LinkedList<K> items = new LinkedList<K>();
|
DefaultLifeTime = defaultLifeTime;
|
||||||
|
}
|
||||||
int maxCacheSize = 4096;
|
|
||||||
|
|
||||||
public Cache()
|
public Cache()
|
||||||
|
: this(TimeSpan.FromMinutes(10))
|
||||||
{
|
{
|
||||||
values = new BTree<K, V>();
|
|
||||||
itemTree = new BTree<K, LinkedListItem<K>>();
|
|
||||||
}
|
|
||||||
public Cache(Comparison<K> compare)
|
|
||||||
{
|
|
||||||
values = new BTree<K, V>(compare);
|
|
||||||
itemTree = new BTree<K, LinkedListItem<K>>(compare);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MaxCacheSize
|
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||||
{
|
{
|
||||||
get => maxCacheSize;
|
lock (this)
|
||||||
set {
|
{
|
||||||
maxCacheSize = value;
|
foreach (var cacheEntry in _cache)
|
||||||
while (maxCacheSize < values.Count)
|
|
||||||
{
|
{
|
||||||
Forget();
|
if (cacheEntry.Value.IsValid)
|
||||||
|
array[arrayIndex++] = new KeyValuePair<TKey, TValue>(cacheEntry.Key, cacheEntry.Value.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public int Count {
|
||||||
{
|
get
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
Forget(items.FirstItem);
|
lock (this) { return _cache.Count; }
|
||||||
}
|
|
||||||
}
|
|
||||||
public void Forget(K key)
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
if (itemTree.TryGet(key,out LinkedListItem<K> item))
|
|
||||||
Forget(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Forget(LinkedListItem<K> item)
|
public bool IsReadOnly => false;
|
||||||
{
|
public void Clear() { lock (this) _cache.Clear(); }
|
||||||
if (item == null)
|
public void Cleanup()
|
||||||
return;
|
|
||||||
|
|
||||||
values.Remove(item.Value);
|
|
||||||
itemTree.Remove(item.Value);
|
|
||||||
items.Remove(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Ensure(K key,V value)
|
|
||||||
{
|
{
|
||||||
lock (this)
|
lock (this)
|
||||||
{
|
{
|
||||||
if (!itemTree.TryGet(key, out LinkedListItem<K> item))
|
foreach (var key in _cache.Keys.ToArray())
|
||||||
{
|
{
|
||||||
item = new LinkedListItem<K>(key);
|
if (!_cache[key].IsValid)
|
||||||
values.Add(key,value);
|
_cache.Remove(key);
|
||||||
}
|
}
|
||||||
Ensure(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Ensure(LinkedListItem<K> item)
|
public bool Contains(KeyValuePair<TKey, TValue> item) { lock (this) return TryGetValue(item.Key, out TValue value) && object.Equals(item.Value, value); }
|
||||||
|
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||||
|
|
||||||
|
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||||
{
|
{
|
||||||
if (!item.IsLinked)
|
lock (this)
|
||||||
itemTree.Add(item.Value, item);
|
{
|
||||||
items.Add(item);
|
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<TKey, TValue> factory)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (!TryGetValue(key, out TValue value))
|
||||||
|
{
|
||||||
|
value = factory(key);
|
||||||
|
Add(key, value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Entry
|
||||||
|
{
|
||||||
|
private Cache<TKey, TValue> _cache;
|
||||||
|
public DateTime TimeOut { get; private set; }
|
||||||
|
public TValue Value { get; private set; }
|
||||||
|
|
||||||
|
public Entry(Cache<TKey,TValue> 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<TKey, TValue> : Cache<TKey, TValue>
|
||||||
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
<Company>l--n.de</Company>
|
<Company>l--n.de</Company>
|
||||||
<AssemblyVersion>0.0.1.1</AssemblyVersion>
|
<AssemblyVersion>0.0.1.1</AssemblyVersion>
|
||||||
<FileVersion>0.0.1.1</FileVersion>
|
<FileVersion>0.0.1.1</FileVersion>
|
||||||
<PackageVersion>0.1.7</PackageVersion>
|
<PackageVersion>0.2.0</PackageVersion>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||||
|
<LangVersion>default</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
Loading…
Reference in New Issue