一个月以前我写了一篇讨论字符串的驻留(string interning)的文章,我今天将会以字符串的驻留为基础,进一步来讨论.NET中的string。string interning的基本前提是string的恒定性(immutability),即string一旦被创建将不会改变。我们就先来谈谈string的恒定性。
一、string是恒定的(immutable)
和其他类型比较,string最为显著的一个特点就是它具有恒定不变性:我们一旦创建了一个string,在managed heap 上为他分配了一块连续的内存空间,我们将不能以任何方式对这个string进行修改使之变长、变短、改变格式。所有对这个string进行各项操作(比如调用ToUpper获得大写格式的string)而返回的string,实际上另一个重新创建的string,其本身并不会产生任何变化。
String的恒定性具有很多的好处,它首先保证了对于一个既定string的任意操作不会造成对其的改变,同时还意味着我们不用考虑操作string时候出现的线程同步的问题。在string恒定的这些好处之中,我觉得最大的好处是:它成就了字符串的驻留。
CLR通过一个内部的interning table保证了CLR只维护具有不同字符序列的string,任何具有相同字符序列的string所引用的均为同一个string对象,同一段为该string配分的内存快。字符串的驻留极大地较低了程序执行对内存的占用。
对于string的恒定性和字符串的驻留,还有一点需要特别指出的是:string的恒定性不单单是针对某一个单独的AppDomain,而是针对一个进程的。
二、String可以跨AppDomain共享的(cross-appDomain)
我们知道,在一个托管的环境下,Appdomain是托管程序运行的一个基本单元。AppDomain为托管程序提供了良好的隔离机制,保证在同一个进程中的不同的Appdomain不可以共享相同的内存空间。在一个Appdomain创建的对象不能被另一个Appdomain直接使用,对象在AppDomain之间传递需要有一个Marshaling的过程:对象需要通过by reference或者by value的方式从一个Appdomain传递到另一个Appdomain。具体内容可以参照我的另一篇文章:用Coding证明Appdomain的隔离性。
但是这里有一个特例,那就是string。Appdomain的隔离机制是为了防止一个Application的对内存空间的操作对另一个Application 内存空间的破坏。通过前面的介绍,我们已经知道了string是恒定不变的、是只读的。所以它根本不需要Appdomain的隔离机制。所以让一个恒定的、只读的string被同处于一个进程的各个Application共享是没有任何问题的。
String的这种跨AppDomain的恒定性成就了基于进程的字符串驻留:一个进程中各个Application使用的具有相同字符序列的string都是对同一段内存的引用。我们将在下面通过一个Sample来证明这一点。
三、证明string垮AppDomain的恒定性
在写这篇文章的时候,我对如何证明string跨AppDomain的interning,想了好几天,直到我偶然地想到了为实现线程同步的lock机制。
我们知道在一个多线程的环境下,为了避免并发操作导致的数据的不一致性,我们需要对一个对象加锁来阻止该对象被另一个线程 操作。相反地,为了证明两个对象是否引用的同一个对象,我们只需要在两个线程中分别对他们加锁,如果程序执行的效果和对同一个对象加锁的情况完全一样的话,那么就可以证明这两个被加锁的对象是同一个对象。基于这样的原理我们来看看我们的Sample:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Artech.ImmutableString


{
class Program

{
static void Main(string[] args)

{
AppDomain appDomain1 = AppDomain.CreateDomain("Artech.AppDomain1");
AppDomain appDomain2 = AppDomain.CreateDomain("Artech.AppDomain2");

MarshalByRefType marshalByRefObj1 = appDomain1.CreateInstanceAndUnwrap(
"Artech.ImmutableString", "Artech.ImmutableString.MarshalByRefType") as MarshalByRefType;
MarshalByRefType marshalByRefObj2 = appDomain2.CreateInstanceAndUnwrap(
"Artech.ImmutableString", "Artech.ImmutableString.MarshalByRefType") as MarshalByRefType;

marshalByRefObj1.StringLockHelper = "Hello World";
marshalByRefObj2.StringLockHelper = "Hello World";

Thread thread1 = new Thread(new ParameterizedThreadStart(Execute));
Thread thread2 = new Thread(new ParameterizedThreadStart(Execute));

thread1.Start(marshalByRefObj1);
thread2.Start(marshalByRefObj2);

Console.Read();
}

static void Execute(object obj)

{
MarshalByRefType marshalByRefObj = obj as MarshalByRefType;
marshalByRefObj.ExecuteWithStringLocked();
}
}

class MarshalByRefType : MarshalByRefObject

{

Private Fields#region Private Fields
private string _stringLockHelper;
private object _objectLockHelper;
#endregion


Public Properties#region Public Properties
public string StringLockHelper

{

get
{ return _stringLockHelper; }

set
{ _stringLockHelper = value; }
}

public object ObjectLockHelper

{

get
{ return _objectLockHelper; }

set
{ _objectLockHelper = value; }
}
#endregion


Public Methods#region Public Methods
public void ExecuteWithStringLocked()

{
lock (this._stringLockHelper)

{
Console.WriteLine("The operation with a string locked is executed/n/tAppDomain:/t{0}/n/tTime:/t/t{1}",
AppDomain.CurrentDomain.FriendlyName, DateTime.Now);
Thread.Sleep(10000);
}
}

public void ExecuteWithObjectLocked()

{
lock (this._objectLockHelper)

{
Console.WriteLine("The operation with a object locked is executed/n/tAppDomain:/t{0}/n/tTime:/t/t{1}",
AppDomain.CurrentDomain.FriendlyName, DateTime.Now);
Thread.Sleep(10000);
}
}
#endregion
}
}
NET技术:深入理解string和如何高效地使用string,转载需保留来源!
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。