From 4a0109cf44a93fe868f45c77f2eec3bbdc8a2e78 Mon Sep 17 00:00:00 2001 From: Francesco Vasco Date: Thu, 10 Sep 2020 07:50:21 +0200 Subject: [PATCH] Use StringBuilder in String.replace (2x faster) KT-41799 --- .../stdlib/jvm/src/kotlin/text/StringsJVM.kt | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt index daaf285785b..8bae08ac8fe 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/StringsJVM.kt @@ -61,10 +61,14 @@ public actual fun String?.equals(other: String?, ignoreCase: Boolean = false): B */ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun String.replace(oldChar: Char, newChar: Char, ignoreCase: Boolean = false): String { - if (!ignoreCase) - return (this as java.lang.String).replace(oldChar, newChar) - else - return splitToSequence(oldChar, ignoreCase = ignoreCase).joinToString(separator = newChar.toString()) + // prefer case-insensitive platform implementation + if (!ignoreCase) return (this as java.lang.String).replace(oldChar, newChar) + + return buildString(length) { + this@replace.forEach { c -> + append(if (c.equals(oldChar, ignoreCase)) newChar else c) + } + } } /** @@ -72,9 +76,29 @@ public actual fun String.replace(oldChar: Char, newChar: Char, ignoreCase: Boole * with the specified [newValue] string. */ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") -public actual fun String.replace(oldValue: String, newValue: String, ignoreCase: Boolean = false): String = - splitToSequence(oldValue, ignoreCase = ignoreCase).joinToString(separator = newValue) +public actual fun String.replace(oldValue: String, newValue: String, ignoreCase: Boolean = false): String { + // prefer case-insensitive platform implementation + if (!ignoreCase) return (this as java.lang.String).replace(oldValue, newValue) + var occurrenceIndex: Int = indexOf(oldValue, 0, ignoreCase) + // FAST PATH: no match + if (occurrenceIndex < 0) return this + + val oldValueLength = oldValue.length + val searchStep = oldValueLength.coerceAtLeast(1) + val newLengthHint = length - oldValueLength + newValue.length + if (newLengthHint < 0) throw OutOfMemoryError() + val stringBuilder = StringBuilder(newLengthHint) + + var i = 0 + do { + stringBuilder.append(this, i, occurrenceIndex).append(newValue) + i = occurrenceIndex + oldValueLength + if (occurrenceIndex >= length) break + occurrenceIndex = indexOf(oldValue, occurrenceIndex + searchStep, ignoreCase) + } while (occurrenceIndex > 0) + return stringBuilder.append(this, i, length).toString() +} /** * Returns a new string with the first occurrence of [oldChar] replaced with [newChar].