具有可过期项目的HashTable

时间:2011-05-14 18:54:03

标签: c# hashtable

我想实现HashTable(或mabybe a HashSetDictionary),其中包含一段时间后过期的唯一成员。例如:

// Items expire automatically after 10 seconds (Expiration period = 10 sec)
bool result = false;
// Starting from second 0
result = MyHashSet.Add("Bob");   // second 0 => true
result = MyHashSet.Add("Alice"); // second 5 => true
result = MyHashSet.Add("Bob");   // second 8 => false (item already exist)
result = MyHashSet.Add("Bob");   // second 12 => true (Bob has expired)

如何以最低成本以线程安全的方式做到这一点?

3 个答案:

答案 0 :(得分:8)

您可以创建自己的哈希表,其中每个项目包含创建时间和时间跨度。 在您尝试返回值的索引器中,如果项的生命周期已过期,则返回null。并删除该项目。从表中删除项目的后台线程将无法确保您不会在没有此项的情况下返回过期项目。然后你可以创建一个执行此操作的线程,只是为了最大限度地删除过期的项目,以便在很多项目永远不会被访问时最小化内存消耗。

答案 1 :(得分:7)

您是否考虑过使用System.Web.Caching而不是自己动手?

http://www.hanselman.com/blog/UsingTheASPNETCacheOutsideOfASPNET.aspx

修改

以上情况不应该给系统增加很多开销,但要看看这个。

以下代码中的一些健康警告。

  • 它不完整......请参阅底部的throw new NotImplementedException()。我会试着回过头来,因为这是一个有趣的难题。
  • 您可能想要完成过期的方式&覆盖“添加方法”以为构造值提供不同的值
  • 我只在控制台应用中测试了它。见测试代码
  • 围绕TKey& amp;还需要做一些工作。 TValue Collections因为他们会盲目地返回整个内部词典的集合而没有任何过期检查......如果你不需要特别精细的过期。您可以将system.timer添加到类中,该类定期遍历整个集合并删除过期的条目。
  • 如果你看一下BCL词典的定义,你会看到它实现了许多其他接口的地狱,所以根据你的要求你也可能想要实现它们。 IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback

测试代码

TimeSpan t = new TimeSpan(0,0,5); //5 Second Expiry
ExpiringDictionary<int, string> dictionary 
    = new ExpiringDictionary<int,string>(t);

dictionary.Add(1, "Alice");
dictionary.Add(2, "Bob");
dictionary.Add(3, "Charlie");
//dictionary.Add(1, "Alice"); //<<this will throw a exception as normal... 

System.Threading.Thread.Sleep(6000);
dictionary.Add(1, "Alice"); //<< this however should work fine as 6 seconds have passed

<强>实施

public class ExpiringDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private class ExpiringValueHolder<T> {
        public T Value { get; set; }
        public DateTime Expiry { get; private set; }
        public ExpiringValueHolder(T value, TimeSpan expiresAfter)
        {
            Value = value;
            Expiry = DateTime.Now.Add(expiresAfter);
        }

        public override string ToString() { return Value.ToString(); }

        public override int GetHashCode() { return Value.GetHashCode(); }
    };
    private Dictionary<TKey, ExpiringValueHolder<TValue>> innerDictionary;
    private TimeSpan expiryTimeSpan;

    private void DestoryExpiredItems(TKey key)
    {
        if (innerDictionary.ContainsKey(key))
        {
            var value = innerDictionary[key];

            if (value.Expiry < System.DateTime.Now)
            { 
                //Expired, nuke it in the background and continue
                innerDictionary.Remove(key);
            }
        }
    }

    public ExpiringDictionary(TimeSpan expiresAfter)
    {
        expiryTimeSpan = expiresAfter;
        innerDictionary = new Dictionary<TKey, ExpiringValueHolder<TValue>>();
    }

    public void Add(TKey key, TValue value)
    {
        DestoryExpiredItems(key);

        innerDictionary.Add(key, new ExpiringValueHolder<TValue>(value, expiryTimeSpan));
    }

    public bool ContainsKey(TKey key)
    {
        DestoryExpiredItems(key);

        return innerDictionary.ContainsKey(key);
    }

    public bool Remove(TKey key)
    {
        DestoryExpiredItems(key);

        return innerDictionary.Remove(key);
    }

    public ICollection<TKey> Keys
    {
        get { return innerDictionary.Keys; }
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        bool returnval = false;
        DestoryExpiredItems(key);

        if (innerDictionary.ContainsKey(key))
        {
            value = innerDictionary[key].Value;
            returnval = true;
        } else { value = default(TValue);}

        return returnval;
    }

    public ICollection<TValue> Values
    {
        get { return innerDictionary.Values.Select(vals => vals.Value).ToList(); }
    }

    public TValue this[TKey key]
    {
        get
        {
            DestoryExpiredItems(key);
            return innerDictionary[key].Value;
        }
        set
        {
            DestoryExpiredItems(key);
            innerDictionary[key] = new ExpiringValueHolder<TValue>(value, expiryTimeSpan);
        }
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        DestoryExpiredItems(item.Key);

        innerDictionary.Add(item.Key, new ExpiringValueHolder<TValue>(item.Value, expiryTimeSpan));
    }

    public void Clear()
    {
        innerDictionary.Clear();
    }

    public int Count
    {
        get { return innerDictionary.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        throw new NotImplementedException();
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        throw new NotImplementedException();
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        throw new NotImplementedException();
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

答案 2 :(得分:0)

试试这个:

static void Main() {
    ExpirationList<string> list = new ExpirationList<string>(new List<string>());
    bool r1 = list.Add("Bob", 3000); // true
    Thread.Sleep(2000);
    bool r2 = list.Add("Bob", 3000); // false
    Thread.Sleep(2000);
    bool r3 = list.Add("Bob", 3000); // true
}

public class ExpirationList<T> {
    private List<T> _list;

    public ExpirationList(List<T> list) {
        if (list == null) throw new ArgumentException();
        _list = list;
    }

    public bool Add(T item, int lifetime) {

        lock (_list) {
            if (_list.Contains(item))
                return false;
            _list.Add(item);
        }

        new Action<int>(time => Thread.Sleep(time))
            .BeginInvoke(lifetime, new AsyncCallback(result => {
                T obj = (T)result.AsyncState;
                lock (_list) {
                    _list.Remove(obj);
                }
            }), item);

        return true;

    }

    // add other proxy code here

}

当然,List可以替换为Hashtable和(它会更加正确)异步委托可以替换为Timers,但我希望常见的方法是明确的