String - 兴趣解读

一、概述

String是我们平常用得最多的基元类型之一,虽然我们经常使用而且感到非常熟悉;但很多朋友只知道一个字符串的定义、使用或知道如何使用StringBuilder来达到高效构建字符串,但是有多少朋友有兴趣去了解背后的一些不为我们知道的秘密

二、为什么把String加入到基元类型中

在以前的面向过程语言中,并没有String这个类型,定义一个字符串的方式则采用一个Char[],虽然提供了对字符串的操作、比较等函数,但是还是不够方便也不太符合面向对象做法,所以在面向对象语言中,string也加入了基元类型的队列中;

三、String核心特征immutable(不可变)

代表一个不可变的顺序字符集,也就是说一经创建,字符串编不能以任何方式进行修改;具有以下3个优点:

1.     允许在一个字符串上执行各种操作,而不实际地更改字符串;

2.     在操作或访问一个字符串的时候不会发生线程同步的问题;

3.     基于性能的考虑,String类型与CLR紧密集成,CLR知道String类型中定义的字段如何布局,而且CLR会直接访问,所以开发的时只好将String定义为密封类(Sealed);

四、被重写的两个方法GetHashCodeEquals

1.     GetHashCode方法进行了重写,目的是为了满足两个字符串的判断;

2.     Equals方法进行重写,其中Equals方法最终还是调用了GetHashCode方法来进行判断;

五、“==”、EqualsCompare,字符串判断到底用哪个好点?

1.     String对“==”操作符进行重载,内部实现进行了空值判断后,再调用Equals进行判断(所以采用"=="操作符,不会抛出空指针异常)

2.     StringEquals方法,因为调用Equals方法的时候,直接通过返回HashCode进行比较,效率最高,有可能对象为空,有可能会抛空指针异常,所以用的时候需要留意;

3.     StringCompare方法:该方法是一个静态方法,内部实现是首先判断字符串的长度是否相等,如果长度不相等,直接返回结果,如果长度相等,则会采用逐个字符进行判断,如果方法中的CultureInfo不为空,则判断的过程中会逐个字符进行展开(这里涉及到语言的问题,如果采用德语会把"β"展开为"ss",所以”strasse”staβe的判断结果是相同的); 

注:如果一般情况下,建议采用Equals进行判断,效率最高,但如果无法确保方法Equals的调用者是否不为null,建议还是采用==或者 "XXXX".Equals(obj),如果需要用到多语言(国际化)判断的时候,可以考虑用Compare

六、拘留池(Interning

字符串操作(比如Compare)的做法是很多程序常见的操作,这样的操作可能造成内存中复制同一个字符串的多个实例(算法内部操作导致),为了达到节省内存的效果,CLR采用了一种叫“字符串留用的技术”(String Interning),开辟了一块名为“拘留池”的空间专门用于存放字符串,而拘留池在程序初始化的时候,会把元数据默认加载到“拘留池”中,而且不会受到垃圾回收器的影响,只有在程序被关闭的时候才会释放“拘留池”中的资源;

七、存储方式,大致分为以下3种:

1.     以常量的方式来定义很保存字符串,比如:var value = "abc";

2.     以对象的方式来保存到堆中:比如 var value = new string('a');

3.     以对象的方式构造然后存放到拘留池(其实常量的方式定义后,默认也会把字符串加入到驻留池中); 

可以参考以下代码和内存分配图进行理解:

    //验证字符串常量默认加载到元数据,默认会把元数据中的字符串加载到拘留池
var data = "abc";       //此声明方式,会把该变量定义为字符串常量,然后存入元数据中
    var a = "a";            //同上
    var b = "b";            //同上
    var c = "c";            //同上
    var ab = a + b;         //根据线程栈上的a、b地址获取到堆上的a、b实例,然后把两个实例的结果进行运算后产生一个新对象,最后新对象地址赋给ab变量
    var abc = a + b + c;    //同上
    var abResult = string.IsInterned(ab);        //返回结果为null,也就是说没有把字符串"ab"存入拘留池中;
    var abcResult = string.IsInterned(abc);     //返回结果为"abc",也就是说已经把字符串(这里是常量)"abc"存放到拘留池中;
    var empty = string.Empty;        //初始化的时候,从String类型对象中获取静态属性Empty的数据;
    var strEmpty = "";               //产生一个值为空的String对象;

    var a1 = "abc";
    var a2 = string.IsInterned(a1);
    var result = object.ReferenceEquals(a1, a2);    

            

     注:默认是不从拘留池中加载,而是直接采用ldstr的特殊指令获得字符串”abc”,但可以确认的是”元数据”跟“拘留池”中的字符串是用个对象; 

八、案例分析

  1. 以下代码的HashCode是否相同,它们是否是同个对象;

      var A = "ab" + "c";
      var B = "abc";
 
  2. 以下代码的HashCode是否相同,他们是否是同个对象:  
      var A = Console.ReadLine();   //输入"abc"
      var B = Console.ReadLine();   //输入"abc"
 
  3. 以下代码的HashCode是否相同,他们是否是同个对象:
      var A = Console.ReadLine(); //输入"abc"
      var B = Console.ReadLine(); //输入"abc"
      var A = string.Intern(B);

 结果请查看这里

posted @ 2014-01-01 23:38  Smlant.  阅读(1366)  评论(5编辑  收藏  举报