1. 简介

Kotlin 标准库功能非常丰富,不仅提供了强大的集合操作函数式编程支持,还内置了大量实用的字符串工具类。

要真正掌握标准库的能力,最好的方式是通过一个实际问题,演示多种解决方案。本文就以“将字符串中每个单词的首字母大写”为例,带你深入理解 Kotlin 的表达力和灵活性。

这类需求在实际开发中并不少见——比如标题格式化、用户输入规范化等场景。虽然看似简单,但不同实现方式在性能、内存占用和边界处理上差异明显,值得我们细细推敲。

2. 最直接的解法

最直观的思路是:把字符串按空格拆分成单词数组,逐个处理首字母大写,再拼接回去。

✅ 示例代码如下:

input
  .split(' ')
  .joinToString(" ") { it.replaceFirstChar(Char::uppercaseChar) }

这里的关键点:

  • split(' ') 将字符串按单个空格分割
  • joinToString 接收一个 transform lambda,对每个元素做转换后再拼接
  • replaceFirstChar(Char::uppercaseChar) 是 Kotlin 字符串 API 提供的安全首字符大写方法(比 toUpperCase() 更安全,避免全转大写)

⚠️ 踩坑提醒:这个方案简洁明了,但会生成三个临时对象 —— 原始字符串、分割后的 List、最终结果字符串。也就是说,内存消耗约为原始字符串的三倍。

对于大多数业务场景这完全不是问题,但如果处理的是超长文本(如日志分析、文件导入),就需要更优方案。

3. 更节省内存的方案

为了减少中间集合的创建,我们可以改用 Sequence 实现惰性求值,逐个提取单词并处理。

✅ 代码实现如下:

sequence {
    var startIndex = 0
    while (startIndex < input.length) {
        val endIndex = input.indexOf(' ', startIndex).takeIf { it > 0 } ?: input.length
        yield(input.substring(startIndex, endIndex))
        startIndex = endIndex + 1
    }
}.joinToString(" ") { it.replaceFirstChar(Char::uppercaseChar) }

📌 关键优化点:

  • 使用 sequence {} 构建器实现懒加载,避免一次性加载所有单词到内存
  • 手动遍历字符串,通过 indexOf 定位空格位置,分段提取子串
  • 最终仍使用 joinToString 拼接结果

💡 内存开销从原来的 3x 降低到约 2x,适合处理较大字符串。若需进一步优化,可考虑流式 IO 处理。

4. 支持多个连续空白字符

现实中的输入往往不规范,比如存在多个空格、制表符或换行符。上述方案遇到 "hello world" 这种情况就会出错。

方案一:正则分割(推荐新手)

直接使用正则表达式匹配非单词字符(包括空格、标点等)进行分割:

input
  .split("\\W+".toRegex())
  .joinToString(" ") { it.replaceFirstChar(Char::uppercaseChar) }

📌 注意:\W+ 表示一个或多个非单词字符(即非字母数字下划线),能有效处理各种空白分隔符。

方案二:手动扫描(高性能场景)

如果追求极致性能且想避免正则开销,可以结合自定义辅助函数手动扫描:

✅ 先定义查找函数:

fun String.findFirstSince(position: Int, test: (Char) -> Boolean): Int {
    for (i in position until length) {
        if (test(this[i])) return i
    }
    return length
}

✅ 再用于 sequence 中精准定位词边界:

sequence {
    var startIndex = 0
    while (startIndex < input.length) {
        val endIndex = input.findFirstSince(startIndex) { it == ' ' }
        yield(input.substring(startIndex, endIndex))
        startIndex = input.findFirstSince(endIndex) { it != ' ' }
    }
}

这种方式完全避开正则引擎,在高频调用场景下更有优势。

5. 模拟新闻出版风格的标题格式

真正的标题格式化远不止“每个单词首字母大写”。例如主流媒体通常遵循 "Title Case" 规则:介词、连词等短词(如 a, an, the, and, but, by, for)除非位于句首或句尾,否则不大写。

实现思路

我们需要引入业务规则字典,并判断每个词的位置。

✅ 步骤如下:

  1. 定义不强制大写的短词集合:
val NON_CAPITALIZED_WORDS = setOf(
    "as", "at", "but", "by", "for", "in", "of", "on", "or", "the", "to", "with"
)
  1. 分割输入字符串:
val components = input.split("\\W+".toRegex())
  1. 使用 buildString {} 构建最终结果:
buildString {
    components.forEachIndexed { index, word ->
        when (index) {
            in 1..components.size - 2 -> word.capitalizeMiddleWord()
            else -> word.replaceFirstChar(Char::uppercaseChar)
        }.let { append(it).append(' ') }
    }
    deleteCharAt(length - 1) // 删除末尾多余空格
}
  1. 核心逻辑封装在 capitalizeMiddleWord() 中:
private fun String.capitalizeMiddleWord(): String =
  if (length > 3 || this !in NON_CAPITALIZED_WORDS) replaceFirstChar(Char::uppercaseChar) else this

📌 技巧说明:

  • 条件判断顺序很重要:先检查 length > 3,这是廉价操作;只有长度 ≤3 才查集合,避免不必要的哈希查找
  • 使用 buildString {} 替代 StringBuilder 可读性更好,且 Kotlin 编译器会自动优化

❌ 错误示范:直接对所有词都调用 replaceFirstChar 而忽略上下文,会导致像 “The Lord of the Rings” 变成 “The Lord Of The Rings”,不符合出版规范。

6. 总结

方案 适用场景 内存消耗 推荐指数
split().joinToString() 输入干净、数据量小 ❌ 高(~3x) ⭐⭐⭐⭐
sequence {} + 手动切片 大文本处理 ✅ 中(~2x) ⭐⭐⭐⭐☆
正则分割 含复杂空白符 ✅ 适中 ⭐⭐⭐⭐
Title Case 规则 标题格式化 ✅ 适中 ⭐⭐⭐⭐⭐

根据不同需求选择合适策略:

  • 快速原型 ➜ 直接 split
  • 高性能要求 ➜ sequence + 手动扫描
  • 专业排版 ➜ 加入业务词典判断

所有示例代码已开源在 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-strings-3

📌 最后提醒:不要低估字符串处理的复杂度。看似简单的功能背后,往往藏着性能陷阱和语义歧义。合理利用 Kotlin 的标准库特性,才能写出既简洁又健壮的代码。


原始标题:Capitalize Every Word in a String with Kotlin