不可变字符串的优缺点

时间:2012-08-07 21:57:10

标签: string language-agnostic language-design

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

4 个答案:

答案 0 :(得分:5)

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


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

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

现在Unicode并不是那么新,虽然最常用的字符仍然适合16位,但你无法真正摆脱假装基本多语言平面的存在。如果你想诚实地声称支持Unicode,你需要可变长度的字符或更大的(32位?)字符​​单元。

对于可变长度字符,您无法再在O(1)时间内索引到任意长度的字符串 - 除非有其他信息,您需要从头开始计算以确定第N个字符是什么。这也破坏了可变字符串缓冲区的主要优点:能够无缝地修改子字符串。

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


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

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

但是,更一般地说,可以编写一个通过引用有效连接的不可变字符串库。这种字符串通常被称为“rope”或“cord”,表明它至少比它所组成的基本字符串更重要,但是为了连接,它更多 more < / em>高效,因为它根本不需要重新复制数据!

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

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

Programming in Luabrilliant explanation的问题。


为了进一步思考,一些语言(如Common Lisp)同时具有非破坏性和破坏性功能,其他语言 - 包括不可变列表和可变列表(Python)。

引用a book on Common Lisp

  

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


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

同样地,Clojure has transients,它看起来像优雅的纯函数而不是不可变的数据结构,但内部使用可变状态来提高效率。