像是js,以及c#等一系列的语言,推出任何一个特征都要和c比一比,你知道的:)。

string的不可变性也是一样,你看:

C的string是可以改变的。
你可以

char str[] = "Foo";
str[0] = ‘G'

str的内容确实被改变了。可以通过print来证明。打印内容,也打印指针。指针不变,内容变了。

2.js的不可改.如下的代码,没有任何效果,也可以log出来证明
var str = "foo";
str[0] = 'g' ;

因为,语义上来说,这样做意味着原位修改str的内存区内容

但是你可以
var str = "foo";
var = 'bar'

因为原位置的内存没有修改,是新分配的。只是打印不了指针,不好直接证明。要么就看代码,那就啰嗦了。非常非常的啰嗦。

不少语言对字符串的实现,都采用了限定不变性。不能说这是不费解的:为何去创建一个新字符串,而不是去修改它呢?毕竟c就是这样做的。

字符串的不可变性有不少好处,但是如果程序员忘了这一点也会导致问题。

如下的c#代码会创建10000个字符串对象,但是除了最后一个,后面都是垃圾需要被回收的。

string s = string.Empty;

for (int i = 0; i < 10000; i++)
{
    s += "x";
}

通过cld profiler可以看到这张图。

clipboard.png

这样的场景应该使用StringBuilder。而不是字符串连接。

StringBuilder sb =  StringBuilder();
for( i = ; i < 10000; i++)
{
    sb.Append();
}
string x = sb.ToString();

针对js,也可以自己做一个StringBuilder

function StringBuilder() {
    this._array = [];
    this._index = 0;
}

StringBuilder.prototype.append = function (str) {
    this._array[this._index] = str;
    this._index++;
}

StringBuilder.prototype.toString = function () {
    return this._array.join('');
}

为何设计者决定实现不可变字符串呢?

这个和存储优化有关。字符串都存储在俘虏池(intern pool)内,因为string都在intern pool内,所以,相同的字符串引用一致。这样的存储效果很好。

可是,如果允许修改string,那么a修改了,b也会跟着修改,d也是。可是,语义上来说,我们认为他们是不同的变量,不应该联动。所以字符串不可变就是intern pool存储的代价。

string a = "xx";
string b = "xx";
string c = "x";
string d = String.Intern(c + c);
Console.WriteLine((object)a == (object)b); Console.WriteLine((object)a == (object)d);

String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,c# 查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;

不变性,对多线程应用是有好处的。
另一个场景是字符串可以用于哈希表的键,而键是不可以改变值的。

string key = "abc";
Hashtable ht = new Hashtable();
ht.Add(key, 123);

key = "xbc";

Console.WriteLine(key); // xbc
Console.WriteLine(ht["abc"]); // 123

Reco
4.6k 声望541 粉丝

敢作敢为