杰瑞科技汇

Java中Unicode汉字如何正确处理?

Unicode 是一个字符集标准,它为世界上几乎所有的字符都分配了一个唯一的数字码点,汉字作为 Unicode 的一部分,自然也有其对应的码点。

Java中Unicode汉字如何正确处理?-图1
(图片来源网络,侵删)

在 Java 中,处理汉字主要有以下几个方面:

  1. Java 内部如何表示汉字:使用 UTF-16 编码。
  2. 汉字的 Unicode 码点:如何获取和使用。
  3. 汉字的输入与输出:处理源代码文件、控制台、文件读写。
  4. 常用操作:判断、拆分、统计长度等。

Java 内部表示:UTF-16 编码

这是理解 Java 字符处理的核心。

  • 基本多语言平面:Unicode 的前 65536 个字符(从 U+0000U+FFFF)被称为 BMP。绝大多数常用的汉字都位于 BMP 平面内,这些字符在 Java 中用一个 char 类型(16位)就可以表示。

    • 汉字 "中" 的 Unicode 码点是 U+4E2D,在 Java 中可以用一个 char 变量存储:char c = '中';
  • 辅助平面:超过 BMP 平面的字符(从 U+10000 开始)被称为辅助平面字符,这些字符在 Java 中需要用两个 char 来表示,这被称为“代理对”(Surrogate Pair)。

    Java中Unicode汉字如何正确处理?-图2
    (图片来源网络,侵删)

    一些生僻字、表情符号等,虽然不常见于普通汉字,但了解这一点很重要。

对于绝大多数日常应用,你可以认为一个 Java char 就代表一个汉字,但在处理可能包含生僻字或特殊符号的文本时,需要意识到一个“逻辑字符”可能由两个 char 组成。


汉字的 Unicode 码点

在 Java 中,你可以通过多种方式获取和使用汉字的 Unicode 码点。

1 使用转义序列 \u

在 Java 源代码中,你可以直接使用 Unicode 转义序列来表示汉字。

public class UnicodeExample {
    public static void main(String[] args) {
        // \u4e2d 是 "中" 的 Unicode 码点
        char zhong = '\u4e2D'; // 注意:转义序列不区分大小写,但通常使用大写
        System.out.println(zhong); // 输出: 中
        // 也可以用在字符串中
        String hello = "\u4F60\u597D\u4E16\u754C"; // 你好世界
        System.out.println(hello); // 输出: 你好世界
    }
}

2 获取字符的码点:charcodePointAt()

  • 对于 BMP 字符,可以直接将 char 强转为 int 得到其码点值。
  • 更通用的方法是使用 String.codePointAt()Character.codePointAt(),它们能正确处理辅助平面字符。
public class CodePointExample {
    public static void main(String[] args) {
        String str = "你好";
        // 方法1: 强转 (仅适用于BMP字符)
        char n = str.charAt(0); // '你'
        int codePointOfNi = (int) n;
        System.out.println("'你'的码点 (int转换): " + codePointOfNi); // 输出: 20320
        // 方法2: 使用 codePointAt() (推荐,更通用)
        int codePointYou = str.codePointAt(0); // '你'
        int codePointHao = str.codePointAt(1); // '好'
        System.out.println("'你'的码点 (codePointAt): " + codePointYou); // 输出: 20320
        System.out.println("'好'的码点 (codePointAt): " + codePointHao); // 输出: 22909
        // 码点通常用十六进制表示,更符合Unicode习惯
        System.out.printf("'你'的码点 (十六进制): U+%04X%n", codePointYou); // 输出: U+4F60
        System.out.printf("'好'的码点 (十六进制): U+%04X%n", codePointHao); // 输出: U+597D
    }
}

汉字的输入与输出(编码问题)

这是 Java 开发中最常见的问题,即乱码问题,乱码的根本原因是编码和解码时使用的字符集不一致

1 源代码文件编码

确保你的 Java 源文件(.java)是使用 UTF-8 编码保存的,现代的 IDE(如 IntelliJ IDEA, Eclipse)和新版本的 JDK 默认都支持 UTF-8,如果源文件编码是 GBK 等其他编码,直接在字符串中写的汉字可能会被编译器错误解析。

2 控制台输出

