一、字符
基本类型char(Character对象封装的值), 占2个字节,最初可以用于表示unicode字符编码中一个字符,但是随着unicode标准的不断发展,已超出2个字节所能表达的字符数量范围0~65535。
unicode(统一码)是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。目前统一码用数字0-0x10FFFF来映射这些字符,最多可以容纳1,114,112个字符(码位),码位就是分配给字符的一一对应的数字。
数字转换到程序字节数据的编码方案,常见的有UTF-8、UTF-16、UTF-32等。
java中char值的编码是UTF-16,表示的是0~0xFFFF码位范围的字符,由于占2个字节,恰好用1个char表示。这些0~0xFFFF码位范围的字符,通常称为基本面BMP(Basic Multilingual Plane),它又简称为"零号平面"范围内的字符。
对于0x10000~0x10FFFF码位范围的字符,java用2个char来表示,此部分字符由unicode中一个被称作代理区(Surrogate)的特殊区域0xD800-0xDFFF处理, 目的是用两个UTF-16字符来表示BMP以外的字符。可以通过Character类封装的静态方法isSurrogate(char ch),来判断字符是否为代理区字符。
JDK1.8中char基于unicode标准版本6.2.0
二、字符串
String类包含属性 private final char value[];
它可以看做是对char[]数组的封装,String类一旦实例化,它的属性char[]就不可被改变。
三、汉字
unicode中"基本汉字"码位范围: 0x4E00~0x9FA5, 以及"基本汉字补充"码位范围: 0x9FA6~0x9FCB, 共20902 + 38 = 20940个字符,此外还有其他扩充、兼容的码位,这部分字符不常用。通常情况下,通过采用码位范围: 0x4E00~0x9FCB 来判断是否是汉字,就够了。
// 判断字符
public static boolean isChinese(char c) {
return (c >= '\u4E00' && c <= '\u9FCB');
}
// 判断字符串
static boolean isFindChinese(String value) {
Pattern pattern = Pattern.compile("[\\u4E00-\\u9FCB]+");
return pattern.matcher(value.trim()).find();
}
四、全角/半角
半角
一个字符占用一个标准的字符位置,也就是ASCII范围内的可见字符,即unicode的码位为 0x21~0x7E, 以及空格 0x20范围内字符,以下是具体字符:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
全角
一个字符占用两个标准的字符位置,例如中文,为让ASCII范围内可见字符,也占据等宽的空间,设计了全角模式, 对应unicode的码位 0xFF01~0xFF5E, 以及空格 0x3000范围内的字符, 以下是具体字符:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
上面之所以把空格单独拎出来, 就是因为其他字符在位置上都是按顺序对应的,字符的全角、半角码位间隔固定:65248,而空格不知为何比较特殊。
// 全角转半角
public static String toHalfChar(String value) {
if (value == null || value.isEmpty()) return value;
char[] arr = value.toCharArray();
for(int i = 0; i < arr.length; i++) {
if (arr[i] == '\u3000') {
arr[i] = '\u0020';
} else if (arr[i] >= '\uff01' && arr[i] <= '\uff5e') {
arr[i] = (char)(arr[i] - 65248);
}
}
return new String(arr);
}
五、代理区(Surrogate)
常见的"表情符"等, 都是码位在0x10000~0x10FFFF范围内的字符,需要2个char来表示。
Character类的静态方法,可判断char是否属于代理区范围内的字符。
public static boolean isSurrogate(char ch)
属于代理区范围字符,在文本展示时,char必须成对,否则就是乱码。
例如:
String s1 = "\uD83D\uDE42";
System.out.println("s1: " + s1);
System.out.println("s1.length: " + s1.length());
System.out.println("s1.codePointCount: " + s1.codePointCount(0, 2));
运行结果:
s1: 🙂
s1.length: 2
s1.codePointCount: 1
六、序列化
字符的序列化就是按编码规则,将字符转换为对应的字节, 以方便数据的传递、持久化存储。
这就需要指定字符集 utf-8, utf-16, utf-32, gbk, gb2312等(base64当然也可以,本文仅讨论字符集间转换情况)。
因java中char本身就是utf-16编码,属于unicode编码标准,因此在序列化为utf系列字符集时,不存在丟数、乱码问题。但 gbk, gb2312 字符集的编码范围相对于unicode要小,对于它不支持的字符,例如"表情符"等,在转化时,就会出现这种丟数、乱码问题。
目前比较常见的utf-8编码,依据不同码位,占1~4个字节(其中1~3个字节的编码, 对应基本面BMP范围内字符)
MySQL在5.5.3之后增加了utf8mb4的编码支持,这是目前推荐的字符集,它的utf8字符集(仅支持1~3个字节的编码), 在存储BMP范围外字符时,同样会出现丟数、乱码问题。
常见乱码情况
1. 用错误的"字符集",来解读字节流, 网页上经常出现,采用正确的字符集就可以。
2. 就是上说的两个字符集固有的不兼容导致,这种情况不可调和,只能通过字符清洗、中间码转意解决。