我知道string
是不可变的,StringBuilder
是可变的。但任何人都可以解释下面的代码输出吗?由于两者都是参考类型,为什么它们会有不同的结果?
String s1 = "hello";
String s2 = "hello";
Console.WriteLine(s1 == s2); //true
Console.WriteLine(Object.ReferenceEquals(s1, s2)); //true
StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = new StringBuilder("hello");
Console.WriteLine(sb1 == sb2); //false
Console.WriteLine(Object.ReferenceEquals(sb1, sb2)); //false
答案 0 :(得分:10)
由于两者都是参考类型,为什么它们会有不同的结果?
因为string
个对象已经过高度优化。特别是,由于它们是不可变的,因此编译器可以对它们进行实习以防止重复。
如果有两个不同的string
对象,它们都代表完全相同的字符串(如示例所示),编译器将识别该对象并仅维护实际字符串对象的一个实例。
结果是,就编译器而言,s1
和s2
对象实际上都是同一个对象,甚至引用内存中的相同位置。
这种簿记发生在一个叫做“实习生表”的幕后,但这并不是你需要担心的事情。重要的是所有字符串文字都由编译器默认实现。
对于StringBuilder
对象,不会发生同样的事情,因为它们不是不可变的。它们旨在允许您修改字符串对象,因此,优化没有多大意义。这就是为什么您的sb1
和sb2
对象实际上被视为两个不同的对象。
经验法则很简单:默认情况下使用string
,或者当您需要单个不可变字符串时。如果要多次修改相同的字符串,请使用StringBuilder
,例如在循环或其他相对较短的代码段中。
答案 1 :(得分:4)
宣布
时String s1 = "hello";
String s2 = "hello";
编译器足够聪明,知道这两个字符串是(并且将永远是)相同的,因此它只存储"hello"
一次并创建s1
和s2
作为别名相同的物理记忆。稍后,当您测试相等性时,两者是相等的,因为它们本质上是相同的变量。
另一方面,当你宣布
时StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = new StringBuilder("hello");
编译器创建两个变量(因为它们都是可变的,但恰好被初始化为相同的值)。它将字符串"hello"
复制到每个字符串中,但现在有2个副本,因为每个副本可能会在以后更改。因此,即使它们的内容相同,它们也是位于不同物理内存位置的2个不同实体,因此对象相等性测试失败。
答案 2 :(得分:3)
默认情况下,当比较两个引用类型对象时,仅当两个引用相等时,result true ,这意味着两个操作数必须引用相同的object实例。因为sb1和sb2引用的对象是两个不同的对象,所以StringBuilders的比较结果为false。
但是String类以这种方式覆盖了相等运算符,它不会通过引用来比较对象,而是通过它们的值来比较,因为这种行为非常直观并且是程序员所期望的。这就解释了为什么s1 == s2返回true。
Object.ReferenceEquals(s1,s2)也返回true的原因(尽管似乎s1和s2引用了不同的string实例)称为string interning。它导致CLR将所有出现的相同字符串文字(例如示例中的“hallo”和“hallo”)放入每个应用程序的内部字符串池中,因此s1和s2实际上都引用了字符串“hallo”的相同实例。这是可能的,因为字符串是不可变的。
答案 3 :(得分:1)
当您执行以下操作时,您将比较两个不同的StringBuilder对象而不是它们的值:
StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = new StringBuilder("hello");
Console.WriteLine(sb1 == sb2); //false
这是你比较他们的价值观的方法:
Console.WriteLine(sb1.ToString() == sb2.ToString());