Java Unicode


Java中char数据类型是一个16位的Unicode字符。


char值的范围为U+0000 ~ U+FFFF(0 ~ 65535),这个范围的字符集合称为基本多语言平面(Basic Multilingual Plane,缩写为BMP),超过这个范围的Unicode字符(U+10000 ~ U+10FFFF)称为增补字符(supplementary character),一个char表示不了,需要用两个char表示。

Unicode编码中每个字符都分配了一个唯一的数字,称为代码点(code point),code point的数值范围是U+0000 ~ U+10FFFF。Java中一个代码单元(code unit)是一个16位的char值。

增补字符(supplementary character)使用两个代码点(code point)表示,称为代理对(surrogate pair)。第一个code point表示high surrogate,范围为U+D800 ~ U+DFBB, 第二个code point表示low surrogate,范围为U+DC00 ~ U+DFFF。例如code point为U+10400的字符'𐐀' 的代理对为U+D801和U+DC00。

代码点和字符互转

下面是代码点和字符互转的例子:

public class CodePointChar {
    public static void main(String[] args) {
        char[] arr = Character.toChars(0x10400);
        for (char c : arr)
            System.out.println(Integer.toHexString(c));    // d801 dc00

        int codePoint = Character.toCodePoint('\uD801', '\uDC00');
        System.out.println("codePoint : " + Integer.toHexString(codePoint));    // 10400

        int charCount = Character.charCount(0x10400);
        System.out.println("charCount : " + charCount);    // 2
    }
}


字节编码和字符串

下面是字节编码和字符串的例子:

public class CodePointStr {
    public static void main(String[] args) {
        try {
            char[] arr = Character.toChars(0x10400);
            for (char c : arr)
                System.out.println(Integer.toHexString(c & 0x0000ffff));    // d801 dc00
            String str = new String(arr);
            System.out.println(str);    // 𐐀

            byte[] arr2 = str.getBytes("UTF-16");
            for (byte b : arr2)
                System.out.println("\t" + Integer.toHexString(b & 0x000000ff));
                // fe ff d8 1 dc 0
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
    }
}

在上面的例子中,byte[] arr2 = str.getBytes("UTF-16");得到的字节长度是6,前面两个字节feff是utf-16编码的字节顺序标记(Byte Order Mark,BOM)。如果BOM是feff表示字节顺序是Big-Endian的,如果BOM是fffe表示字节顺序是Little-Endian的。

Unicode和其它编码的转换

Java平台内部字符编码使用utf-16。

如果使用字符输入流,如InputStreamReader,读取utf-8编码的的文件,读取到的utf-8的字节流会被转成utf-16的字符流。 如果使用字符输出流,如OutputStreamWriter,输出的字节流编码指定为utf-8,则utf-16的字符流会被转成utf-8的字节流后输出。

下面是Unicode和其它编码的转换的例子:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;

public class UnicodeConvert {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("a.txt");
                Writer out = new OutputStreamWriter(fos, "UTF8")){
            out.write('a');
            out.write('二');
            out.flush();
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }

        try (FileInputStream fis = new FileInputStream("a.txt");
                InputStreamReader in = new InputStreamReader(fis, "UTF8")){
            int c;

            while ((c = in.read()) != -1) {
                System.out.println(Integer.toHexString(c));
                    // 61 4e8c
                System.out.println((char) c);
                    // a 二
            }
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
    }
}