加入非常长的字符串列表,通过从头到尾简单地将它们添加起来是非常慢的:填充缓冲区逐渐增长,并且一遍又一遍地重新分配,导致额外的复制(并且大量使用垃圾回收器)。
连接长列表最有效的方法是始终从所有其他候选对中总长度最小的相邻字符串对开始连接;然而,这将需要进行复杂的查找以找到最佳对(类似于汉诺塔问题),仅为了将副本数量减少到最少会减慢速度。
您需要一个聪明的算法,使用“分而治之”的递归算法和一个很好的启发式算法,它非常接近于这个最优解。
- 如果没有字符串需要连接,则返回空字符串。
- 如果只有一个字符串需要连接,就直接返回它。
- 否则,如果只有两个字符串需要连接,则将它们连接起来并返回结果。
- 计算最终结果的总长度。
- 然后确定要从左边连接多少个字符串,直到达到总长度的一半,以确定“分割点”,将字符串集分成两个非空部分(每个部分必须包含至少一个字符串,分割点不能是要连接的字符串集中的第一个或最后一个字符串)。
- 如果最小部分包含至少两个字符串,则连接最小部分,否则连接其他部分(使用此算法递归地进行连接)。
- 回到开始(1.)以完成其他连接。
请注意,集合中的空字符串必须被忽略,就像它们不是集合的一部分一样。
许多库中默认的String.join(字符串表,可选分隔符)实现是缓慢的,因为它们使用从左到右的单调增连接。当你需要连接许多小字符串以生成一个非常大的字符串时,以上的分治算法会更快。
这种情况并不罕见,它发生在文本预处理器、生成器或HTML处理中(例如在“Element.getInnerText()”中,当元素是包含许多由许多命名元素分隔或包含的文本元素的大型文档时)。
以上策略适用于源字符串全部(或几乎全部)都要被垃圾收集以仅保留最终结果的情况。如果结果与源字符串列表一样长,则最佳选择是仅为其总长度分配一次最终大缓冲区,然后从左到右复制源字符串。
在两种情况下,这都需要对所有字符串进行第一遍扫描以计算它们的总长度。
如果使用可重新分配的“字符串缓冲区”,则如果“字符串缓冲区”不断重新分配,则无法很好地工作。但是,在执行第一遍扫描时,字符串缓冲区可能很有用,可以将一些短字符串预连接到其中,其合理(中等)大小为4KB(一页内存):一旦它满了,请将子集的字符串替换为字符串缓冲区的内容,并分配一个新的字符串缓冲区。
这可以显著减少源集中小字符串的数量。第一遍后,您将得到为结果分配的总长度,其中您将逐步复制第一遍收集到的所有剩余中等大小的字符串。当源字符串列表来自解析器函数或生成器时,此方法非常有效,因为在解析/生成结束之前,总长度是未知的:您只会使用中等大小的中间字符串缓冲区,最终生成最终缓冲区,而无需重新解析输入(以获取许多增量片段)或反复调用生成器(这将很慢,或者对于某些生成器或如果从解析器的输入消耗并且不能从头开始恢复,则无法正常工作)。
请注意,这个备注不仅适用于连接字符串,也适用于文件I/O:逐步写入文件也会受到重新分配或碎片化的影响:您应该能够预计生成的文件的总最终长度。否则,您需要一个经典的缓冲区(在大多数文件I/O库中实现,通常在内存中大小约为4KB的一页,但您应该分配更多,因为文件I/O速度较慢,并且当文件片段以过小的单位“簇”的形式递增分配时,碎片化变成了后续文件访问的性能问题;使用约1MB的缓冲区将避免由文件系统上的碎片化分配引起的大多数性能问题,因为片段将变得相当大;像NTFS这样的文件系统被优化为支持高达64MB的片段,超过这个大小,碎片化不再是一个明显的问题;对于Unix/Linux文件系统也是如此,它们倾向于仅对最大片段大小进行碎片整理,并且可以使用按2的幂组织的自由簇池有效地处理小片段的分配,例如1个簇、2个簇、4个簇、8个簇...,因此整理这些池是简单直接且成本不高的,并且可以在后台异步完成,当有低水平的I/O活动时进行。
在所有现代操作系统中,内存管理与磁盘存储管理相关联,使用内存映射文件来处理缓存:内存由存储支持,由虚拟内存管理器管理(这意味着您可以分配比物理RAM更多的动态内存,其余部分将在需要时分页到磁盘):您用于管理非常大的缓冲区的策略往往与页面的I/O性能有关:使用内存映射文件是一个好的解决方案,并且现在可以在非常大的(虚拟)内存中完成所有与文件I/O相关的工作。
String
是不可变的,你应该优先使用StringBuilder
而不是StringBuffer
。 - Elliott Frischs1 + s2 + s3 + s4
即可。如果需要使用循环连接字符串,则应明确使用StringBuilder
。 - Paul Boddingtonconcat
更快,因为没有创建StringBuilder
对象的开销。如果您的目标仅是连接两个字符串,则concat()
更快。如果您需要连接超过两个字符串,或者在将来的任何时候可能会添加多个连接,则我会怀疑显式的 StringBuilder 更快。 - nickb