在Java中,一个char类型变量能否存储一个中文汉字?

答案是肯定的,显然,Java中一个char变量能够存储一个中文汉字

char ch = '中';
String chstr = "你好"
System.out.println(chstr.length());   // 2

因为在Java中,字符是以Unicode的格式保存的,一个char类型对应一个16位的Unicode码

public static void main(String[] args) throws UnsupportedEncodingException {
    char ch = '中';
    String str="中国";
    System.out.println(Integer.toHexString(ch));   // 4e2d
    System.out.println(strToHexString(str));    //4e 2d 56 fd
}

//将String转换为16进制码
public static String strToHexString(String str) throws UnsupportedEncodingException {
    StringBuilder sb = new StringBuilder();
    byte[] units = str.getBytes("UTF-16BE");
    for(int i=0;i<units.length;++i){
        if(i!=0) sb.append(" ");
        sb.append(Integer.toHexString(units[i]&0xff)+"");
    }
    return sb.toString();
}

String.getBytes(codeFormation)方法能够将String转化为对应字符编码的byte数组,可以发现char的真正存储格式和UTF-16BE是相同的,那么尝试其他几种格式呢?

编码

内容

长度(字节)

字符'A'的UTF-16BE编码

00 41

2

字符'A'的UTF-16LE编码

41 00

2

字符'A'的UTF-16编码(BE)

FE FF 00 41

2+2

字符'A'的UTF-16编码(LE)

FF FE 41 00

2+2

字符'中'的UTF-8编码

41

1

字符'A'的UTF-32编码

00 00 00 41

4

字符'A'的GBK编码

41

1

字符'A'的GB2312编码

41

1

字符'A'的ASCII编码

41

1

 

编码

内容

长度(字节)

字符'中'的UTF-16BE编码

4E 2D

2

字符'中'的UTF-16LE编码

2D 4E

2

字符'中'的UTF-16编码(BE)

FE FF 4E 2D

2+2

字符'中'的UTF-16编码(LE)

FF FE 2D 4E

2+2

字符'中'的UTF-8编码

E4 B8 AD

3

字符'中'的UTF-32编码

00 00 4E 2D

4

字符'中'的GBK编码

D6 D0

2

字符'中'的GB2312编码

D6 D0

2

字符'中'的ASCII编码

3F(表示无)

 

 

可见Java中一个字符对应的是一个Unicode码,而一个String由Unicode码组成,如果转换成UTF-16的话,需要BOM(Byte Order Mark)的字符(big endian:feff,little endian:ff fe)来表明编码的格式,getbtyes()默认是转为UTF-8格式

那么,一个char变量一定能完整的存储下一个字符么?

答案是否定的

在这里介绍下编码的知识

  • Unicode编码(Universal Mutiple-Octet Coded Character Set,简称UCS,俗称Unicode)
  • Unciode编码定义了所有字符的数字表示,每个数字表示称之为码点(Code Point)
  • 一般以 U+ABCD 来表示
  • 第一版为UCS-2(2个字节),第二版为UCS-4(4个字节)
  • 目前UCS已经扩展到了21位(U+0000 ~ U+10 FFFF)
  • 编码空间被分为17个平面(plane),0号平面(U+0000 ~ U+FFFF)称之为基本多语言平面(Basic Multilingual Plane),包含了常用语言字符,其他平面为辅助字符(特殊符号,emoji)
  • UTF(Unicode Transformation Format)
  • UTF定义了码点在计算机中的二进制存储形式
  • 码点按照UTF定义的格式,转换为的二进制串的单元称之为码元(Code Unit),一个码元的大小对应了UTF的后缀
  • UTF-16
  • 一般编程语言的字符机内转换格式
  • 长度可变,代码单元大小为2个字节
  • 基本多语言平面中,一个代码点由一个代码单元表示;辅助字符平面中,一个代码点由两个代码单元表示
  • UTF-8
  • 最常用的转换格式,存储和传输效率最高
  • 长度由1~4,兼容ASCII码
  • 0~127的码点转化为1个字节,其余字符转换为2~4个字节(中文一般3个字节)

 

在Java5.0之后,添了辅助字符,需要2个char类型存储,

比如 '?',对应的unicode码为 D834 DF06,在Java中转换为2个码元

String normalStr="你好Hello";
System.out.println(normalStr+",代码单元数量:"+normalStr.length());   // 7
System.out.println(normalStr+",代码点数量:"+normalStr.codePointCount(0,normalStr.length())); // 7

char[] c = Character.toChars(Integer.parseInt("1D306", 16));
String supplyStr=new String(c)+"是一个辅助字符";  // ”?是一个辅助字符”
System.out.println(supplyStr+",代码单元数量:"+supplyStr.length()); // 9
System.out.println(supplyStr+",代码点数量:"+supplyStr.codePointCount(0,supplyStr.length())); 8

 总结:Java中的char类型,包含两个字节,存储一个UTF-16格式的代码单元

一个char类型能表示常用语言的字符,但辅助字符需要两个代码单元来表示

附上:一个展示编码,代码点,代码单元的小程序

package com.stk.learnjava;

import java.io.UnsupportedEncodingException;

