深入C#字串和享元(Flyweight)模式的流量分析

來源:互聯網
上載者:User

寫這個文章,主要是因為網上對C#字串和享元模式的誤解比較多。

Flyweight模式
先說這名字,fly呢,就是蒼蠅,沒錯這裡面不是飛的意思,是蒼蠅的意思,weight大家都知道,就是重量,蒼蠅的重量,就是非常非常輕的意思。所以Flyweight模式就是處理非常非常輕量級對象的一個東西。
Flyweight的目標是解決大量細粒度對象的記憶體消耗問題,當然,巧婦難為無米之炊,任何模式和手法都不能憑空造出記憶體來,所以享元模式針對的情況是這些細粒度對象的中資料有重複的情況。
Flyweight的做法是,把對象的狀態(通常用屬性工作表示),分成兩個部分,一部分是內部狀態,另一部分是外部狀態。內部狀態外部狀態是不易重複的(或者說必要的),外部狀態 內部狀態是易重複的。所以,Flyweight把外部狀態提取出來共用,這樣就一定程度解決了記憶體佔用問題。

C#中的字串不是Flyweight模式
在網上常常可以看到一個說法,說C#中的字串使用了Flyweight模式,開門見山地說,這個說法是錯誤的。
錯在哪裡呢?按照上文的介紹,錯就錯在字串它沒有所謂的“內部狀態外部狀態”。
通常講字串是享元的原因就是以下代碼:
string a = "Hello World";
Console.WriteLine(Object.ReferenceEquals(a, "Hello World")); //True
當使用字串直接量的時候,不論你寫了多少個"Hello World",最終記憶體裡面只有一個字串對象。
運行時建立的字串並不在此列,可以使些手段,強制在記憶體裡面產生新的字串。
string a = "Hello World";
Console.WriteLine(Object.ReferenceEquals(a, new String("Hello World".ToCharArray()))); //False
因為我們強行調用了new,所以這個字串跟記憶體中的直接量"Hello World"對應的對象不是同一個。
有趣的是,C#還允許強制把一個字串加入到(如果已經有了,就只是找出來)字串池裡面。
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) );
前面提到了,這個行為跟Flyweight使用的內部狀態和外部狀態不同,是兩個對象實實在在就是同一個對象。

C#中的字串與Flyweight模式
好吧,前面說了不少,C#中的字串不是Flyweight模式,但是是不是就意味著C#裡面字串跟Flyweight沒有關係呢?
當然不是,否則我寫這麼一篇文章豈不是太蛋疼了……
字串池和Intern方法簡直是實現Flyweight的神器啊!
考慮我們有某一類對象,可能會建立幾百萬個,對象裡面恰巧有這麼一個屬性叫做顏色,它在物件建構的時候隨機產生,顏色用的是rgb色,用rgb24來表示,於是顏色字串類似#ccc這樣子。
代碼寫起來就像下面的樣子:

複製代碼 代碼如下: 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];
}
}

接下來我們建立3千萬個對象看看如何複製代碼 代碼如下: Element[] eles = new Element[30000000];
for (var i = 0; i < 30000000; i++)
{
eles[i] = new Element();
}

從工作管理員看到一大塊記憶體被吃掉了

接下來我們使用String.Intern來實現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]);
}
}

可以看到記憶體佔用量的明顯變化。
因為字串對象的不可更改性質,使用了String.Intern之後,我們完全看不出前後color的區別,也就是說,修改前後的Element類是完全等效的,但是Flyweight為我們節約了大量的記憶體。

更多思考
這個典型的使用flyweight情境為我們揭示了享元外部狀態內部狀態的特徵:像字串一樣不可更改的對象。GoF原書的例子中的字型對象Glyph也是如此。
String.Intern這種對象池的方式實現flyweight也值得借鑒,我們可以考慮自己設計flyweight的外部狀態物件時使用類似的方式。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.