mirror of https://github.com/Squidex/squidex.git
3 changed files with 194 additions and 0 deletions
@ -0,0 +1,108 @@ |
|||
// ==========================================================================
|
|||
// LRUCache.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace Squidex.Infrastructure.Caching |
|||
{ |
|||
public sealed class LRUCache |
|||
{ |
|||
private readonly Dictionary<object, LinkedListNode<LRUCacheItem>> cacheMap = new Dictionary<object, LinkedListNode<LRUCacheItem>>(); |
|||
private readonly LinkedList<LRUCacheItem> cacheHistory = new LinkedList<LRUCacheItem>(); |
|||
private readonly int capacity; |
|||
|
|||
public LRUCache(int capacity) |
|||
{ |
|||
Guard.GreaterThan(capacity, 0, nameof(capacity)); |
|||
|
|||
this.capacity = capacity; |
|||
} |
|||
|
|||
public bool Set(object key, object value) |
|||
{ |
|||
Guard.NotNull(key, nameof(key)); |
|||
|
|||
if (cacheMap.TryGetValue(key, out var node)) |
|||
{ |
|||
node.Value.Value = value; |
|||
|
|||
cacheHistory.Remove(node); |
|||
cacheHistory.AddLast(node); |
|||
|
|||
cacheMap[key] = node; |
|||
|
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
if (cacheMap.Count >= capacity) |
|||
{ |
|||
RemoveFirst(); |
|||
} |
|||
|
|||
var cacheItem = new LRUCacheItem { Key = key, Value = value }; |
|||
|
|||
node = new LinkedListNode<LRUCacheItem>(cacheItem); |
|||
|
|||
cacheMap.Add(key, node); |
|||
cacheHistory.AddLast(node); |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public bool Remove(object key) |
|||
{ |
|||
Guard.NotNull(key, nameof(key)); |
|||
|
|||
if (cacheMap.TryGetValue(key, out var node)) |
|||
{ |
|||
cacheMap.Remove(key); |
|||
cacheHistory.Remove(node); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool TryGetValue(object key, out object value) |
|||
{ |
|||
Guard.NotNull(key, nameof(key)); |
|||
|
|||
value = null; |
|||
|
|||
if (cacheMap.TryGetValue(key, out var node)) |
|||
{ |
|||
value = node.Value.Value; |
|||
|
|||
cacheHistory.Remove(node); |
|||
cacheHistory.AddLast(node); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public bool Contains(object key) |
|||
{ |
|||
Guard.NotNull(key, nameof(key)); |
|||
|
|||
return cacheMap.ContainsKey(key); |
|||
} |
|||
|
|||
private void RemoveFirst() |
|||
{ |
|||
var node = cacheHistory.First; |
|||
|
|||
cacheMap.Remove(node.Value.Key); |
|||
cacheHistory.RemoveFirst(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// LRUCacheItem.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
#pragma warning disable SA1401 // Fields must be private
|
|||
|
|||
namespace Squidex.Infrastructure.Caching |
|||
{ |
|||
internal class LRUCacheItem |
|||
{ |
|||
public object Key; |
|||
public object Value; |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// ==========================================================================
|
|||
// LRUCacheTests.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Xunit; |
|||
|
|||
namespace Squidex.Infrastructure.Caching |
|||
{ |
|||
public class LRUCacheTests |
|||
{ |
|||
private readonly LRUCache sut = new LRUCache(10); |
|||
private readonly string key = "Key"; |
|||
|
|||
[Fact] |
|||
public void Should_always_override_when_setting_value() |
|||
{ |
|||
sut.Set(key, 1); |
|||
sut.Set(key, 2); |
|||
|
|||
Assert.True(sut.TryGetValue(key, out var value)); |
|||
Assert.True(sut.Contains(key)); |
|||
|
|||
Assert.Equal(2, value); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_remove_old_items_when_capacity_reached() |
|||
{ |
|||
for (int i = 0; i < 15; i++) |
|||
{ |
|||
sut.Set(i.ToString(), i); |
|||
} |
|||
|
|||
for (int i = 0; i < 5; i++) |
|||
{ |
|||
Assert.False(sut.TryGetValue(i.ToString(), out var value)); |
|||
Assert.Null(value); |
|||
} |
|||
|
|||
for (int i = 5; i < 15; i++) |
|||
{ |
|||
Assert.True(sut.TryGetValue(i.ToString(), out var value)); |
|||
Assert.NotNull(value); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_return_false_when_item_to_remove_does_not_exist() |
|||
{ |
|||
Assert.False(sut.Remove(key)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Should_remove_inserted_item() |
|||
{ |
|||
sut.Set(key, 2); |
|||
|
|||
Assert.True(sut.Remove(key)); |
|||
Assert.False(sut.Contains(key)); |
|||
Assert.False(sut.TryGetValue(key, out var value)); |
|||
Assert.Null(value); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue