Skip to content

Instantly share code, notes, and snippets.

@codeyu
Created September 6, 2016 02:31
Show Gist options
  • Save codeyu/78dc0dd88d1c28d4ed067cff541089c5 to your computer and use it in GitHub Desktop.
Save codeyu/78dc0dd88d1c28d4ed067cff541089c5 to your computer and use it in GitHub Desktop.
LRU cache
void Main()
{
dynamic model = new System.Dynamic.ExpandoObject();
model.IsTransferOut = null;
model.OwnerId = -1;
if(model.IsTransferOut == true)
{
Console.WriteLine(model.IsTransferOut);
}
var t1 = Convert.ToDateTime("2016-05-02");
Console.WriteLine(t1.AddDays(10).CompareTo(DateTime.Now.Date));
const int maxSize = 10;
var maxAge = new TimeSpan(0, 1, 0, 0);
LRU<string, string>.FetchValueDelegate f = null;
var target = new LRU<string, string>(maxSize, maxAge, f);
target.Add("1","a");
target.Add("2","b");
target.Add("3","c");
target.Add("4","d");
Console.WriteLine(target.Count);
//var removeValue = "";
//target.RemoveKey("4", out removeValue);
Console.WriteLine(target.Count);
Console.WriteLine("Get:"+target.Get("1"));
Console.WriteLine("Get:"+target.Get("4"));
Console.WriteLine("Get:"+target.Get("3"));
Console.WriteLine("Get:"+target.Get("4"));
target.Add("5","e");
target.Add("6","f");
target.Add("7","g");
target.Add("8","h");
target.Add("9","i");
target.Add("10","j");
target.Add("11","k");
target.Add("12","L");
target.Add("12","L");
Console.WriteLine("Get:"+target.Get("2"));
}
// This class implements an LRU cache of values. It keeps a bounded set of values and will
// flush "old" values
internal class LRU<TKey, TValue> : IEnumerable<KeyValuePair<TKey,TValue>> where TKey : class
{
// Delegate type for fetching the value associated with a given key.
public delegate TValue FetchValueDelegate(TKey key);
// The following machinery is used to notify client objects when a key and its value
// is being flushed from the cache.
// The client's event handler is called after the key has been removed from the cache,
// but when the cache is in a consistent state so that other methods on the cache may freely
// be invoked.
public class FlushEventArgs : EventArgs
{
private readonly TKey key;
private readonly TValue value;
public FlushEventArgs(TKey k, TValue v)
{
key = k;
value = v;
}
public TKey Key
{
get { return key; }
}
public TValue Value
{
get { return value; }
}
}
public event EventHandler<FlushEventArgs> RaiseFlushEvent;
private long nextGeneration = 0;
private long generationToFree = 0;
private readonly TimeSpan requiredFreshness;
// We want this to be a reference type so that we can update the values in the cache
// without having to call AddOrUpdate, which is a nuisance
private class TimestampedValue
{
public readonly DateTime WhenLoaded;
public readonly TValue Value;
public long Generation;
public TimestampedValue(LRU<TKey,TValue> l, TValue v)
{
Generation = Interlocked.Increment(ref l.nextGeneration);
Value = v;
WhenLoaded = DateTime.UtcNow;
}
}
private readonly ConcurrentDictionary<TKey, TimestampedValue> cache;
readonly FetchValueDelegate fetcher;
public int Count { get { return cache.Count; } }
public int MaximumSize { get; private set; }
/// <summary>
/// Creates a new LRU cache.
/// </summary>
/// <param name="maxSize">Maximum number of entries to allow.</param>
/// <param name="maxAge">Maximum age of an entry.</param>
/// <param name="f"></param>
public LRU(int maxSize, TimeSpan maxAge, FetchValueDelegate f)
{
if (maxSize <= 0)
{
throw new ArgumentOutOfRangeException("maxSize", "LRU maxSize must be greater than 0");
}
MaximumSize = maxSize;
requiredFreshness = maxAge;
fetcher = f;
cache = new ConcurrentDictionary<TKey, TimestampedValue>();
}
public void Add(TKey key, TValue value)
{
AdjustSize();
var result = new TimestampedValue(this, value);
cache.AddOrUpdate(key, result, (k, o) => result);
}
public bool ContainsKey(TKey key)
{
TimestampedValue ignore;
return cache.TryGetValue(key, out ignore);
}
public bool RemoveKey(TKey key, out TValue value)
{
value = default(TValue);
TimestampedValue tv;
if (!cache.TryRemove(key, out tv)) return false;
value = tv.Value;
return true;
}
public void Clear()
{
foreach (var pair in cache)
{
var args = new FlushEventArgs(pair.Key, pair.Value.Value);
EventHandler<FlushEventArgs> handler = RaiseFlushEvent;
if (handler == null) continue;
handler(this, args);
}
cache.Clear();
}
public bool TryGetValue(TKey key, out TValue value)
{
TimestampedValue result;
value = default(TValue);
if (cache.TryGetValue(key, out result))
{
result.Generation = Interlocked.Increment(ref nextGeneration);
var age = DateTime.UtcNow.Subtract(result.WhenLoaded);
if (age > requiredFreshness)
{
if (!cache.TryRemove(key, out result)) return false;
if (RaiseFlushEvent == null) return false;
var args = new FlushEventArgs(key, result.Value);
RaiseFlushEvent(this, args);
return false;
}
value = result.Value;
}
else
{
return false;
}
return true;
}
public TValue Get(TKey key)
{
TValue value;
if (TryGetValue(key, out value)) return value;
if (fetcher == null) return value;
value = fetcher(key);
Add(key, value);
return value;
}
private void AdjustSize()
{
while (cache.Count >= MaximumSize)
{
long generationToDelete = Interlocked.Increment(ref generationToFree);
KeyValuePair<TKey, TimestampedValue> entryToFree =
cache.FirstOrDefault(kvp => kvp.Value.Generation == generationToDelete);
if (entryToFree.Key == null) continue;
TKey keyToFree = entryToFree.Key;
TimestampedValue old;
if (!cache.TryRemove(keyToFree, out old)) continue;
if (RaiseFlushEvent == null) continue;
var args = new FlushEventArgs(keyToFree, old.Value);
RaiseFlushEvent(this, args);
}
}
#region Implementation of IEnumerable
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return cache.Select(p => new KeyValuePair<TKey, TValue>(p.Key, p.Value.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment