深入理解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

        
Public Properties

        
Public Methods
    }

}

NET技术深入理解string和如何高效地使用string,转载需保留来源!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。