Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,7 @@
## 2026-08-11 - Array의 toMutableList 할당 오버헤드 최적화
**학습:** 배열을 정렬하기 위해 `.toMutableList()`를 호출하면 새로운 `ArrayList` 객체와 내부 배열 객체가 할당되어 대규모 디렉토리를 순회할 때 가비지 컬렉션(GC) 부하를 유발합니다. 배열 복제가 필요한 경우 `.clone()`을 사용하면 하나의 배열 객체만 새로 할당되므로 더 효율적입니다.
**조치:** 디렉토리 파일 배열을 정렬하기 전에 복사할 때 `.toMutableList()` 대신 `.clone()`을 사용하여 불필요한 중간 컬렉션 할당을 제거하고 성능을 향상시켰습니다.

## 2024-09-05 - [성능 최적화: escapeHtml 배열 기반 조회로 변경]
**Learning:** Kotlin에서 문자를 매핑할 때, 핫 패스(예: HTML 이스케이프)에서 `when` 조건 분기 테이블을 사용하는 것보다 직접 배열 기반 조회(예: `Array<String?>(128)`)를 사용하는 것이 훨씬 더 빠릅니다. `ArrayIndexOutOfBoundsException`을 방지하기 위해 배열 접근 전에 반드시 범위 검사(예: `cInt < 128`)를 수행해야 합니다. 그리고 JaCoCo 테스트 커버리지를 유지하기 위해 속성을 `private object` 안에 넣고 `@JvmField` 어노테이션을 사용하여 암묵적 getter 생성을 방지해야 합니다. `Char.toInt()` (Kotlin 1.5 미만) 혹은 `Char.code`를 사용하여 문자 코드를 추출할 수 있습니다 (현재 1.3.72 환경에 맞춤).
**Action:** 빈번하게 호출되는 문자 변환이나 이스케이프 로직을 최적화할 때는 배열 조회를 사용하고, 커버리지 유지를 위해 `@JvmField`를 적극 활용합니다.
21 changes: 12 additions & 9 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,8 @@ fun String.escapeHtml(): String {
var sb: StringBuilder? = null
for (i in 0 until this.length) {
val c = this[i]
val replacement = when (c) {
'&' -> "&amp;"
'<' -> "&lt;"
'>' -> "&gt;"
'"' -> "&quot;"
'\'' -> "&#x27;"
'`' -> "&#x60;"
else -> null
}
val cInt = c.toInt()
val replacement = if (cInt < 128) Constants.HTML_ESCAPE_TABLE[cInt] else null
if (replacement != null) {
if (sb == null) {
sb = StringBuilder(this.length + 16)
Expand Down Expand Up @@ -496,6 +489,16 @@ fun help() {
}

private object Constants {
@JvmField
val HTML_ESCAPE_TABLE: Array<String?> = Array<String?>(128) { null }.apply {
this['&'.toInt()] = "&amp;"
this['<'.toInt()] = "&lt;"
this['>'.toInt()] = "&gt;"
this['"'.toInt()] = "&quot;"
this['\''.toInt()] = "&#x27;"
this['`'.toInt()] = "&#x60;"
}

@JvmField
val defaultSensitiveFiles = listOf(".git", ".env", ".ssh", ".htpasswd", ".htaccess", "id_rsa", "id_ed25519", "secrets.yml", ".html4ignore", ".DS_Store", ".aws", ".kube", ".npmrc", ".gnupg", "config.json", "credentials.json")

Expand Down
21 changes: 21 additions & 0 deletions src/test/kotlin/html4tree/Benchmark.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package html4tree

import kotlin.system.measureTimeMillis

object Benchmark {
@JvmStatic
fun main(args: Array<String>) {
val testString = "This is a <test> string with & some \"special\" 'characters' like `this`."
// Warm up
for (i in 0..10000) {
testString.escapeHtml()
}

val time = measureTimeMillis {
for (i in 0..1000000) {
testString.escapeHtml()
}
}
println("escapeHtml took ${time}ms")
}
}
Loading