最近需要實作将 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 等等,應該都可以借鑒這個思路。
感謝閱讀,有不足之處,請大家批評指正!