/**
 * Created by stk on 2016/8/16 0016.
 *
 */
public class LearnString {
    public static void main(String[] args) throws UnsupportedEncodingException {
        learnCodePoint();
        learnUnicode();
        learnStringAPI();
    }

    private static void learnStringAPI()
    {
        String substr="abc";
        StringBuilder sb=new StringBuilder();
        sb.append(true);
        sb.append('a');
        sb.append("str");
        System.out.println(sb.toString());
    }
    private static void learnCodePoint()
    {
        System.out.println("------关于代码点和代码单元------");
        String normalStr="你好Hello";
        System.out.println(normalStr+",代码单元数量:"+normalStr.length());
        System.out.println(normalStr+",代码点数量:"+normalStr.codePointCount(0,normalStr.length()));
        System.out.println(normalStr+",第一个代码单元:"+normalStr.charAt(0));
        System.out.println(normalStr+",第一个代码点:"
                +"0x"+Integer.toHexString(normalStr.codePointAt(normalStr.offsetByCodePoints(0,0)))+"("
                +new String(Character.toChars(normalStr.codePointAt(normalStr.offsetByCodePoints(0,0))))+")");
        System.out.println(normalStr+",第二个代码单元:"+normalStr.charAt(1));
        System.out.println(normalStr+",第二个代码点:"
                +"0x"+Integer.toHexString(normalStr.codePointAt(normalStr.offsetByCodePoints(0,1)))+"("
                +new String(Character.toChars(normalStr.codePointAt(normalStr.offsetByCodePoints(0,1))))+")");

        char[] c = Character.toChars(Integer.parseInt("1D306", 16));
        String supplyStr=new String(c)+"是一个辅助字符";
        System.out.println(supplyStr+",代码单元数量:"+supplyStr.length());
        System.out.println(supplyStr+",代码点数量:"+supplyStr.codePointCount(0,supplyStr.length()));
        System.out.println(supplyStr+",第一个代码单元:"+supplyStr.charAt(0));
        System.out.println(supplyStr+",第一个代码点:"
                +"0x"+Integer.toHexString(supplyStr.codePointAt(supplyStr.offsetByCodePoints(0,0)))+"("
                +new String(Character.toChars(supplyStr.codePointAt(supplyStr.offsetByCodePoints(0,0))))+")");
        System.out.println(supplyStr+",第二个代码单元:"+supplyStr.charAt(1));
        System.out.println(supplyStr+",第二个代码点:"
                +"0x"+Integer.toHexString(supplyStr.codePointAt(supplyStr.offsetByCodePoints(0,1)))+"("
                +new String(Character.toChars(supplyStr.codePointAt(supplyStr.offsetByCodePoints(0,1))))+")");
    }
    private static void learnUnicode() throws UnsupportedEncodingException {
        System.out.println("------关于字符编码-----");
        char ech='A';
        System.out.println("字符'A'Java中的16进制码:"+Integer.toHexString(ech));
        String estr="A";
        System.out.println("字符'A'的UTF-16BE编码:"+byteToHex(estr.getBytes("UTF-16BE")));
        System.out.println("字符'A'的UTF-16LE编码:"+byteToHex(estr.getBytes("UTF-16LE")));
        System.out.println("字符'A'的UTF-16编码:"+byteToHex(estr.getBytes("UTF-16")));
        System.out.println("字符'A'的UTF-8编码:"+byteToHex(estr.getBytes("UTF-8")));
        System.out.println("字符'A'的UTF-32编码:"+byteToHex(estr.getBytes("UTF-32")));
        System.out.println("字符'A'的GBK编码:"+byteToHex(estr.getBytes("GBK")));
        System.out.println("字符'A'的GB2312编码:"+byteToHex(estr.getBytes("GB2312")));
        System.out.println("字符'A'的ASCII编码:"+byteToHex(estr.getBytes("ASCII")));

        char cch='中';
        System.out.println("字符'中'Java中的16进制码:"+Integer.toHexString(cch).toUpperCase());
        String cstr="中";
        System.out.println("字符'中'的UTF-16BE编码:"+byteToHex(cstr.getBytes("UTF-16BE")));
        System.out.println("字符'中'的UTF-16LE编码:"+byteToHex(cstr.getBytes("UTF-16LE")));
        System.out.println("字符'中'的UTF-16编码:"+byteToHex(cstr.getBytes("UTF-16")));
        System.out.println("字符'中'的UTF-8编码:"+byteToHex(cstr.getBytes("UTF-8")));
        System.out.println("字符'中'的UTF-32编码:"+byteToHex(cstr.getBytes("UTF-32")));
        System.out.println("字符'中'的GBK编码:"+byteToHex(cstr.getBytes("GBK")));
        System.out.println("字符'中'的GB2312编码:"+byteToHex(cstr.getBytes("GB2312")));
        System.out.println("字符'中'的ASCII编码:"+byteToHex(cstr.getBytes("ASCII")));
    }

    private static String byteToHex(byte[] bt)
    {
        StringBuilder sb = new StringBuilder(4);

        for(int b:bt) {
            sb.append(Integer.toHexString(b & 0xFF).toUpperCase());
            sb.append(" ");
        }

        return sb.toString();
    }
}