【问题标题】:HashTable with expirable items带有过期项目的哈希表
【发布时间】:2011-08-25 15:16:14
【问题描述】:

我想实现一个 HashTable(或者可能是一个 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)

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

【问题讨论】:

    标签: c# hashtable


    【解决方案1】:

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

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

    编辑

    上面不应该给系统增加太多的开销,但是看看这个。

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

    • 不完整...见底部的throw new NotImplementedException()s。我会尝试过一段时间再回来,因为这是一个有趣的谜题。
    • 您可能希望改变到期的方式并覆盖添加方法以向构造值提供不同的值
    • 我只在控制台应用程序中对其进行了最低限度的测试。查看测试代码
    • 它还需要围绕 TKey 和 TValue 集合进行一些工作,因为它们会盲目地返回整个内部字典的集合,而无需任何过期检查……如果您不需要特别精细的过期。您可以将 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();
        }
    }
    

    【讨论】:

    • @soandos 该链接显示了如何在 Web 应用程序之外使用它。
    • 它是一种IDS(入侵检测系统)作为一个独立的库。我需要高性能。
    【解决方案2】:

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

    【讨论】:

    • 这是个好主意(答案的第一部分)。我的问题出在插入端,但可以应用这个想法。我不同意使用任何额外的线程。
    • 您可能是对的,hastable 不会花时间在 hastable 中找到一个项目,因此删除项目不会有助于提高速度。但是额外的线程将毫无用处地使用cpu。该线程唯一有帮助的是,如果从未访问过很多添加的项目,它将删除它们并最大限度地减少内存消耗
    • 是的,但要考虑到 hastable 的读取次数比写入次数多得多,因为这符合她的目的,所以只要看看你通常拥有的读写比率,你就可以在以后插入时触发线程有时,当你统计上应该有更多的“垃圾”时,除了在一些初始化时间你只有很多写入。在这种情况下,您也不需要计时器。但可能您不需要在当今的计算机上进行这种级别的优化:D
    【解决方案3】:

    试试这个:

    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,但我希望通用方法明确

    【讨论】:

    • 在我的情况下,这将导致大量的睡眠线程(每分钟插入数千次)。
    • 我认为这对于大型收藏是不可持续的
    • @Xargon:因此设置 1 个计时器,例如每秒 1 次滴答声,以及一个额外的列表,以保持生命周期。减少每个滴答的所有生命周期,如果其中一个变为零,则从列表中删除它及其链接项(在主列表的同一索引上)。为避免创建两个列表,您可以将类包装在具有额外属性的新列表中:Lifetime 并使用一个列表
    • 第二种方法作为第一种方法,但更多的是能够返回过期的项目,因为时间需要时间来完成操作。而另一个线程可以访问该集合。
    猜你喜欢
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 2011-10-24
    • 2012-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-19
    相关资源
    最近更新 更多