由于字符串在.NET中是不可变的,为什么要将它们复制用于Substring
或Split
等简单操作?例如,通过保留char[] value
,int start
和int length
,可以创建子字符串以简单地指向现有字符串,并且我们可以节省复制字符串的开销,这很简单操作。所以我想知道,为什么选择复制字符串进行此类操作?
例如,这样做是为了支持StringBuilder
的当前实现吗?或者,当只需要几个字符时,为了避免保留对大char[]
的引用?或者你能想到的任何其他原因?你能为这种设计提出利弊吗?
正如@cletus所提到并得到@Jon Skeet的支持,这更像是在讨论为什么.NET字符串在这方面与Java的构建方式不同。
答案 0 :(得分:10)
这基本上就是Java的工作方式。 .NET方式有一些好处,IMO:
20+2*n
个字节。在Java中,你有大小的数组(12 + 2*n
)字节和字符串本身(24字节:对象开销,引用,开始和计数;它还会缓存哈希值,如果它曾经计算过它)。因此对于一个空字符串,与Java的36相比,.NET版本需要大约20个字节。当然这是最糟糕的情况,并且它只会是“常数差异” - 但如果你使用了很多独立的字符串,那么最终意义重大。更多垃圾收集器也可以查看。当然,当上方的别名出现时,其优势在于需要更少的空间。
最终它将取决于您的使用情况 - 编译器和运行时无法预测您的确切代码中更有可能使用哪种使用模式。
当前的字符串表示可能还有互操作的好处,但我对此肯定不够了解。
编辑:我不确定为什么你的问题收到了这么多有些恶意的答案。它当然不是表示字符串的“愚蠢”方式,它显然有效。在这种情况下,对数据丢失和复杂性的担忧几乎就是FUD,我相信 - Java字符串实现简单而强大。我个人怀疑在大多数程序中,.NET的处理方式更有效率,我怀疑MS做了研究来检查,但肯定会出现这种情况。 “共享”模式效果更好。答案 1 :(得分:5)
如果你重复使用相同的字符串来返回子字符串,当主字符串超出范围时会发生什么?
在最好的情况下,它需要保留在内存中,并且在所有子字符串也被释放之前无法收集,所以你最终会使用更多的内存。
这只是其中一个问题。
实际上,垃圾收集器几乎没有选择:
将整个原始字符串保留在内存中,即使只使用非常短的子字符串也可以使用。
释放原始字符串中未引用的部分,并仅保留子字符串。这会产生很多碎片,这意味着垃圾收集器可能不得不在某些时候重新定位字符串:无论如何我们最终会制作副本。
我确信它有它的用例,并且有时在处理子字符串时更有效率(比如在处理大型XML文档时)。
但是,正如Jon所说,Java字符串对象需要更多空间,所以如果你有很多小字符串,它们实际上可能会使用比.Net更多的内存。
这是一种权衡 我认为,如果您处理内存的管理方式并且需要具有完全可预测的行为,那么Java或.Net都不是最好的工具。
我们使用垃圾收集器,因为它们经过优化,可以在绝大多数情况下高效工作
知道它们是如何工作的很重要,但是它们是否重新使用字符串更多地是对底层框架的优化,它不应该在表面上泄漏太多。
毕竟,GC是为了帮助我们。
答案 2 :(得分:1)
在你的子字符串示例中,这意味着每次我们引用“new”字符串时我们都会重新执行子字符串逻辑。单凭这一开销就很明显我们为什么要复制字符串。
答案 3 :(得分:0)
我想关键是突出显示:
之间的区别如果字符串是#2,你所说的会有用。但是,虽然字符串是不可变的,但它们可以被销毁。
正如您所看到的,他们有自己的成本:
很容易理解为什么#1会更好:)
(但我并不是说#2很糟糕或愚蠢)
答案 4 :(得分:0)
相信我,如果字符串不是不可变的,你会讨厌它。举一个Java的例子:java.util.Date是可变的,这是一场噩梦。 Basiclaly它强制任何接收数据作为参数或函数返回的人必须防御性地复制它。
我不能说.Net字符串,但Java的子字符串操作实际上确实引用了主字符串,这意味着Java中的每个字符串都有大约16-20字节的开销(指向字符串的指针,起始索引,结束索引,长度可能还有别的东西)。这有利有弊。从记忆饥饿的角度来看,它可能是一个真正的“陷阱”。在我工作的一个项目中,我们使用了大量的内存。事实证明,我们正在接收大量消息(数千个字符)并使用子字符串处理它们。因为子字符串保留了对原始字符串的引用,所以原始字符串从未被清除。
现在你可以通过使用String构造函数解决这个问题,但这并不明显,很多人都不知道。
基本上,你所谈论的字串是真正的蠕虫。小心你想要的。
答案 5 :(得分:0)
如果字符串对象包含对字符数据的引用,那么这意味着大多数字符串将是两个对象而不是一个。