当前位置:首页 > C#编程 > C#/.net框架 > 正文内容

C#字符串与享元(Flyweight)模式

Jorge2年前 (2022-05-08)C#/.net框架747

注:

关注这个话题是因为看到C#的关键字 lock时,其传入引用对象。

因为string也是引用对象,所以能否做为lock的参数?

对于这个问题,要搞明白C#的字符串的一个特点,它使用类似于享元模式的机制。因此在lock中锁字符串是相当不安全的。

下面贴子是对C#字符串与享元模式的深入讨论。



写这个文章,主要是因为网上对C#字符串和享元模式的误解比较多。

Flyweight模式

先说这名字,fly呢,就是苍蝇,没错这里面不是飞的意思,是苍蝇的意思,weight大家都知道,就是重量,苍蝇的重量,就是非常非常轻的意思。所以Flyweight模式就是处理非常非常轻量级对象的一个东西。

Flyweight的目标是解决大量细粒度对象的内存消耗问题,当然,巧妇难为无米之炊,任何模式和手法都不能凭空造出内存来,所以享元模式针对的情况是这些细粒度对象的中数据有重复的情况。

Flyweight的做法是,把对象的状态(通常用属性表示),分成两个部分,一部分是内部状态,另一部分是外部状态。内部状态外部状态是不易重复的(或者说必要的),外部状态 内部状态是易重复的。所以,Flyweight把外部状态提取出来共享,这样就一定程度解决了内存占用问题。

C#中的字符串不是Flyweight模式

在网上常常可以看到一个说法,说C#中的字符串使用了Flyweight模式,开门见山地说,这个说法是错误的。

错在哪里呢?按照上文的介绍,错就错在字符串它没有所谓的“内部状态外部状态”。

通常讲字符串是享元的原因就是以下代码:

C#
string a = "Hello World";Console.WriteLine(Object.ReferenceEquals(a, "Hello World")); //True

当使用字符串直接量的时候,不论你写了多少个"Hello World",最终内存里面只有一个字符串对象。

运行时创建的字符串并不在此列,可以使些手段,强制在内存里面产生新的字符串。

C#
string a = "Hello World";Console.WriteLine(Object.ReferenceEquals(a, new String("Hello World".ToCharArray())));  //False

因为我们强行调用了new,所以这个字符串跟内存中的直接量"Hello World"对应的对象不是同一个。

有趣的是,C#还允许强制把一个字符串加入到(如果已经有了,就只是找出来)字符串池里面。

C#
string a = "Hello World";string b = String.Intern(new String("Hello World".ToCharArray()));Console.WriteLine(Object.ReferenceEquals(a,b) );

或者

C#
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这样子。

代码写起来就像下面的样子:

C#
    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千万个对象看看如何

C#
	    Element[] eles = new Element[30000000];
	    for (var i = 0; i < 30000000; i++)
	    {
		eles[i] = new Element();
	    }

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

QFOMR9}(NR%(T3`V3Q35MSY

接下来我们使用String.Intern来实现Flyweight:

C#
    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]);
	}
    }

同样来看看运行结果:


@XMI6L75IKU}S%NGI6L31@K

可以看到内存占用量的明显变化。

因为字符串对象的不可更改性质,使用了String.Intern之后,我们完全看不出前后color的区别,也就是说,修改前后的Element类是完全等效的,但是Flyweight为我们节约了大量的内存。

更多思考

这个典型的使用flyweight场景为我们揭示了享元外部状态内部状态的特征:像字符串一样不可更改的对象。GoF原书的例子中的字型对象Glyph也是如此。

String.Intern这种对象池的方式实现flyweight也值得借鉴,我们可以考虑自己设计flyweight的外部状态对象时使用类似的方式。



#转载请注明出处 www.skcircle.com 《少有人走的路》勇哥的工业自动化技术网站。


扫描二维码推送至手机访问。

版权声明:本文由7点博客发布,如需转载请注明出处。

本文链接:http://6dot.cn/?id=41

标签: .NET.NET框架
分享给朋友:

“C#字符串与享元(Flyweight)模式” 的相关文章

C# Modelbus crc16计算校验和程序

C# Modelbus crc16计算校验和程序

我们手里一个无刷电机,采用485的modelbus crc16协议来控制。因此需要一个计算校验和的工具。源码:C#using System;using System.Collections.Generic;using System.ComponentModel;usin...

计算代码执行时间,可以精确到十亿分之一秒

计算代码执行时间,可以精确到十亿分之一秒

注:.Net的Stopwatch类可以精确到1/10000毫秒, 有没有更精确的计时吗?见下面的代码。暂时没试过效果,大家可以试下。计算某个代码片段的执行时间,精确到CPU执行一条指令所用的时间(十亿分之一秒),可用于精确计算某个算法的执行时间。 代码:C#using Syste...

c#——表达式树在LINQ动态查询

c#——表达式树在LINQ动态查询

一般如果逻辑比较简单,只是存在有的情况多一个查询条件,有的情况不需要添加该查询条件简单方式这样操作就可以了C#public IQueryable<FileImport> DynamicChainedSyntax (IQueryable<FileImport&g...

如何在C#中调试LINQ查询

如何在C#中调试LINQ查询

在C#中我最喜欢的特性就是LINQ。使用LINQ, 我们可以获得一种易于编写和理解的简洁语法,而不是单调的foreach循环,它可以让你的代码更加美观。但是LINQ也有不好的地方,就是调试起来非常难。我们无法知道查询中到底发生了什么。我们可以看到输入值和输出值,但是仅此而已。当代码出现问题的时候,我...

C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped

C# .Net 多进程同步 通信 共享内存 内存映射文件 Memory Mapped

 节点通信存在两种模型:共享内存(Shared memory)和消息传递(Messages passing)。        内存映射文件对于托管世界的开发人员来说似乎很陌生,但它确实已经是很远古的技术了,而且在操作系统...

C#中CancellationToken和CancellationTokenSource用法

C#中CancellationToken和CancellationTokenSource用法

 继续谈一下异步中的任务取消机制CancellationToken和CancellationTokenSource。  之前做开发时,一直没注意这个东西,做了.net core之后,发现CancellationToken用的越来越平凡了。  这也难怪,原来.net framework使用异...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。