深入C#字符串和享元(Flyweight)模式的使用分析

所属分类: 软件编程 / C#教程 阅读数: 50
收藏 0 赞 0 分享

写这个文章,主要是因为网上对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();
     }

从任务管理器看到一大块内存被吃掉了

QFOMR9}(NR%(T3`V3Q35MSY

接下来我们使用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的外部状态对象时使用类似的方式。

更多精彩内容其他人还在看

C#中Datetimepicker出现问题的解决方法

这篇文章主要给大家介绍了关于C#中Datetimepicker出现问题的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

C# SQLite数据库入门使用说明

这篇文章主要给大家介绍了关于C#中SQLite数据库入门使用的相关资料,文中通过图文以及示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

C#实现批量下载图片到本地示例代码

这篇文章主要给大家介绍了关于C#如何实现批量下载图片到本地的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用c#具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

如何获取C#中方法的执行时间以及其代码注入详解

这篇文章主要给大家介绍了关于如何获取C#中方法的执行时间以及其代码注入的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧
收藏 0 赞 0 分享

C#中通过LRU实现通用高效的超时连接探测

这篇文章主要介绍了c#中通过LRU实现通用高效的超时连接探测,非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下
收藏 0 赞 0 分享

如何使用C#将Tensorflow训练的.pb文件用在生产环境详解

这篇文章主要给大家介绍了关于如何使用C#将Tensorflow训练的.pb文件用在生产环境的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
收藏 0 赞 0 分享

C#程序启动项的设置方法

这篇文章主要为大家详细介绍了C#程序启动项的设置方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

c#爬虫爬取京东的商品信息

这篇文章主要给大家介绍了关于利用c#爬虫爬取京东商品信息的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们随着小编来一起学习学习吧
收藏 0 赞 0 分享

C#随机数生成字母金字塔

这篇文章主要为大家详细介绍了C#随机数生成字母金字塔,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

WPF实现窗体中的悬浮按钮

这篇文章主要为大家详细介绍了WPF实现窗体中的悬浮按钮,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享
查看更多