c#壓縮字符串的方法
一:背景
1. 講故事
在我們的一個(gè)全內(nèi)存項(xiàng)目中,需要將一家大品牌店鋪小千萬(wàn)的trade灌入到內(nèi)存中,大家知道trade中一般會(huì)有訂單來(lái)源,省市區(qū) ,當(dāng)把這些字段灌進(jìn)去后,你會(huì)發(fā)現(xiàn)他們特別侵蝕內(nèi)存,因?yàn)槎际亲址愋?,不知道大家?duì)內(nèi)存侵蝕性是不是很清楚,我就問(wèn)一個(gè)問(wèn)題。
Question: 一個(gè)空字符串占用多大內(nèi)存? 你知道嗎?
思考之后,下面我們就一起驗(yàn)證下,使用windbg去托管堆一查究竟,代碼如下:
static void Main(string[] args) { string s = string.Empty; Console.ReadLine(); } 0:000> !clrstack -l OS Thread Id: 0x308c (0) Child SP IP Call Site ConsoleApp6.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp6\Program.cs @ 19] LOCALS: 0x00000087391febd8 = 0x000002605da91420 0:000> !DumpObj /d 000002605da91420 Name: System.String String: Fields: MT Field Offset Type VT Attr Value Name 00007ff9eb2b85a0 4000281 8 System.Int32 1 instance 0 m_stringLength 00007ff9eb2b6838 4000282 c System.Char 1 instance 0 m_firstChar 00007ff9eb2b59c0 4000286 d8 System.String 0 shared static Empty >> Domain:Value 000002605beb2230:NotInit << 0:000> !objsize 000002605da91420 sizeof(000002605da91420) = 32 (0x20) bytes (System.String)
從圖中你可以看到,僅僅一個(gè)空字符串就要占用 32byte,如果500w個(gè)空字符串就是: 32byte x 500w = 152M,是不是不算不知道,一算嚇一跳。。。 這還僅僅是一個(gè)什么都沒(méi)有的空字符串哦。
2. 回歸到Trade
問(wèn)題也已經(jīng)擺出來(lái)了,接下來(lái)回歸到Trade中,為了方便演示,先模擬以文件的形式從數(shù)據(jù)庫(kù)讀取20w的trade。
class Program { static void Main(string[] args) { var trades = Enumerable.Range(0, 20 * 10000).Select(m => new Trade() { TradeID = m, TradeFrom = File.ReadLines(Environment.CurrentDirectory + "http://orderfrom.txt") .ElementAt(m % 4) }).ToList(); GC.Collect(); //方便測(cè)試,把臨時(shí)變量清掉 Console.WriteLine("執(zhí)行成功"); Console.ReadLine(); } } class Trade { public int TradeID { get; set; } public string TradeFrom { get; set; } }
然后用windbg去跑一下托管堆,再量一下trades的大小。
0:000> !dumpheap -stat Statistics: MT Count TotalSize Class Name 00007ff9eb2b59c0 200200 7010246 System.String 0:000> !objsize 0x000001a5860629a8 sizeof(000001a5860629a8) = 16097216 (0xf59fc0) bytes (System.Collections.Generic.List`1[[ConsoleApp6.Trade, ConsoleApp6]])
從上面輸出中可以看到托管堆有200200 = 20w(程序分配)+ 200(系統(tǒng)分配)個(gè)
,然后再看size: 16097216/1024/1024= 15.35M
,這就是展示的所有原始情況。
二:壓縮技巧分析
1. 使用字典化處理
其實(shí)在托管堆上有20w個(gè)字符串,但你仔細(xì)觀察一下會(huì)發(fā)現(xiàn)其實(shí)就是4種狀態(tài)的重復(fù)顯示,要么一淘,要么淘寶。。。這就給了我優(yōu)化機(jī)會(huì),何不在獲取數(shù)據(jù)的時(shí)候構(gòu)建好OrderFrom的字典,然后在trade中附增一個(gè)TradeFromID記錄字典中的映射值,因?yàn)樘卣髦瞪?,用byte就可以了,有了這個(gè)思想,可以把代碼修改如下:
class Program { public static Dictionary<int, string> orderfromDict = new Dictionary<int, string>(); static void Main(string[] args) { var trades = Enumerable.Range(0, 20 * 10000).Select(m => { var tradefrom = File.ReadLines(Environment.CurrentDirectory + "http://orderfrom.txt") .ElementAt(m % 4); var kv = orderfromDict.FirstOrDefault(k => k.Value == tradefrom); if (kv.Key == 0) { orderfromDict.Add(orderfromDict.Count + 1, tradefrom); } var trade = new Trade() { TradeID = m, TradeFromID = (byte)kv.Key }; return trade; }).ToList(); GC.Collect(); //方便測(cè)試,把臨時(shí)變量清掉 Console.WriteLine("執(zhí)行成功"); Console.ReadLine(); } } class Trade { public int TradeID { get; set; } public byte TradeFromID { get; set; } public string TradeFrom { get { return Program.orderfromDict[TradeFromID]; } } }
代碼還是很簡(jiǎn)單的,接下來(lái)用windbg看一下空間到底壓縮了多少?
0:000> !dumpheap -stat Statistics: MT Count TotalSize Class Name 00007ff9eb2b59c0 204 10386 System.String 0:000> !clrstack -l OS Thread Id: 0x2ce4 (0) Child SP IP Call Site ConsoleApp6.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp6\Program.cs @ 42] LOCALS: 0x0000006f4d9ff078 = 0x0000016fdcf82ab8 0000006f4d9ff288 00007ff9ecd96c93 [GCFrame: 0000006f4d9ff288] 0:000> !objsize 0x0000016fdcf82ab8 sizeof(0000016fdcf82ab8) = 6897216 (0x693e40) bytes (System.Collections.Generic.List`1[[ConsoleApp6.Trade, ConsoleApp6]])
從上面的輸出中可以看到,托管堆上string現(xiàn)在是:204 = 4(程序分配) + 200(系統(tǒng)分配)個(gè),這4個(gè)就是字典中的4個(gè)哦,空間的話:6897216 /1024/1024= 6.57M,對(duì)應(yīng)之前的 15.35M優(yōu)化了將近60%。
雖然優(yōu)化了60%,但這種優(yōu)化是破壞性的優(yōu)化,需要修改我的Trade結(jié)構(gòu),同時(shí)還要定義個(gè)Dictionary,而且還有不小幅度的修改業(yè)務(wù)邏輯,大家都知道線上的代碼是能不改則不改,不改肯定沒(méi)錯(cuò),改出問(wèn)題肯定是你兜著走,是吧,那問(wèn)題就來(lái)了,如何最小化的修改而且還能壓縮空間,有這樣兩全其美的事情嗎???
2. 利用字符串駐留池
貌似一說(shuō)出來(lái),大家都如夢(mèng)初醒,駐留池的出現(xiàn)就是為了解決這個(gè)問(wèn)題,CLR會(huì)在內(nèi)部維護(hù)了一個(gè)我剛才定義的字典機(jī)制,重復(fù)的字符串就不需要在堆上再次分配,直接存它的引用地址即可,如果你不清楚駐留池,建議看一下我這篇: http://www.dbjr.com.cn/article/189450.htm
接下來(lái)只需要在tradefrom 字段包一層 string.Intern 即可,改動(dòng)不要太小,代碼如下:
static void Main(string[] args) { var trades = Enumerable.Range(0, 20 * 10000).Select(m => new Trade() { TradeID = m, TradeFrom = string.Intern(File.ReadLines(Environment.CurrentDirectory + "http://orderfrom.txt") .ElementAt(m % 4)), //包一層 string.Intern }).ToList(); GC.Collect(); //方便測(cè)試,把臨時(shí)變量清掉 Console.WriteLine("執(zhí)行成功"); Console.ReadLine(); }
然后用windbg抓一下托管堆。
0:000> !dumpheap -stat Statistics: MT Count TotalSize Class Name 00007ff9eb2b59c0 204 10386 System.String 0:000> !clrstack -l OS Thread Id: 0x13f0 (0) Child SP IP Call Site ConsoleApp6.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp6\Program.cs @ 27] LOCALS: 0x0000005e4d3ff0a8 = 0x000001f8a15129a8 0000005e4d3ff2b8 00007ff9ecd96c93 [GCFrame: 0000005e4d3ff2b8] 0:000> !objsize 0x000001f8a15129a8 sizeof(000001f8a15129a8) = 8497368 (0x81a8d8) bytes (System.Collections.Generic.List`1[[ConsoleApp6.Trade, ConsoleApp6]])
觀察后發(fā)現(xiàn),當(dāng)用了駐留池之后空間為: 8497368 /1024/1024 =8.1M,你可能有疑問(wèn),為什么和字典化相比內(nèi)存要大24%呢? 仔細(xì)觀察你會(huì)發(fā)現(xiàn),當(dāng)用駐留池后,List<Trade> 中的TradeFrom存的是string在堆中的內(nèi)存地址,在x64機(jī)器上要占用8個(gè)字節(jié),而字典化方式內(nèi)存堆上Trade是不分配TradeFrom,而是用了一個(gè)byte來(lái)替代,總體來(lái)說(shuō)相當(dāng)于一個(gè)trade省了7byte的空間,然后用windbg看一下。
0:000> !da -length 1 -details 000001f8b16f9b68 Name: ConsoleApp6.Trade[] Size: 2097176(0x200018) bytes Array: Rank 1, Number of elements 262144, Type CLASS Fields: MT Field Offset Type VT Attr Value Name 00007ff9eb2b85a0 4000001 10 System.Int32 1 instance 0 <TradeID>k__BackingField 00007ff9eb2b59c0 4000002 8 System.String 0 instance 000001f8a1516030 <TradeFrom>k__BackingField 0:000> !DumpObj /d 000001f8a1516030 Name: System.String String: WAP
可以看到, 000001f8a1516030
就是 堆上 string=Wap
的引用地址,這個(gè)地址占用了8byte空間。
再回頭dump一下使用字典化方式的Trade,可以看到它是沒(méi)有 <TradeFrom>k__BackingField
字段的。
0:000> !da -length 1 -details 000001ed52759ac0 Name: ConsoleApp6.Trade[] Size: 262168(0x40018) bytes Array: Rank 1, Number of elements 32768, Type CLASS Fields: MT Field Offset Type VT Attr Value Name 00007ff9eb2b85a0 4000002 8 System.Int32 1 instance 0 <TradeID>k__BackingField 00007ff9eb2b7d20 4000003 c System.Byte 1 instance 0 <TradeFromID>k__BackingField
三:總結(jié)
大家可以根據(jù)自己的情況使用,使用駐留池方式是改變最小的,簡(jiǎn)單粗暴,自己構(gòu)建字典化雖然最省內(nèi)存,但需要修正業(yè)務(wù)邏輯,這個(gè)風(fēng)險(xiǎn)自擔(dān)哦。。。
以上就是c#壓縮字符串的方法的詳細(xì)內(nèi)容,更多關(guān)于c#壓縮字符串的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#通過(guò)第三方組件生成二維碼(QR Code)和條形碼(Bar Code)
用C#如何生成二維碼,我們可以通過(guò)現(xiàn)有的第三方dll直接來(lái)實(shí)現(xiàn),下面列出幾種不同的生成方法2016-12-12C#如何優(yōu)雅的對(duì)WinForm窗體應(yīng)用程序進(jìn)行權(quán)限控制
經(jīng)常會(huì)出現(xiàn)winfrom頁(yè)面需要加載權(quán)限樹(shù),下面這篇文章主要給大家介紹了關(guān)于C#如何優(yōu)雅的對(duì)WinForm窗體應(yīng)用程序進(jìn)行權(quán)限控制的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11C#對(duì)稱加密(AES加密)每次生成的結(jié)果都不同的實(shí)現(xiàn)思路和代碼實(shí)例
這篇文章主要介紹了C#對(duì)稱加密(AES加密)每次生成的結(jié)果都不同的實(shí)現(xiàn)思路和代碼實(shí)例,每次解密時(shí)從密文中截取前16位,這就是實(shí)現(xiàn)隨機(jī)的奧秘,本文同時(shí)給出了實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-07-07使用C#實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)堆的代碼
這篇文章主要介紹了使用C#實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)堆,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02C#用表達(dá)式樹(shù)構(gòu)建動(dòng)態(tài)查詢的方法
這篇文章主要介紹了C#用表達(dá)式樹(shù)構(gòu)建動(dòng)態(tài)查詢的方法,幫助大家更好的理解和學(xué)習(xí)c#,感興趣的朋友可以了解下2020-12-12C#實(shí)現(xiàn)嵌套循環(huán)的示例代碼
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)嵌套循環(huán)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09c#實(shí)現(xiàn)網(wǎng)頁(yè)圖片提取工具代碼分享
c#實(shí)現(xiàn)網(wǎng)頁(yè)圖片提取工具代碼分享,大家參考使用吧2013-12-12.Net WInform開(kāi)發(fā)筆記(五)關(guān)于事件Event
我前面幾篇博客中提到過(guò).net中的事件與Windows事件的區(qū)別,本文討論的是前者,也就是我們代碼中經(jīng)常用到的Event,感興趣的朋友可以了解下2013-01-01C#中Hashtable和Dictionary的區(qū)別與用法示例
由于 Hashtable 和 Dictionary 同時(shí)存在, 在使用場(chǎng)景上必然存在選擇性, 并不任何時(shí)刻都能相互替代。所以這篇文章主要給大家介紹了關(guān)于C#中Hashtable和Dictionary區(qū)別的相關(guān)資料,需要的朋友可以參考下2021-05-05