1、Java的运行环境中,String是什么编码?
使用java做程序设计语言,字符编码是和jvm相关的,和操作系统无关。
java默认的编码是jvm在安装的时候就确定了的,它是根据你的系统的环境确定并默认的。
我们可以通过java的Charset类的defaultCharset()方法来获取它默认的字符编码。
我安装的JDK是1.7,系统环境是64位,获取的默认的字符编码是utf16的,并且是Big-Endian(这点我比较奇怪,我的机器是Little-endian的,而虚拟机竟然默认是大端的编码)。
看如下代码:
String name = "张三";
Charset def = Charset.defaultCharset(); // 获取系统默认的编码 def = utf-16(Big-Endian)
Charset utf8 = Charset.forName("UTF-8");
Charset gbk = Charset.forName("GBK");
byte[] bdefult = name.getBytes(); // 获取的name的字节流 = [-2, -1, 95, 32, 78, 9]
byte[] butf16Big = name.getBytes(def); // name转换为utf16(Big-Endian)后的字节流 = [-2, -1, 95, 32, 78, 9]
byte[] bgbk = name.getBytes(gbk); // name转换为gbk后的字节流 = [-43, -59, -56, -3]
byte[] butf8 = name.getBytes(utf8); // name转换为utf8后的字节流 = [-27, -68, -96, -28, -72, -119]
由此我们可以看出执行String name = "张三"后String存储的就已经是系统默认的字符编码了utf16(Big-Endian)。
可能细心的人会发现,为什么"张三"的字节数是[-2, -1, 95, 32, 78, 9]有6个字节呢?不是utf16没个字符是占用两个字节来表示的呀。
对的,往下看:
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。
(Unicode是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。)
在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。
我们知道单字节的字符的表示(char)是没有大小端之分的,而ushort、int、long、int64等类型才有大小端之分,故gbk和utf8的字节流不需要表示它是否是大小端,而utf-16(即ushort)的字节流是需要告知它是大端or小端。
由于 Java 中的String默认采用带有 UCS2 的 UTF-16BE 拆分的,所以我们需要在字节流前面加上(FE, FF)告知是大端的。
其实 [-2, -1, 95, 32, 78, 9] = [FE, FF, 95, 32, 78, 9],所以前面两个字节的含义只是表明存储的字节流是大端还是小端的,方便传输的时候识别,并无其它的含义。
总结:
(1)、java默认的字符编码是虚拟机决定的,并与系统无关。
(2)、Unicode和UTF-16:1个字符占2个字节(不管是哪国语言)
(3)、Java中的char默认采用Unicode编码,所以Java中一个char也占2个字节
2、c和c++语言
大家都知道c和c++语言是没有java一样的虚拟机的,它们最终都是调用的系统的API,这就注定了它最终调用的接口是和系统相关的。
(1)、windows系统
VC/VS的C++开发环境,它实现的标准C++接口有不少肯定是需要使用windows api的。
要了解Win32子系统的DLL们提供了哪些API,最直接的方法就是用Win32dsm直接查看DLL们的导出表。这时我们会发现Win32 API中带字符串的API一般都有两个版本,例如CreateFileA()和CreateFileW()。当然也有例外,例如GetProcAddress函数。
A代表ANSI代码页,W是宽字符,即Unicode字符。Windows中的Unicode字符一般指UCS2的UTF16-LE编码。让我们通过几个实例观察A/W版本间的关系。
我们用WIn32dsm查看gdi32.dll的汇编代码,可以看到CreateFileA()调用GdiGetCodePage()获取当前代码页,再调用MultiByteToWideChar()转换输入的字符串,然后调用一个内部函数。而CreateFileW()直接调用这个内部函数。
故可以判断windows系统API内部实现是用UCS2的UTF16-LE编码。故windows api我们尽量使用宽字符的接口。
了解windows的API实现是用UCS2实现的后,那它的单字节的接口怎么转换成UCS2的呢?
windows的API提供setlocale接口可以设置当前程序使用的字符编码信息。故你通过此接口告诉windows你当前程序中char*使用的字符编码是什么。
获取当前程序的字符编码信息: char* pLocale = setlocale(LC_ALL, NULL); 可以了解到C++默认的字符编码是C locale。
设置当前程序为系统环境字符编码:char* pLocale = setlocale(LC_ALL, ""); 如果是简体中文版的话,即等同与setlocale(LC_ALL, ".936")。
(2)、Linux系统
本人对linux系统的API实现了解不深,不过据网上资料说它是用的UTF-8字符编码实现的。
有兴趣的人可以研究libstdc++,它实现了linux上的标准C++接口,可以看它的源码了解了解,其中应该有不少调用linux系统函数的方法。
3、总结:
(1)、Windows的系统API使用UCS2的UTF16-LE编码实现,linux的系统API使用UTF-8编码实现。
(2)、JAVA默认的字符编码与jvm相关,一般默认为UCS2的UTF-16BE编码,与系统无关。
(3)、C/C++的默认字符编码为C locale,用户可以通过setlocale()修改当前程序的字符编码环境。