技术开发 频道

深入理解String 高效使用精妙设计

    【IT168 评论】无论你所使用的是哪种编程语言,我们都不得不承认这样一个共识:string是我们使用最为频繁的一种对象。但是string的常用性并不意味着它的简单性,而且我认为,正是由于string的频繁使用才会促使其设计人员在string的设计上花大量的功夫。所以正是这种你天天见面的string,蕴含了很多精妙的设计思想。

  一个月以前我写了一篇讨论字符串的驻留(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
    }
}
0
相关文章