深入C#字符串和享元(Flyweight)模式的使用分析
寫這個(gè)文章,主要是因?yàn)榫W(wǎng)上對(duì)C#字符串和享元模式的誤解比較多。
Flyweight模式
先說(shuō)這名字,fly呢,就是蒼蠅,沒(méi)錯(cuò)這里面不是飛的意思,是蒼蠅的意思,weight大家都知道,就是重量,蒼蠅的重量,就是非常非常輕的意思。所以Flyweight模式就是處理非常非常輕量級(jí)對(duì)象的一個(gè)東西。
Flyweight的目標(biāo)是解決大量細(xì)粒度對(duì)象的內(nèi)存消耗問(wèn)題,當(dāng)然,巧婦難為無(wú)米之炊,任何模式和手法都不能憑空造出內(nèi)存來(lái),所以享元模式針對(duì)的情況是這些細(xì)粒度對(duì)象的中數(shù)據(jù)有重復(fù)的情況。
Flyweight的做法是,把對(duì)象的狀態(tài)(通常用屬性表示),分成兩個(gè)部分,一部分是內(nèi)部狀態(tài),另一部分是外部狀態(tài)。內(nèi)部狀態(tài)外部狀態(tài)是不易重復(fù)的(或者說(shuō)必要的),外部狀態(tài) 內(nèi)部狀態(tài)是易重復(fù)的。所以,F(xiàn)lyweight把外部狀態(tài)提取出來(lái)共享,這樣就一定程度解決了內(nèi)存占用問(wèn)題。
C#中的字符串不是Flyweight模式
在網(wǎng)上常??梢钥吹揭粋€(gè)說(shuō)法,說(shuō)C#中的字符串使用了Flyweight模式,開(kāi)門見(jiàn)山地說(shuō),這個(gè)說(shuō)法是錯(cuò)誤的。
錯(cuò)在哪里呢?按照上文的介紹,錯(cuò)就錯(cuò)在字符串它沒(méi)有所謂的“內(nèi)部狀態(tài)外部狀態(tài)”。
通常講字符串是享元的原因就是以下代碼:
string a = "Hello World";
Console.WriteLine(Object.ReferenceEquals(a, "Hello World")); //True
當(dāng)使用字符串直接量的時(shí)候,不論你寫了多少個(gè)"Hello World",最終內(nèi)存里面只有一個(gè)字符串對(duì)象。
運(yùn)行時(shí)創(chuàng)建的字符串并不在此列,可以使些手段,強(qiáng)制在內(nèi)存里面產(chǎn)生新的字符串。
string a = "Hello World";
Console.WriteLine(Object.ReferenceEquals(a, new String("Hello World".ToCharArray()))); //False
因?yàn)槲覀儚?qiáng)行調(diào)用了new,所以這個(gè)字符串跟內(nèi)存中的直接量"Hello World"對(duì)應(yīng)的對(duì)象不是同一個(gè)。
有趣的是,C#還允許強(qiáng)制把一個(gè)字符串加入到(如果已經(jīng)有了,就只是找出來(lái))字符串池里面。
string a = "Hello World";
string b = String.Intern(new String("Hello World".ToCharArray()));
Console.WriteLine(Object.ReferenceEquals(a,b) );
或者
string a = String.Intern(new String("Hello World".ToCharArray()));
string b = String.Intern(new String("Hello World".ToCharArray()));
Console.WriteLine(Object.ReferenceEquals(a,b) );
前面提到了,這個(gè)行為跟Flyweight使用的內(nèi)部狀態(tài)和外部狀態(tài)不同,是兩個(gè)對(duì)象實(shí)實(shí)在在就是同一個(gè)對(duì)象。
C#中的字符串與Flyweight模式
好吧,前面說(shuō)了不少,C#中的字符串不是Flyweight模式,但是是不是就意味著C#里面字符串跟Flyweight沒(méi)有關(guān)系呢?
當(dāng)然不是,否則我寫這么一篇文章豈不是太蛋疼了……
字符串池和Intern方法簡(jiǎn)直是實(shí)現(xiàn)Flyweight的神器啊!
考慮我們有某一類對(duì)象,可能會(huì)創(chuàng)建幾百萬(wàn)個(gè),對(duì)象里面恰巧有這么一個(gè)屬性叫做顏色,它在對(duì)象構(gòu)造的時(shí)候隨機(jī)產(chǎn)生,顏色用的是rgb色,用rgb24來(lái)表示,于是顏色字符串類似#ccc這樣子。
代碼寫起來(lái)就像下面的樣子:
class Element
{
static Random rnd = new Random();
static char[] table;
static Element()
{
table = "0123456789abcdef".ToCharArray();
}
public string color;
public Element()
{
color = "" + table[rnd.Next() % 16] + table[rnd.Next() % 16] + table[rnd.Next() % 16];
}
}
接下來(lái)我們創(chuàng)建3千萬(wàn)個(gè)對(duì)象看看如何
Element[] eles = new Element[30000000];
for (var i = 0; i < 30000000; i++)
{
eles[i] = new Element();
}
從任務(wù)管理器看到一大塊內(nèi)存被吃掉了
接下來(lái)我們使用String.Intern來(lái)實(shí)現(xiàn)Flyweight:
class Element
{
static Random rnd = new Random();
static char[] table;
static Element()
{
table = "0123456789abcdef".ToCharArray();
}
public string color;
public Element()
{
color = String.Intern("" + table[rnd.Next() % 16] + table[rnd.Next() % 16] + table[rnd.Next() % 16]);
}
}
可以看到內(nèi)存占用量的明顯變化。
因?yàn)樽址畬?duì)象的不可更改性質(zhì),使用了String.Intern之后,我們完全看不出前后color的區(qū)別,也就是說(shuō),修改前后的Element類是完全等效的,但是Flyweight為我們節(jié)約了大量的內(nèi)存。
更多思考
這個(gè)典型的使用flyweight場(chǎng)景為我們揭示了享元外部狀態(tài)內(nèi)部狀態(tài)的特征:像字符串一樣不可更改的對(duì)象。GoF原書的例子中的字型對(duì)象Glyph也是如此。
String.Intern這種對(duì)象池的方式實(shí)現(xiàn)flyweight也值得借鑒,我們可以考慮自己設(shè)計(jì)flyweight的外部狀態(tài)對(duì)象時(shí)使用類似的方式。
- C#對(duì)象為Null模式(Null Object Pattern)實(shí)例教程
- C#策略模式(Strategy Pattern)實(shí)例教程
- C#中的IDisposable模式用法詳解
- C#中委托和事件在觀察者模式中的應(yīng)用實(shí)例
- c#標(biāo)準(zhǔn)idispose模式使用示例
- C#中利用代理實(shí)現(xiàn)觀察者設(shè)計(jì)模式詳解
- c#使用簡(jiǎn)單工廠模式實(shí)現(xiàn)生成html文件的封裝類分享
- c# 代理模式
- C#設(shè)計(jì)模式之外觀模式介紹
- C# 觀察者模式實(shí)例介紹
- c#設(shè)計(jì)模式 適配器模式詳細(xì)介紹
- C#單例模式(Singleton Pattern)實(shí)例教程
相關(guān)文章
C#中Byte轉(zhuǎn)換相關(guān)的函數(shù)
這篇文章主要介紹了C#中Byte轉(zhuǎn)換相關(guān)的函數(shù)介紹,非常具有參考借鑒價(jià)值,特此分享到腳本之家平臺(tái)供大家學(xué)習(xí)2016-05-05Unity實(shí)現(xiàn)老虎機(jī)滾動(dòng)抽獎(jiǎng)效果的示例代碼
這篇文章主要介紹了Unity實(shí)現(xiàn)老虎機(jī)滾動(dòng)抽獎(jiǎng)效果的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04C#微信公眾號(hào)開(kāi)發(fā)之接收事件推送與消息排重的方法
這篇文章主要介紹了C#微信公眾號(hào)開(kāi)發(fā)之接收事件推送與消息排重的方法,詳細(xì)分析了事件推送與消息排重的使用技巧,對(duì)微信開(kāi)發(fā)有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-01-01C#面向?qū)ο笤O(shè)計(jì)原則之里氏替換原則
這篇文章介紹了C#面向?qū)ο笤O(shè)計(jì)原則之里氏替換原則,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03C#策略模式(Strategy Pattern)實(shí)例教程
這篇文章主要介紹了C#策略模式(Strategy Pattern),以一個(gè)簡(jiǎn)單的實(shí)例講述了C#策略模式的實(shí)現(xiàn)方法,包括策略模式的用途以及具體實(shí)現(xiàn)方法,需要的朋友可以參考下2014-09-09