Lru cache codes:
1. use double linked list for LRU.
2. For the lock, Do we need to put Get operation in lock or not?
For example: 2.1. this.cache.TryGetValue(key, out entry); in method GetItem(...);
2.2. this.cache.ContainsKey(key) in method AddItem(...).
For 2.1. We have to put it witin lock. because in method TryGetValue(key, out entry), it is not thread-safe.
when one thread is get the entry index; then another thread delete the entry from entries, it is possible that entries[i] will return another entry value.
For 2.2. this.cache.ContainsKey(key), which is still not thread-safe. I put it out of lock{}, maybe there is some risk.(it is safe if we put it in lock{}).
// It is from https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace LruCache_20170421
{
public class LruCache<T>
{
public LruCache(int maxEntries)
{
if (maxEntries < 0)
{
throw new ArgumentOutOfRangeException("maxEntries", maxEntries, "maxEntries must be greater than or equal zero.");
}
this.maxEntries = maxEntries;
}
public bool LruCacheEnabled
{
get
{
return this.maxEntries > 0;
}
}
public void AddItem(string key, T item)
{
if (!LruCacheEnabled)
{
throw new ApplicationException("LruCache is disabled.");
}
if (string.IsNullOrEmpty(key))
{
throw new ArgumentNullException("key", "Key cannot be null or empty.");
}
if (this.cache.ContainsKey(key))
{
return;
}
lock (this.cache)
{
if (!this.cache.ContainsKey(key))
{
if (this.cache.Count >= maxEntries)
{
RemoveOldestEntry();
}
CacheEntry cacheEntry = new CacheEntry();
cacheEntry.Key = key;
cacheEntry.Item = item;
if (listEnd == null)
{
// set the first element in the list
cacheEntry.Previous = null;
cacheEntry.Next = null;
listHead = cacheEntry;
listEnd = cacheEntry;
}
else
{
AddElementToEnd(cacheEntry);
}
this.cache.Add(key, cacheEntry);
}
}
}
/// <summary>
/// add the element to the end of the list
/// </summary>
/// <param name="cacheEntry"></param>
private void AddElementToEnd(CacheEntry cacheEntry)
{
cacheEntry.Previous = listEnd;
cacheEntry.Next = null;
listEnd.Next = cacheEntry;
listEnd = cacheEntry;
}
public T GetItem(string key, out bool cached)
{
if (!LruCacheEnabled)
{
throw new ApplicationException("LruCache is disabled.");
}
if (string.IsNullOrEmpty(key))
{
//Logger.LogInformation("Key cannot be null or empty.");
cached = false;
return default(T);
}
T item = default(T);
CacheEntry entry;
lock (this.cache)
{
cached = this.cache.TryGetValue(key, out entry);
if (cached)
{
item = entry.Item;
#region move the element to the end of the list
if (entry == listEnd)
{
return listEnd.Item;
}
if (entry == listHead)
{
// it is the head of the list
// remove from the head
listHead = entry.Next;
listHead.Previous = null;
}
else // entry.Previous!=null
{
// remove from the list
entry.Previous.Next = entry.Next;
entry.Next.Previous = entry.Previous;
}
// add to the end
AddElementToEnd(entry);
#endregion
}
}
return item;
}
#region Private
/// <summary>
/// Remove the end entry, which is oldest.
/// </summary>
private void RemoveOldestEntry()
{
if (listHead == null) // cache is null.
{
return;
}
// remove the element in list head
CacheEntry entryToRemove = listHead;
string keyToRemove = entryToRemove.Key;
if (entryToRemove.Next != null)
{
entryToRemove.Next.Previous = null;
}
listHead = entryToRemove.Next;
if (listEnd == entryToRemove)
{
listEnd = listHead;
}
this.cache.Remove(keyToRemove);
}
private readonly Dictionary<string, CacheEntry> cache = new Dictionary<string, CacheEntry>();
private readonly int maxEntries;
private CacheEntry listHead; // element with the farest access time
private CacheEntry listEnd; // element with the nearest access time
private class CacheEntry
{
public string Key;
public T Item;
// double linked list
public CacheEntry Previous; // point the element with the far access time
public CacheEntry Next; // point the element with the near access time
}
#endregion
}
}