String - 兴趣解读
一、概述
String是我们平常用得最多的基元类型之一,虽然我们经常使用而且感到非常熟悉;但很多朋友只知道一个字符串的定义、使用或知道如何使用StringBuilder来达到高效构建字符串,但是有多少朋友有兴趣去了解背后的一些“不为我们知道的秘密”?
二、为什么把String加入到基元类型中
在以前的面向过程语言中,并没有String这个类型,定义一个字符串的方式则采用一个Char[],虽然提供了对字符串的操作、比较等函数,但是还是不够方便也不太符合面向对象做法,所以在面向对象语言中,string也加入了基元类型的队列中;
三、String核心特征immutable(不可变)
代表一个不可变的顺序字符集,也就是说一经创建,字符串编不能以任何方式进行修改;具有以下3个优点:
1. 允许在一个字符串上执行各种操作,而不实际地更改字符串;
2. 在操作或访问一个字符串的时候不会发生线程同步的问题;
3. 基于性能的考虑,String类型与CLR紧密集成,CLR知道String类型中定义的字段如何布局,而且CLR会直接访问,所以开发的时只好将String定义为密封类(Sealed);
四、被重写的两个方法GetHashCode与Equals
1. GetHashCode方法进行了重写,目的是为了满足两个字符串的判断;
2. Equals方法进行重写,其中Equals方法最终还是调用了GetHashCode方法来进行判断;
五、“==”、Equals、Compare,字符串判断到底用哪个好点?
1. String对“==”操作符进行重载,内部实现进行了空值判断后,再调用Equals进行判断(所以采用"=="操作符,不会抛出空指针异常)
2. String的Equals方法,因为调用Equals方法的时候,直接通过返回HashCode进行比较,效率最高,有可能对象为空,有可能会抛空指针异常,所以用的时候需要留意;
3. String的Compare方法:该方法是一个静态方法,内部实现是首先判断字符串的长度是否相等,如果长度不相等,直接返回结果,如果长度相等,则会采用逐个字符进行判断,如果方法中的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 a2 = string.IsInterned(a1);
var result = object.ReferenceEquals(a1, a2);
注:默认是不从拘留池中加载,而是直接采用ldstr的特殊指令获得字符串”abc”,但可以确认的是”元数据”跟“拘留池”中的字符串是用个对象;
八、案例分析
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);
结果请查看这里