C# + .NET4.0使用超大Dictionary內存不足問題

最近需要實現將 XML 文件中存儲的數據統一讀取入內存,並快速查詢指定數據的功能。當 XML 中的數據量不大時,這個功能非常簡單,選擇 Dictionary 數據結構,按鍵值對的方式存儲數據就好了,查詢也十分便捷。然而,我處理的 XML 數據小則幾百萬條,大則幾千萬條,使用傳統的方式在 .NET4.0 下會報 “System.OutOfMemory” 的錯誤。這主要是因爲 .NET4.0 下有個硬性限制,單個對象不能超過 2G 。而在 .NET4.5 之後,則可以通過配置程序的 *.exe.config 文件開啓超大對象支持,來解決這個問題。當然,必須保證你是 X64 的程序,並且物理內存足夠。由於我的程序由於一些第三方開發庫限制,必須是基於 .NET4.0 ,雖然物理內存足夠,這種方法並不適用。

研究了一下,最後我的解決方案還蠻 Tricky 的,分享給大家。

使用固定長度的Dictionary

可變長度的 Dictionary 在後臺實現時,超過某個長度就會自動擴展容量。當數據量很大的時候,這個擴容的過程非常耗費時間和內存,有時候甚至它會佔用兩倍於當前數據大小的內存。爲了避免這種問題,應該使用定長的 Dictionary ,防止它增長。在我的程序中,最大可以將這個長度設置爲 4000000 。讀者可以按需調整。

// 示例:
Dictionary<long, string> dic = new Dictionary<long, string>(4000000);

創建Dictionary列表對象

既然單個對象不能超過 2G ,那麼可以使用多個對象拆分存儲不就好了。於是,我試着創建了幾個 Dictionary 對象,當前一個 Dictionary 對象的長度超過固定值時,我就將後面的數據存儲在下一個 Dictionary 對象中。經過測試,這種方法是可行的。之所以說這個解決方案蠻 Tricky ,就是這個原因。

這裏又引申出幾個問題,拆分成多少個 Dictionary 對象合適?每個查詢數據的地方都需要寫循環查詢每個 Dictionary 的代碼,比較冗餘。於是,我將這種方法封裝成了一個單獨的類,重寫了添加、查詢的方法。我沒有寫刪除的方法,因爲我不需要刪除,有需求的讀者可以自己寫,比較簡單。

類代碼如下,可參考。

public class NodeDicList
{
    private int capacity; // 每個Dictionary的固定容量
    public List<Dictionary<long, string>> dicList;
    
    public NodeDicList(int cap = 4000000)
    {
        capacity = cap;
        Dictionary<long, string> dic = new Dictionary<long, string>(cap);
        dicList = new List<Dictionary<long, string>>();
        dicList.Add(dic);
    }

	// 統計列表總長度
    public int count()
    {
        int count = 0;
        foreach (Dictionary<long, string> dic in dicList)
        {
            count += dic.Count;
        }
        return count;
    }

	// 添加新數據,會自動創建新的Dictionary對象
    public void addItem(long key, string p)
    {
        if (dicList.ElementAt(dicList.Count - 1).Count < capacity)
        {
            dicList.ElementAt(dicList.Count - 1).Add(key, p);
        }
        else
        {
            Dictionary<long, string> dic = new Dictionary<long, string>(capacity);
            dicList.Add(dic);
            dic.Add(key, p);
        }
    }

	// 查詢是否包含某個數據
    public bool containsKeyItem(long key)
    {
        foreach (Dictionary<long, string> dic in dicList)
        {
            if (dic.ContainsKey(key))
            {
                return true;
            }
        }
        return false;
    }

	// 獲取某個數據的值
    public string getItemByKey(long key)
    {
        foreach (Dictionary<long, string> dic in dicList)
        {
            if (dic.ContainsKey(key))
            {
                return dic[key];
            }
        }
        return null;
    }
}

最後,如果你的對象不是 Dictionary ,是 Array 、List 等等,應該都可以借鑑這個思路。

感謝閱讀,有不足之處,請大家批評指正!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章