6

一些语言(C# 或 Java)有不可变的字符串,而其他语言(例如 Ruby)有可变的字符串。这些设计选择背后的原因是什么?

4

4 回答 4

5

不可变字符串之所以好的原因之一是它使 Unicode 支持更容易。现代 Unicode 不再能有效地适应固定大小的数据单元,这会破坏字符串索引和内存地址之间的一一对应关系,这为可变字符串提供了优势。


过去,大多数西方应用程序使用单字节字符(各种基于 ASCII 的编码,或 EBCDIC...),因此您通常可以通过将字符串视为字节缓冲区来有效地处理它们(就像在传统的 C 应用程序中一样)。

当 Unicode 相当新时,对前 16 位之外的任何内容都没有太多要求,因此 Java 使用双字节字符作为其Strings(和StringBuffers)。这使用了两倍的内存,并且忽略了超过 16 位的 Unicode 扩展可能出现的任何问题,但在当时很方便。

现在 Unicode 已经不是那么新了,虽然最常用的字符仍然适合 16 位,但你不能假装基本多语言平面就是所有存在的东西。如果您想诚实地声明 Unicode 支持,您需要可变长度字符或更大(32 位?)字符​​单元。

使用可变长度字符,您不能再在 O(1) 时间内索引到任意长度的字符串——除非附加信息,否则您需要从头开始数以找出第 N 个字符是什么。这也扼杀了可变字符串缓冲区的主要优势:无缝修改子字符串的能力。

幸运的是,大多数字符串操作实际上并不需要这种就地修改功能。词法分析、解析和搜索都在顺序、迭代的基础上从头到尾进行。一般的搜索和替换从一开始就没有到位,因为替换字符串不必与原始字符串的长度相同。


连接大量子字符串实际上也不需要就地修改来提高效率。但是,您确实需要更加小心,因为(正如其他人指出的那样)通过为 N 个部分子字符串中的每一个分配一个新字符串,一个简单的连接循环很容易成为 O(N^2) ......

避免幼稚连接的一种方法是提供一个可变对象StringBufferConcatBuffer旨在有效进行连接的对象。另一种方法是包含一个不可变的字符串构造函数,它将迭代器带入要(有效)连接的字符串序列。

但是,更一般地说,可以编写一个通过引用有效连接的不可变字符串库。这种字符串通常被称为“绳索”或“绳索”,表明它至少比组成它的基本字符串重一点,但出于连接目的,它有效,因为它不需要完全重新复制数据!

上面的维基百科链接说“绳索”数据结构是 O(log N) 连接,但 Okasaki 的开创性论文“ Purely Functional Data Structures ”显示了如何在 O(1) 时间内进行连接。

于 2012-08-15T19:28:53.280 回答
2

至少在 Java 的情况下,使字符串不可变的部分原因是为了安全和线程安全。Java 非常重视运行时安全性(它最初的设计目的是允许机顶盒和 Web 浏览器下载和执行远程内容,而不会损害主机系统)。为了帮助提高安全性,字符串是不可变的并且不能被子类化。这意味着 Java 运行时可以传递和接收来自用户的字符串,同时保证字符串的值保持不变(也就是说,攻击者不能子类化字符串,将看似有效的字符串传递给函数,但是然后稍后更改该值以访问错误的数据,或者使用多个线程以使字符串在某一点看起来正确,但稍后会发生突变)。

此外,不变性在多线程系统中具有效率优势,因为不必对字符串进行锁定。它还可以轻松实现子字符串操作,因为许多字符串可以共享相同的底层字符数组,尽管起点和终点不同。

于 2012-08-07T22:05:47.543 回答
1

至于缺点,不可变字符串需要互补的可变数据结构(即字符串缓冲区)以实现经济的附加、重新排序和其他类似操作。

在不可变结构上执行的此类操作将需要不合理数量的资源。

Lua 编程对这个问题有很好的解释


为了进一步反映,一些语言(如 Common Lisp)同时具有非破坏性和破坏性功能,而另一些语言则同时具有不可变和可变列表(Python)。

引用一本关于 Common Lisp 的书

如果作业充满危险,为什么不在语言中省略它呢?有两个原因:表现力和效率。分配是更改共享数据的最清晰方法。并且赋值比绑定更有效。绑定创建一个新的存储位置,该位置分配存储,这会消耗额外的内存(如果绑定永远不会超出范围)或对垃圾收集器征税(如果绑定最终确实超出范围)。


然而,作为一个反例,许多 JavaScript(具有不可变字符串)解释器在实现级别将字符串视为可变数组。

同样,Clojure 也有瞬态,它看起来像不可变数据结构上的优雅纯函数,但内部使用可变状态来提高效率。

于 2012-08-07T22:23:02.003 回答
1

如果您考虑一下,所有基本数据类型都是不可变的。您不会将整数 10 更改为 11,而是将 10 替换为 11。使字符串基本且不可变,从而允许池化和其他无法进行的优化。

于 2012-08-07T22:07:05.573 回答