UTF-8 vs UTF-16两种格式有何异同
|
admin
2025年4月19日 21:51
本文热度 125
|
之前,介绍了UTF-8和UTF-16,实际上还有UTF-32这个名称就知道是什么意思了,因为应用不广,所以就不展开说了。先说一个共同点:UTF-8和UTF-16都是变长编码,因为UTF-16是变长编码(每个字符为2字节或4字节)这事很少被提及。看标题应该也能猜到,我们主要说的是UTF-8和UTF-16的差别。但在说这个之前,我们要考虑,程序真正使用的编码是什么?是Unicode吗?实际上并不会真的使用Unicode(除非是UTF-32),而是使用UTF-8或UTF-16等(这就和GB2312编码中真实使用的都是GB2312内码一样)。UTF-16虽然是变长,但实际上Unicode基本多文种面(0号面)的使用频率非常高,这导致了UTF-6的编码长度基本等于字符数的2倍。UTF-8编码中,一个字符长度为1~4字节不等(Unicode目前最长为21位,所以不会使用介绍的5~6字节):
这样实际上,UTF-8编码的一个字符基本是1~3字节,具体的使用概率应该和用户相关(就是用户主要使用那种语言)。我们知道:Windows操作系统使用的是UTF-16编码,Linux操作系统使用的是UTF-8编码。这里分析一下它们各自的选择逻辑,微软是为了卖给全世界的,所以它选择了UTF-16,例如,Java等也是使用了UTF-16。但Linux不同,它是开源的,早期的开发者都是欧美的,选择UTF-8使用的空间一定不比UTF-16差(哪怕是拉丁字母占比更多,也就是和UTF-16持平,只有中文等字符占比多的情况下才会出现UTF-16更优的)。在使用的时候,有时需要UTF-8和UTF-16相互转换,因为它们有规律,是可以相互转换的。但是,如果需要GB18030和UTF-8相互转换,就没有可以终结的规律了。所以,这里就不介绍UTF-8和UTF-16相互转换的代码了,编码的相互转换可以使用iconv(参考附录三)。在源码的编码上,推荐使用UTF-8,此时使用MSVC编译,添加一个参数 /utf-8。传输协议的文本编码方案,要优先考虑兼容性,然后再考虑节省空间。最后,说一下Windows上的编码问题,内核使用的是UTF-16编码,但很多API,都提供了A版和W版,例如,CreateFile这个API,实际上提供了CreateFileA和CreateFileW两个版本,而CreateFile实际上是一个宏。但是有个别API只有W版的,更有甚者,W版和A版的功能(或效果)不完全一致,所以,如果可以,尽量使用W版本的。Windows的应用程序主要有两种(Console和Windows,VS界面设置里在Linker->SubSystem中)。Console的默认编码通常并不是UTF-16,中文系统默认为代码页936(这个可以看之前的文章,代码页、GB2312或GBK等),这个默认编码通常称为本地编码。本地编码和UTF-16的相互转化可以使用下面的系统API:
注意,这两个API的用法和iconv差异挺大的,微软的API文档很完整,这里就不展开了。非必要,Windows上不用iconv,这个看附录三。Linux的编码使用UTF-8的一个好处,就是内核裁剪上有那么一点点优势----就是剪裁到嵌入式系统的层度。关于信创系统的编码,目前基本都是UTF-8编码。我能想到的两个原因:1. 修改系统层面的编码节省的空间和现在计算机硬件相比,实际上已经没那么重要了2. 能够更大层度的兼容当前的软件。这里的兼容主要是指二进制的兼容,就是第三方软件不需要重新编译就可以运行。关于iconv这个库,需要注意函数iconv,一共5个参数,第二和第三两个参数共同组成一个内存段(内存起点指针和长度),第四和第五两个参数共同组成一个内存段。它对内存段的使用有一点特别,都是传入传出参数(即是入参也是出参)。无论函数是否成功,内存段都会变为剩余的部分,举例,下面示例代码:// 示例代码1
#include <iconv.h>
int main()
{
// 编码转换的源编码文本
constchar utf8[] = "幻想";
// 转换后的编码的储存位置
char buff[20] = { 0 };
// in_size值是7, 如果使用strlen则是6,都可以
size_t in_size = sizeof(utf8);
// 这里是 UTF-8 转 GB2312
iconv_t co = iconv_open( "GB2312", "UTF-8");
do {
if (co == (iconv_t)(-1)) {
// iconv_open失败
break;
}
// 这里一定要使用一个新的指针变量,iconv函数不能直接使用变量 utf8
// 因为 utf8 作为一个指针,是不能修改的,这不符合iconv的用法
char* in_buff = (char*)(utf8);
// 输出内存段的起始位置
size_t out_size = sizeof(buff);
// 这里也需要使用一个新的指针变量
char* out_buff = (char*)(buff);
// 在iconv执行前:
// in_buff == utf8 而且 out_buff == buff
size_t result = iconv(co, &in_buff, &in_size, &out_buff, &out_size);
// 在iconv执行前:
// in_buff != utf8 而且 out_buff != buff
// 通常为 in_buff > utf8 而且 out_buff > buff
// 并且 in_size 和 out_size 的值也会变小
// -------------------
// GB2312的编码结果存在的内存起点为 buff, 长度为 sizeof(buff) - out_size
// result == (iconv_t)(-1) 为失败
} while(false);
if (co != (iconv_t)(-1)) {
// 这里回收 iconv_open 的资源
iconv_close(co);
}
}
在linux和mac上,基本都是默认安装了iconv库的。如果在Windows上需要使用iconv,可以参考下面文档:
阅读原文:原文链接
该文章在 2025/4/21 10:32:56 编辑过