不可变字符串的好处之一是使Unicode支持更加容易。现代Unicode不能有效地适应固定大小的数据单元,这破坏了字符串索引和内存地址之间的一对一对应关系,而这正是可变字符串的优势所在。
过去,大多数西方应用程序使用单字节字符(各种基于ASCII的编码或EBCDIC...),因此您通常可以通过将字符串视为字节缓冲区(如传统的C应用程序中)有效地处理它们。
当Unicode比较新时,没有太多需要使用第一个16位以外内容的要求,因此Java在其String和StringBuffer中使用双字节字符。这使用了两倍的内存,并忽略了Unicode扩展超过16位可能出现的任何问题,但当时这很方便。
现在Unicode不再那么新鲜,虽然最常用的字符仍适合16位,但你不能真正假装基本多语言平面是唯一存在的。如果你想诚实地声称支持Unicode,则需要可变长度字符或更大的(32位?)字符单元。
使用可变长度字符,您不能再在O(1)时间内索引任意长度的字符串 - 除非有额外的信息,否则您需要从开头计算以确定第N个字符。这也破坏了可变字符串缓冲区的主要优势:无缝修改子字符串的能力。
幸运的是,大多数字符串操作实际上并不需要这种原地修改的能力。词法分析、语法分析和搜索都是按顺序迭代进行的,从头到尾。通用的搜索和替换从一开始就不是原地操作,因为替换字符串的长度不必与原始字符串相同。
连接大量的子字符串并不需要修改原字符串来提高效率。但是需要更加小心,因为(正如其他人指出的那样),一个天真的拼接循环可以很容易地通过为每个 N 个部分子字符串分配一个新字符串而变成 O(N^2)…
避免天真的拼接一种方法是提供一个可变的StringBuffer或ConcatBuffer对象,用于高效地进行拼接。另一种方法是包含一个不可变字符串构造函数,该构造函数将迭代器输入到要(高效)拼接的字符串序列中。
但是,更一般地,可以编写一个不可变字符串库,通过引用高效地进行拼接。这种字符串通常被称为“
rope”或“cord”,以表明它至少比组成它的基本字符串更重,但对于拼接目的,它更加高效,因为它根本不需要重新复制数据!
上面的维基百科链接说,“绳索”数据结构连接的时间复杂度为O(log N),但Okasaki的经典论文“
Purely Functional Data Structures”展示了如何在O(1)时间内完成连接。