首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 字符串反转与 Unicode 代理对字符处理深度解析

     編輯:Java 相關知識

基础反转算法的陷阱

// 看似正确,实则在补充字符面前失效
public String reverseBad(char[] value) {
    for (int i = 0, j = value.length - 1; i < j; i++, j--) {
        char tmp = value[i];
        value[i] = value[j];
        value[j] = tmp;
    }
    return new String(value);
}

测试 "😀😃😄"(Emoji 为 UTF-16 代理对,每个占 2 个 char):

输入:  [D83D, DE00, D83D, DE03, D83D, DE04]  // 3 个 Emoji
输出:  [DE04, D83D, DE03, D83D, DE00, D83D]  // 乱码:High/Low Surrogate 被拆分

StringBuffer.reverse() 的正确实现

JDK 源码 (AbstractStringBuilder.reverse()) 核心逻辑:

  1. 首轮反转:按 char 数组整体反转
  2. 检测代理对:扫描是否存在 Character.MIN_SURROGATE (\\uD800) ~ Character.MAX_SURROGATE (\\uDFFF)
  3. 二次修复:若存在代理对,正向扫描,发现 Low Surrogate 后跟 High Surrogate 则交换二者,恢复正确代理对顺序
public AbstractStringBuilder reverse() {
    boolean hasSurrogate = false;
    int n = count - 1;
    // 第一步:整体反转 char 数组
    for (int j = (n - 1) >> 1; j >= 0; --j) {
        char c1 = value[j];
        char c2 = value[n - j];
        if (!hasSurrogate) {
            hasSurrogate = (c1 >= Character.MIN_SURROGATE && c1 <= Character.MAX_SURROGATE)
                        || (c2 >= Character.MIN_SURROGATE && c2 <= Character.MAX_SURROGATE);
        }
        value[j] = c2;
        value[n - j] = c1;
    }
    // 第二步:修复代理对顺序
    if (hasSurrogate) {
        for (int i = 0; i < count - 1; i++) {
            char low = value[i];
            if (Character.isLowSurrogate(low)) {
                char high = value[i + 1];
                if (Character.isHighSurrogate(high)) {
                    value[i++] = high;
                    value[i] = low;
                }
            }
        }
    }
    return this;
}

Unicode 核心概念速查

术语定义Java API
Code Point (代码点)一个完整的 Unicode 字符,范围 U+0000 ~ U+10FFFFCharacter.toCodePoint(high, low), codePointAt()
Code Unit (代码单元)UTF-16 编码单元,Java char 即 16-bit 代码单元char 类型
BMP (基本多文种平面)U+0000 ~ U+FFFF,单 char 即可表示Character.isBmpCodePoint()
Supplementary Characters (增补字符)U+10000 ~ U+10FFFF,需 2 个 char (代理对)Character.isSupplementaryCodePoint()
High Surrogate (高位代理)\\uD800 ~ \\uDBFF,代理对首位Character.isHighSurrogate(char)
Low Surrogate (低位代理)\\uDC00 ~ \\uDFFF,代理对次位Character.isLowSurrogate(char)

正确处理增补字符的工具方法

// 1. 统计真实字符数(代码点数)
public int realLength(String s) {
    return s.codePointCount(0, s.length());  // 或 s.chars().count()
}

// 2. 按代码点遍历
public void forEachCodePoint(String s, IntConsumer action) {
    s.codePoints().forEach(action);  // Java 8+ Stream API
}

// 3. 安全反转(保持代理对完整)
public String safeReverse(String s) {
    int[] codePoints = s.codePoints().toArray();
    for (int i = 0, j = codePoints.length - 1; i < j; i++, j--) {
        int tmp = codePoints[i];
        codePoints[i] = codePoints[j];
        codePoints[j] = tmp;
    }
    return new String(codePoints, 0, codePoints.length);
}

// 4. 按代码点截取子串
public String substringByCodePoint(String s, int beginCp, int endCp) {
    int begin = s.offsetByCodePoints(0, beginCp);
    int end = s.offsetByCodePoints(0, endCp);
    return s.substring(begin, end);
}

常见误区避坑指南

  1. String.length() 返回 char 数,非字符数:"😀".length() == 2
  2. charAt(index) 可能返回半个代理对:"😀".charAt(0) == '\\uD83D'(无意义)
  3. 正则 . 默认匹配单个 char:用 \\X 或 (?s). 匹配整个代码点
  4. 字节序列化:String.getBytes(StandardCharsets.UTF_8) 正确编码为 UTF-8 字节流,反序列化用 new String(bytes, StandardCharsets.UTF_8)

最佳实践总结

  • 涉及字符级操作(反转、截取、计数、遍历)一律使用 Code Point API (codePoints(), codePointCount(), offsetByCodePoints())
  • 存储/传输统一用 UTF-8,内存中 JVM 用 UTF-16 (char[]/String)
  • 数据库列类型选 utf8mb4 (MySQL) 或 UTF8 (PostgreSQL) 支持增补字符
  • 前端传输 JSON 时,Emoji 等增补字符在 UTF-8 下无需转义,直接传输
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved