子字符串

从字符串中提取一部分的操作,可通过 slice、substring、substr 等方法实现。

子字符串 (Substring) 是从原始字符串中提取一部分而得到的字符串。它是文本处理和数据分析中频繁使用的基本操作,广泛应用于从文件路径中提取文件名、从 URL 中提取域名、从日志消息中截取日期时间部分等各种实际场景。

JavaScript 提供了三种子字符串提取方法:slice()、substring() 和 substr()。slice(start, end) 支持负索引,可以从末尾定位,是最通用的方法。substring(start, end) 将负参数视为 0,并会自动交换参数的大小顺序。substr(start, length) 的第二个参数表示长度,但在 ECMAScript 规范中已被弃用。实际开发中使用 slice() 最为安全。

Python 使用切片语法 str[start:end] 作为标准方式。str[2:5] 提取索引 2 到 4 的 3 个字符,str[-3:] 获取末尾 3 个字符。步长指定 (str[::2]) 可以实现每隔一个字符提取。Java 使用 substring(beginIndex, endIndex) 方法,Go 使用 str[start:end] 切片语法。

从 Unicode 字符串中提取子字符串时,需要注意代理对和书写素簇的边界。JavaScript 的 slice() 以 UTF-16 代码单元为单位操作,因此在表情符号或代理对字符的中间截断,就会生成无效的字符串。像 [...str].slice(start, end).join('') 这样用展开语法先行分解,可以消除在代理对中间断开的问题。不过这只到码点单位为止,并不能守住书写素簇的边界。把分解形的 "パン" (ハ + 结合用半浊点 U+309A + ン) 按开头 1 个码点截取会变成 "ハ",半浊点脱落而成为另一个字 (页面上显示的 "パン" 是合成形,试验时请用 'ハ' + '\u309A' + 'ン' 组装分解形)。把国旗表情符号按 1 个码点截取,只会留下其中一侧的区域指示符;把家庭表情符号按 1 个码点截取,则只剩下一个人物。要以显示上的 1 个字为单位切出,就先用 Intl.Segmenter 分割为书写素簇,再把所需的范围连接起来。把前述 3 个例子合起来 (家庭表情按父母加 1 个孩子的 3 人构成计),相对于码点数 10,书写素簇数为 4,两种数法互不一致。

一个常见的误解是子字符串提取总是创建新字符串。在旧版 Java (Java 7 update 6 之前) 中,substring() 共享原始字符串的内部数组,导致仅保留短子字符串就会阻止整个原始字符串被垃圾回收,造成内存泄漏。现代 Java 已改为复制到新数组,解决了这一问题。

从字符计数的角度来看,子字符串的长度可以通过 end - start 计算,但在包含多字节字符时字节数与字符数并不一致。例如在 UTF-8 中提取 "东京塔" 的前 2 个字符,字符数为 2,而字节数为 6 (每个字符 3 字节)。在将子字符串存储到有字符数限制的表单字段或数据库列时,同时确认字符数和字节数非常重要。

分享这篇文章