控制台能否正确显示汉字,取决于你的操作系统和终端的编码设置。

  • Windows: 默认是 GBK,如果你使用 UTF-8 编码编译程序,直接输出到控制台可能会乱码,可以在程序中设置控制台输出编码为 UTF-8:

    public class ConsoleOutput {
        public static void main(String[] args) {
            // 在 Windows 上,设置控制台输出编码为 UTF-8
            System.setProperty("file.encoding", "UTF-8");
            // 重定向标准输出流
            try {
                System.setOut(new PrintStream(System.out, true, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            System.out.println("你好,世界!"); // 现在应该能正确显示了
        }
    }
  • Linux / macOS: 终端通常默认支持 UTF-8,所以一般不会有问题。

3 文件读写

当读写包含汉字的文本文件时,必须显式指定字符集

错误示范(会产生乱码):

// FileReader 和 FileWriter 默认使用系统默认字符集(在中文Windows上是GBK)
// 如果文件是UTF-8编码,这里读出来的就是乱码
try (FileReader fr = new FileReader("test.txt");
     BufferedReader br = new BufferedReader(fr)) {
    String line = br.readLine();
    System.out.println(line); // 可能乱码
} catch (IOException e) {
    e.printStackTrace();
}

正确示范(使用 InputStreamReaderOutputStreamWriter):

import java.io.*;
public class FileReadWrite {
    public static void main(String[] args) {
        String content = "这是要写入文件的内容。";
        String filePath = "test_utf8.txt";
        // 1. 写入文件(指定UTF-8编码)
        try (FileOutputStream fos = new FileOutputStream(filePath);
             OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
             BufferedWriter bw = new BufferedWriter(osw)) {
            bw.write(content);
            System.out.println("文件写入成功。");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 2. 读取文件(指定UTF-8编码)
        try (FileInputStream fis = new FileInputStream(filePath);
             InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
             BufferedReader br = new BufferedReader(isr)) {
            String line = br.readLine();
            System.out.println("从文件读取的内容: " + line); // 正确显示
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最佳实践:从 Java 7 开始,推荐使用 Files 类和 StandardCharsets 枚举,它更简洁、更安全。

import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class ModernFileReadWrite {
    public static void main(String[] args) throws IOException {
        String content = "使用 N.IO 写入文件。";
        Path path = Paths.get("test_nio.txt");
        // 写入
        Files.write(path, content.getBytes(StandardCharsets.UTF_8));
        // 读取
        String readContent = Files.readString(path, StandardCharsets.UTF_8);
        System.out.println("从文件读取的内容 (N.IO): " + readContent);
    }
}

常用操作

1 判断一个字符是否为汉字

汉字在 Unicode 中的码点范围大致在 U+4E00U+9FFF 之间(CJK 统一汉字),还有扩展区等,但这个范围覆盖了绝大多数常用字。

public class IsChineseCharacter {
    public static boolean isChinese(char c) {
        // 基本汉字范围
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
                || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
            return true;
        }
        return false;
    }
    // 更简单的方法(针对基本汉字区)
    public static boolean isSimpleChinese(char c) {
        // \u4e00 - \u9fff 是基本汉字区
        return c >= '\u4E00' && c <= '\u9FFF';
    }
    public static void main(String[] args) {
        char c1 = '汉';
        char c2 = 'A';
        char c3 = '。'; // 中文标点
        System.out.println(c1 + " 是汉字吗? " + isChinese(c1)); // true
        System.out.println(c2 + " 是汉字吗? " + isChinese(c2)); // false
        System.out.println(c3 + " 是汉字吗? " + isChinese(c3)); // true (因为包含了标点符号区)
        System.out.println(c1 + " 是基本汉字吗? " + isSimpleChinese(c1)); // true
        System.out.println(c3 + " 是基本汉字吗? " + isSimpleChinese(c3)); // false
    }
}

2 正确计算字符串长度(包含汉字)

String.length() 方法返回的是 char 的数量,而不是“逻辑字符”的数量,对于包含辅助平面字符的字符串,这个值会不准确,对于纯汉字(BMP)字符串,它虽然能得到正确的字符数,但最佳实践是使用 codePointCount()

public class StringLength {
    public static void main(String[] args) {
        String str = "Hello世界!";
        // char 数量
        int charLength = str.length();
        System.out.println("char 数量: " + charLength); // 输出: 8
        // 逻辑字符数量 (推荐)
        int codePointLength = str.codePointCount(0, str.length());
        System.out.println("逻辑字符数量: " + codePointLength); // 输出: 7 (H,e,l,l,o,世,界,!)
    }
}

3 拆分字符串(按“字”而不是按 char

同样,如果字符串包含辅助平面字符,用 charAt() 遍历会出错,应该使用 codePoints() 方法。

public class StringIteration {
    public static void main(String[] args) {
        String str = "Java你好";
        // 错误的方式(对于辅助平面字符会出问题)
        System.out.println("--- 按char遍历 ---");
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            System.out.println(c);
        }
        // 输出:
        // J
        // a
        // v
        // a
        // 你
        // 好
        // 正确的方式(按逻辑字符遍历)
        System.out.println("\n--- 按逻辑字符 (codePoint) 遍历 ---");
        str.codePoints().forEach(cp -> {
            // 将码点转换回字符
            System.out.println((char)cp);
        });
        // 输出和上面一样,但这种方法更健壮
    }
}
场景 关键点 推荐方法
内部表示 Java 使用 UTF-16,一个 char 通常对应一个汉字。 了解即可,代码中直接使用 charString
获取码点 将汉字转为其数字标识。 String.codePointAt()(int)char
源代码 确保文件编码为 UTF-8。 IDE 设置,或使用 javac -encoding UTF-8
控制台 避免乱码,确保编码一致。 Windows 下可设置 System.setProperty("file.encoding", "UTF-8")
文件读写 必须显式指定字符集! 使用 InputStreamReader/OutputStreamWriterFiles 类,并指定 StandardCharsets.UTF_8
判断汉字 检查字符是否在 Unicode 的汉字区块内。 Character.UnicodeBlock.of(c) 或简单的码点范围判断。
计算长度 区分 char 数量和逻辑字符数量。 使用 String.codePointCount()
遍历字符 正确处理所有 Unicode 字符,包括辅助平面。 使用 String.codePoints().forEach()

掌握这些要点,你就可以在 Java 中游刃有余地处理汉字了。编码一致是避免乱码的核心

分享:
扫描分享到社交APP
上一篇
下一篇