【问题标题】:List of dictionaries vs nested dictionaries字典列表与嵌套字典
【发布时间】:2015-11-04 19:42:26
【问题描述】:

为了稍微解释一下我的环境,我需要跟踪用户和每个用户拥有的东西的集合。这是高度动态的,尽管基本用户的数量总是高于他们拥有的东西的集合。我决定采用这样的方式:

private static Dictionary<string, Dictionary<string, MyClass>> ActiveUsers = 
    new Dictionary<string, Dictionary<string, MyClass>>();

在这种情况下,父字典的TKey是用户的connectionId,内部字典的TKey是一个字符串,表示MyClass的Id
我之前的意思是 ActiveUser 将持有(希望)大量的 TKey,而 TValue 通常会持有少于 10 个项目。由于这些项目是相关的,当用户断开连接时,它会从父词典中删除,并且父词典中的所有其他项目将在其内部词典中搜索某个值,如果存在这些将被删除。
将不断(非常频繁地)访问此字段,我正在努力实现最佳性能。

与使用 ID 和 Dictionary&lt;string, MyClass&gt; 作为字段创建类并保存此类列表相比,这是一种更好的方法(与性能相关)吗?
这样的东西会更好吗?

public class ActiveUsersManager
{
    public string Id{ get; private set; }
    public Dictionary<string, MyClass> ActiveUsers { get; private set; }

    public ActiveUsersManager(string connectionId)
    {
        Id = connectionId;
        ActiveUsers = new Dictionary<string, MyClass>();
    }
}

//In another class
private static List<ActiveUsersManager> ActiveUsers = new List<ActiveUsersManager>();

如果有帮助,ActiveUsers 是 ASP.NET 控制器中的静态字段。

编辑:回答 cmets

这本词典的使用方式如下:

public static MyClass GetInformation(string myId, string objectId)
{
    //Validation removed
    Dictionary<string, MyClass> dictionaryResult = null;
    MyClass result = null;

    if (!ActiveUsers.TryGetValue(myId, out dictionaryResult)) //check if the user was already added to the Dictionary
    {
        dictionaryResult = new Dictionary<string, MyClass>();
        result = new MyClass(objectId);
        dictionaryResult.Add(objectId, result);
        ActiveUsers.Add(myId, dictionaryResult);
    }
    else if (!dictionaryResult.TryGetValue(objectId, out result)) //otherwise check if the user already has this item
    {
        result = new MyClass(objectId);
        dictionaryResult.Add(objectId, result);
        ActiveUsers.Add(myId, dictionaryResult);
    }
    //else everything is already set-up
    return result;
}

编辑:显示如何删除内部项目的代码

public static void RemoveUserAndSessions(string userId, string objectId)
{ 
    ActiveUsers.Remove(userId);
    foreach (Dictionary<string, MyClass> dic in ActiveUsers.Values)
        dic.Remove(objectId);
}

这是我工作的第一个 ASP.NET 应用程序,我之前没有做过任何涉及字典的多线程,我怎样才能使这个线程安全?

编辑:试图让事情更清楚。

我不想透露细节,但我想他们需要了解这背后的原因。这是一个聊天应用程序。每个用户都存储在 ActiveUsers 中,以跟踪活跃用户。每个用户都有一个他们所连接的客户端的字典,而 MyClass 对象包含一组客户端通信所需的属性。一旦用户断开连接,必须立即删除所有活动会话,因此使用 delete 方法。我想这可以通过创建另一个类来在字典中保存活动会话并将这个类放在原始字典中来完成。
按照建议,我将看看 ConcurrentDictionary

【问题讨论】:

  • 制作了一个控制台应用程序来测试这个...虽然嵌套字典需要 0.15 秒来遍历 50.000 个基本项和 10 个内部项,但列表需要 57.34 秒来遍历相同的集合...
  • 你为什么要使用字典,究竟是什么?您几乎可以肯定不需要使用嵌套字典,而应该只设计一个类或接口。使用字典,尤其是嵌套字典,会使您的代码更加复杂,更难调试/维护。
  • @R.Salisbury 因为 O(1) 在集合中查找特定对象?最常用的方法使用嵌套字典需要不到 15 行。你会建议什么其他的实现来做到这一点?
  • 你是对的,但你仍然不应该使用字典词典。使用 ActiveUsers 或 ActiveUserLookups 的字典,如果您确实需要另一个 Dictionary,请将其作为您分配给父 Dictionary 的 Value 的类的属性,而不是 Dictionary 本身的 Value。它只是使代码更易于阅读/维护。
  • 您到底是如何使用这本词典的?此外,您在多线程环境中使用 static 字典,其中字典不是线程安全的。

标签: c# list dictionary


【解决方案1】:

我强烈建议像这样构建数据:

private static ConcurrentDictionary<string, ActiveUser> ActiveUsers { get; set; }

private class ActiveUser {
   public int ConnectionId { get; set; }
   public Dictionary<string, MyClass> MyClasses { get; set; } = new Dictionary<string, MyClass>();
}   
// . . . 
Dictionary<string, ActiveUser> ActiveUsers = new Dictionary<string, ActiveUser>();

(注意:如果你使用的是C#ActiveUser构造函数中初始化字典)

对于您的删除方法,您可能需要执行以下操作:

public static void RemoveUserAndSessions(string userId, string objectId)
{ 
    ActiveUsers.Remove(userId);
    Parallel.ForEach(ActiveUsers.Values, dic => dic.Remove(objectId));
}

编辑:为清晰起见重命名了一些内容。

【讨论】:

  • 我做了一些与此非常相似的事情,但我没有在您的 MyClasses 中使用字典,而是使用了 ConcurrentDictionary。我忘记了 Parallel.ForEach,谢谢提醒!编辑:顺便说一句,ConcurrentDictionary 没有 Remove 方法,而是 TryRemove
【解决方案2】:

从性能角度来看,我在您的设计中看到的主要问题是需要在 RemoveUserAndSessions 方法期间迭代所有字典。这是一个不存在该问题的示例设计

static class UserManager
{
    private static readonly Dictionary<string, User> ActiveUsers = new Dictionary<string, User>();
    public static User GetInformation(string userId)
    {
        lock (ActiveUsers)
        {
            User user;
            if (!ActiveUsers.TryGetValue(userId, out user))
                ActiveUsers.Add(userId, user = new User(userId));
            return user;
        }
    }
    public static void RemoveUserAndSessions(string userId)
    {
        lock (ActiveUsers)
        {
            User user;
            if (ActiveUsers.TryGetValue(userId, out user))
            {
                ActiveUsers.Remove(userId);
                user.EndAllSessions();
            }
        }
    }
    public static void StartSession(string firstUserId, string secondUserId)
    {
        lock (ActiveUsers)
        {
            var firstUser = GetInformation(firstUserId);
            var secondUser = GetInformation(secondUserId);
            firstUser.StartSession(secondUser);
            secondUser.StartSession(firstUser);
        }
    }
}
class User
{
    private Dictionary<string, User> sessions = new Dictionary<string, User>();
    public string Id { get; private set; }
    public User(string id) { Id = id; }
    public void StartSession(User other)
    {
        sessions.Add(other.Id, other);
    }
    public void EndSession(User other)
    {
        sessions.Remove(other.Id);
    }
    public void EndAllSessions()
    {
        foreach (var other in sessions.Values)
            other.EndSession(this);
        sessions.Clear();
    }
}

这只是为了了解这个想法,希望您可以将其映射到您的具体案例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-11-22
    • 2022-08-16
    • 2021-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-04
    • 1970-01-01
    相关资源
    最近更新 更多