diff --git a/.jules/bolt.md b/.jules/bolt.md index 83cc604..1e61273 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -5,3 +5,7 @@ ## 2024-05-24 - Loop Allocation Hot Paths **Learning:** Rendering directory entries with repeated string concatenation and list-based exclusion lookups creates avoidable allocation and lookup cost in large directories. **Action:** Use `StringBuilder` for entry rendering and a `Set` for excluded file names. + +## 2024-08-01 - Chained String Replace +**Learning:** Using chained `.replace()` calls on a String for frequent operations (like HTML escaping) is inefficient due to multiple intermediate allocations. +**Action:** Use a single-pass loop with a lazily-initialized `StringBuilder` for string manipulation functions called frequently on hot paths to minimize allocations and improve performance. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 2e2809f..b237de5 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -49,12 +49,31 @@ fun go(topDir: String, maxLevel: Int) { } fun String.escapeHtml(): String { - return this.replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("\"", """) - .replace("'", "'") - .replace("`", "`") + // ⚡ Bolt: Use a single-pass loop with a lazily-initialized StringBuilder + // to avoid intermediate string allocations from chained replace calls. + var sb: java.lang.StringBuilder? = null + for (i in this.indices) { + val c = this[i] + val replacement = when (c) { + '&' -> "&" + '<' -> "<" + '>' -> ">" + '"' -> """ + '\'' -> "'" + '`' -> "`" + else -> null + } + if (replacement != null) { + if (sb == null) { + sb = java.lang.StringBuilder(this.length + 16) + sb.append(this, 0, i) + } + sb.append(replacement) + } else { + sb?.append(c) + } + } + return sb?.toString() ?: this } fun String.urlEncodePath(): String {