一个字符串不可变有什么优点?

21

我曾经学习过字符串是不可变的优点,因为这有助于提高内存性能。

有人能给我解释一下吗?我在网上找不到相关信息。


1
可能是什么是不可变的?的重复问题。虽然那个问题的标题不同,但它包含了一个关于优缺点的问题,其中至少有一个答案(由Imagist提供)回答得非常好。 - Jerry Coffin
7个回答

34

不可变性(针对字符串或其他类型)可以带来许多好处:

  • 它使得代码更易于理解,因为您可以对变量和参数进行假设,而否则您无法进行。
  • 它简化了多线程编程,因为从一个无法更改的类型中读取始终是安全的。
  • 它允许通过将相同的值组合在一起并从多个位置引用来减少内存使用。Java和C#都执行字符串池化以减少嵌入代码中的文本字符串的内存成本。
  • 它简化了某些算法(例如采用回溯或值空间划分的算法)的设计和实现,因为以前计算的状态可以在后面重复使用。
  • 不可变性是许多函数式编程语言的基础原则-它允许代码被视为从一个表示转换为另一个表示的一系列变换,而不是一系列变异。

不可变字符串也有助于避免将字符串用作缓冲区的诱惑。 C/C++程序中的许多缺陷与使用裸字符数组组合或修改字符串值导致的缓冲区溢出问题有关。将字符串视为可变类型鼓励使用更适合于缓冲区操作的类型(请参阅.NET或Java中的StringBuilder)。


1
但是,如果您使用写时复制范例,则可以同时拥有不可变字符串和私有副本的优点,并且在需要时可以进行原地修改,例如用于快速解析。StringBuilder只是解决不可变字符串连接缓慢的一种方法,并且与有效的堆管理系统相关联的COW模式相比几乎没有好处。 COW可以使您的缓冲区访问同时安全又快速。 - Arnaud Bouchez
其中一些参数似乎将“const”参数(与Java中的“final”相同)与所引用对象的不可变性混淆了。 - Eric Grange
@LBushkin,虽然你在你的要点中提出了一些好的论点,但是你的“不可变字符串也有助于避免...”段落是有误导性的。StringBuilder解决的问题与C中的缓冲区覆盖完全不同——它只是针对字符串连接的优化,因为连接不可变字符串很慢——每次连接步骤都需要创建两个新对象并复制所有字符。 - Alexey
@ArnaudBouchez,COW并没有不可变对象的所有优点,它只为大多数字符串情况提供了一些性能上的优势。然而,“它使得代码更易于理解”的这个优点是COW模式所没有提供的不可变对象的优点。 - Alexey
@Alexey:在某些语言中,可以指定类型为“String”的变量s1s2应分别持有对“StringRef”的单独实例的唯一引用,并且s1=s2;应该被执行为s1.CopyFrom(s2);,而不改变s1所引用的“StringRef”的实例。Java和.NET语言不支持这样的概念,但C++支持(至少在不必与其他.NET语言交互的代码中)。 - supercat

9
考虑一下另一种情况。Java没有const限定符。如果String对象是可变的,那么任何传递字符串引用的方法都可能具有修改字符串的副作用。不可变字符串消除了防御性拷贝的需要,并降低了程序错误的风险。

5

不可变字符串易于复制,因为您无需复制所有数据 - 只需复制指向数据的引用或指针。

任何类型的不可变类在多线程环境下更易于使用,只需要同步销毁即可。


1

也许我的回答已经过时了,但可能有人会在这里找到新的信息。

为什么Java中的字符串是不可变的,以及它的好处:

  • 您可以在线程之间共享字符串,并确保它们中没有一个会更改字符串并混淆另一个线程
  • 您不需要锁定。多个线程可以在没有冲突的情况下使用不可变字符串
  • 如果您刚刚收到一个字符串,您可以确信在此之后没有人会更改其值
  • 您可以拥有许多字符串副本-它们将指向单个实例,只有一个副本。这可以节省计算机内存(RAM)
  • 您可以进行子字符串操作而无需复制-通过创建指向现有字符串元素的指针。这就是为什么Java子字符串操作实现如此快的原因
  • 不可变字符串(对象)更适合用作哈希表中的键

0

从根本上讲,如果一个对象或方法希望向另一个对象传递信息,它有几种方法可以实现:

  1. 它可以提供一个包含信息的可变对象的引用,并且接收方承诺永远不会修改它。

  2. 它可以提供一个包含数据的对象的引用,但它并不关心其内容。

  3. 它可以将信息存储到一个可变对象中,预期的数据接收者知道这个对象(通常是由该数据接收者提供的对象)。

  4. 它可以返回一个包含信息的不可变对象的引用。

其中,方法#4是最容易的。在许多情况下,可变对象比不可变对象更易于处理,但是在没有将信息复制到其他位置之前,没有简单的方法可以与“不可信”代码共享可变对象中的信息。相比之下,持有引用的不可变对象中保存的信息可以通过仅分享该引用的副本来轻松地共享。


0

想象一下各种字符串坐在一个公共池中。然后,字符串变量指向池中的位置。如果您复制一个字符串变量,则原始字符串和副本共享相同的字符。这种共享效率超过了通过提取子字符串和连接来进行字符串编辑的低效性。


0

a) 想象一下没有使字符串不可变的StringPool设施,这是不可能的,因为在字符串池的情况下,一个字符串对象/字面量例如“Test”被许多引用变量引用,所以如果其中任何一个更改值,则其他变量也会自动受到影响,例如: String A =“Test”和String B =“Test” 现在String B调用“Test”。toUpperCase()将同一对象更改为“TEST”,因此A也将是“TEST”,这是不可取的。
b) Java中String不可变的另一个原因是允许String缓存其哈希码,由于Java中的String是不可变的,因此它缓存其哈希码并且不会在每次调用String的hashcode方法时计算,这使得它作为哈希映射键非常快速。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接