字符编码Unicode原理和实践

目录

一、字符集编码的发展

1.1、字符集和字符编码的区别和联系

1.2、字符集编码的发展

 1.3、总结

二、字符集相关命令

2.1、什么是BOM

 2.2、字符集相关命令

 三、字符集转换相关编程实现

3.1、windows下vs2015编译安装iconv

3.2、在VS2015中编译实现转换


一、字符集编码的发展

1.1、字符集和字符编码的区别和联系

  • 字符集:多个字符的集合。例如GB2312是中国国家标准的简体中文字符集,GB2312收录简化汉字(6763个)及一般符号、序号、数字、拉丁字母、日文假名、希腊字母、俄文字母、汉语拼音符号、汉语注音字母,共 7445 个图形字符。
  • 字符编码:把字符集中的字符编码为(映射)指定集合中的某一对象(例如:比特模式、自然数序列、电脉冲),以便文本在计算机中存储和通过通信网络的传递。

字符集和字符编码的关系 : 字符集是书写系统字母与符号的集合,而字符编码则是将字符映射为一特定的字节或字节序列,是一种规则。

通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码(例如:ASCII、IOS-8859-1、GB2312、GBK,都是即表示了字符集又表示了对应的字符编码,但Unicode不是,它采用现代的模型))

1.2、字符集编码的发展

(1) 单字节

ASCII(American Standard Code for Information Interchange), 128个字符, 用7位二进制表示(00000000-01111111即0x00-0x7F); EASCII(Extended ASCII),256个字符,用8位二进制表示(00000000-11111111即0x00-0xFF)。 当计算机传到了欧洲,国际标准化组织在ASCII的基础上进行了扩展,形成了ISO-8859标准,跟EASCII类似,兼容ASCII,在高128个码位上有所区别。但是由于欧洲的语言环境十分复杂,所以根据各地区的语言又形成了很多子标准,ISO-8859-1、ISO-8859-2、ISO-8859-3、……、ISO-8859-16。

(2)双字节

当计算机传到了亚洲,256个码位就不够用了。于是乎继续扩大二维表,单字节改双字节,16位二进制数,65536个码位。在不同国家和地区又出现了很多编码,大陆的GB2312、港台的BIG5、日本的Shift JIS等等。

注意65536个码位这种说法只是理想情况,由于双字节编码可以是变长的,也就是说同一个编码里面有些字符是单字节表示,有些字符是双字节表示。这样做的好处是,一方面可以兼容ASCII,另一方面可以节省存储容量,代价就是会损失一部分码位。

GBK(Chinese Internal Code Specification)是GB2312的扩展(gbk编码能够用来同时表示繁体字和简体字),按理说都属于双字节编码,码位是一样的,根本谈不上扩展,但实际上是预留空间在起作用。比如下图为GBK的编码空间,GBK/1、GBK/2是GB2312的区域,GBK/3、GBK/4、GBK/5是GBK的区域,红色是用户自定义区域,白色可能就是由于变长编码损失的区域了。 GBK全称《汉字内码扩展规范》,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字。

GBK字符集中所有中文字符和全角符号占2个字节,字母和半角符号占一个字节。 没有特殊的编码方式,习惯称呼GBK 编码。一般在国内,汉字较多时使用。

当互联网席卷了全球,地域限制被打破了,不同国家和地区的计算机在交换数据的过程中,就会出现乱码的问题,即对同一组二进制数据,不同的编码会解析出不同的字符。

UNICODE字符集国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。 有多个编码方式,分别是UTF-8,UTF-16,UTF-32编码。

范例:“汉字”对应的UNICODE数字是0x6c49和0x5b57,而编码的程序数据是:

UTF8编码:E6B189    E5AD97

UTF16BE编码:6C49 5B57

UTF32BE编码:00006C490 0005B57

(3) 多字节

Unicode字符集 可以使用的编码有三种:

  • UFT-8:一种变长的编码方案,使用 1~6 个字节来存储;
  • UFT-32:一种固定长度的编码方案,不管字符编号大小,始终使用 4 个字节来存储;
  • UTF-16:介于 UTF-8 和 UTF-32 之间,使用 2 个或者 4 个字节来存储,长度既固定又可变。

UTF 是 Unicode Transformation Format 的缩写,意思是“Unicode转换格式”,后面的数字表明至少使用多少个比特位(Bit)来存储字符。

GB18030字符集采用单字节、双字节和四字节三种方式对字符编码。 兼容GBK和GB2312字符集。

(4)UTF-8

UTF-8: 是一种变长字符编码,被定义为将代码点编码为1至4个字节,具体取决于代码点数值中有效位的数量。

注意:UTF-8不是编码规范,而是编码方式。

unicode编码(16进制) 

UTF-8 字节流(二进制)

000000 - 00007F

0xxxxxxx

000080 - 0007FF

110xxxxx 10xxxxxx

000800 - 00FFFF

1110xxxx 10xxxxxx 10xxxxxx

01 0000 - 10 FFFF

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

例如:汉字 严的Unicode码是4E25转换成二进制就是01001110 00100101共15位,根据上表可知使用UTF-8字符编码后占3个字节,因此前3位是1,第4位(n+1位)是0,后面两个字节中每个字节的前两位都是10,即1110 xxxx 10 xxxxxx 10xxxxxx。填充进去后就变成了1110 0100 10 111000 10 100101共计24位占3个字节。

000800 - 00FFFF (4E25)

‭ 0100 111000 100101‬

11100100 10111000 10100101  -> E4B8A5

(5)UFT-16

UFT-16比较奇葩,它使用 2 个或者 4 个字节来存储。

对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储,并且直接存储 Unicode 编号,不用进行编码转换,这跟 UTF-32 非常类似。

对于 Unicode 编号范围在 10000~10FFFF 之间的字符,UTF-16 使用四个字节存储,具体来说就是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,较低的一些比特位(剩下的比特位)用一个值介于 DC00~DFFF 之间的双字节存储。

Unicode 编号范围(十六进制)

 

具体的 Unicode 编号(二进制)

UTF-16 编码

编码后的 字节数

0000 0000 ~ 0000 FFFF

xxxxxxxx xxxxxxxx

xxxxxxxx xxxxxxxx

2

0001 0000---0010 FFFF

yyyy yyyy yyxx xxxx xxxx

110110yy yyyyyyyy 110111xx xxxxxxxx

4

UTF-16BE,其后缀是 BE 即 big-endian,大端的意思。大端就是将高位的字节放在低地址表示。 UTF-16LE,其后缀是 LE 即 little-endian,小端的意思。小端就是将高位的字节放在高地址表示。 UTF-16,没有指定后缀,即不知道其是大小端,所以其开始的两个字节表示该字节数组是大端还是小端。即FE FF表示大端,FF FE表示小端。

(6)UTF-32

UTF-32 是固定长度的编码,始终占用 4 个字节,足以容纳所有的 Unicode 字符,所以直接存储 Unicode 编号即可,不需要任何编码转换。浪费了空间,提高了效率。

 1.3、总结

程序打开一个文件时怎么识别用的是UTF-8还是UTF-16? 是否有标志做标志,在文件的开头几个字节就是标志.

  • EF BB BF 表示UTF-8
  • FE FF 表示UTF-16BE
  • FF FE 表示UTF-16LE
  • 00 00 FE FF 表示UTF32-BE
  • FF FE 00 00 表示UTF32-LE

只有 UTF-8 兼容 ASCII,UTF-32 和 UTF-16 都不兼容 ASCII,因为它们没有单字节编码。

查看完整的Unicode字符集,以及各种编码方式:https://unicode-table.com/cn/

Unicode和UTF编码转换:https://www.qqxiuzi.cn/bianma/Unicode-UTF.php

二、字符集相关命令

2.1、什么是BOM

BOM(Byte Order Mark)字节序(字节顺序的标识),其实就是用大端(BE)还是小端(LE)。

UTF在文件中的存储。UTF格式在文件中总有固定文件头:

UTF编码

Byte Order Mark

UTF-8

EF BB BF

UTF-16LE

FF FE

UTF-16BE

FE FF

UTF-32LE

FF FE 00 00

UTF-32BE

00 00 FE FF 

 UTF-8缺省不带BOM UTF-8中有一字节的情况,这种情况,就没有两端的说法了。至于另外的二,三,四字节情况,以三字节为例,如果你一定要弄出端法,也不是说不可以,比如,小端法就是“小-中-大”,大端法就是“大-中-小”。但现实情况是UTF-8仅仅采用了一种端法,就是大端法。

 2.2、字符集相关命令

如何查看文件的编码方式?

范例:

file -i chatset.cpp

如何转换编码方式?

iconv命令是用来转换文件的编码方式的,比如它可以将UTF8编码的转换成GB18030的编码,反过来也行。Linux下的iconv开发库包括iconv_open,iconv_close,iconv等C函数,可以用来在C/C++程序中很方便的转换字符编码

语法: iconv -f encoding [-t encoding] [inputfile]...

选项:

-f encoding :把字符从encoding编码开始转换。

-t encoding :把字符转换到encoding编码。

-l :列出已知的编码字符集合

-o file :指定输出文件

-c :忽略输出的非法字符

-s :禁止警告信息,但不是错误信息

--verbose :显示进度信息

-f和-t所能指定的合法字符在-l选项的命令里面都列出来了。

(1)iconv支持的格式

这里节选部分格式: UTF-8, UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE,  UTF-32LE, UTF8,  UTF16,   UTF16BE,   UTF16LE,   UTF32,   UTF32BE,   UTF32LE   GB2312  GBK ISO-8859-1

(2)iconv支持的转换各种格式

iconv -f UTF-8  -t UTF-8 utf8.txt -o UTF-8.txt 
iconv -f UTF-8  -t UTF8  utf8.txt -o UTF8.txt

iconv -f UTF-8  -t UTF-16   	UTF-8.txt -o UTF-16.txt
iconv -f UTF-8  -t UTF-16BE 	UTF-8.txt -o UTF-16BE.txt
iconv -f UTF-8  -t UTF-16LE 	UTF-8.txt -o UTF-16LE.txt

iconv -f UTF-8  -t UTF16   	UTF-8.txt -o UTF16.txt
iconv -f UTF-8  -t UTF16BE 	UTF-8.txt -o UTF16BE.txt
iconv -f UTF-8  -t UTF16LE 	UTF-8.txt -o UTF16LE.txt

iconv -f UTF-8  -t UTF-32   UTF-8.txt -o UTF-32.txt
iconv -f UTF-8  -t UTF-32BE UTF-8.txt -o UTF-32BE.txt
iconv -f UTF-8  -t UTF-32LE UTF-8.txt -o UTF-32LE.txt

iconv -f UTF-8  -t GB2312   UTF-8.txt -o GB2312.txt
iconv -f UTF-8  -t GBK 		UTF-8.txt -o GBK.txt
iconv -f UTF-8  -t ISO-8859-1 UTF-8.txt -o ISO-8859-1.txt

 三、字符集转换相关编程实现

随着互联网时代的到来,通过互联网进行文字交流也逐渐增多:浏览外国的网站,这个时候字符编码的转换变得尤为重要。这带来了一个问题,就是许多字符在某一种编码方式中没有。为了解决这种混乱,Unicode的编码方式被建立。Unicode是一种超级编码包含了所有这些编码的字符集,因此一些新的文本格式像XML的默认编码方式就是Unicode.

但是很多老式的计算机还在使用当地的传统的字符编码方式。而一些程序,例如邮件程序和浏览器必须能在这些不同的用户编码之间作转换。其他的一些程序则内置支持Unicode,以顺利支持国际化的处理,但是仍然有在Unicode和其他的传统编码之间转换的需求。GNU的libiconv就是为这两种应用设计的编码转换库。

3.1、windows下vs2015编译安装iconv

这里可以直接使用我已经编译好的libiconv库,具体链接:

链接:https://pan.baidu.com/s/1FAqkN9ggxSpLlRhvPtMIig 提取码:6jpt

参考链接:在Windows下编译iconv

3.2、在vs2015中编译实现转换

  • 获得转换句柄

函数:iconv_t iconv_open (const char* tocode, const char* fromcode);

其中:tocode:目标编码方式;fromcode: 源编码方式

另外,还可以设置目的编码, 如TRANSLIT:遇到无法转换的字符就找相近字符替换; IGNORE  :遇到无法转换字符跳过。具体使用中可以这样设置:char *encTo = "UNICODE//TRANSLIT",UNICODE表示编码方式,TRANSLIT编码设置。

范例:iconv_t cd = iconv_open(“UTF−8”, “UTF−16”);

  • 进行转换

函数:size_t iconv (iconv_t cd, const char* * inbuf, size_t * inbytesleft, char* * outbuf, size_t * outbytesleft);

其中:

cd:iconv_open()产生的句柄

inbuf:需要转换的字符串

inbytesleft:存放还有多少字符没有转换

outbuf:存放转换后的字符串

outbytesleft:存放转换后,tempoutbuf剩余的空间

返回值:返回-1则说明出现异常,错误码:E2BIG:outbuf没有足够的空间;EILSEQ:遇到无效的多字节序列;EINVAL:遇到不完整的多字节序列。

范例:size_t ret = iconv(cd, &srcstart, &srclen, &tempoutbuf, &outlen);

  • 关闭句柄

函数:int iconv_close (iconv_t cd);

范例:iconv_close(cd);

这里有个最基本的例子来说明,具体代码如下:

#include<iostream>
#include<string>
#include<iconv.h>

using namespace std;

int main(){
	/* 目的编码, TRANSLIT:遇到无法转换的字符就找相近字符替换
	*          IGNORE  :遇到无法转换字符跳过*/
	//char *encTo = "UNICODE//TRANSLIT";
	// char *encTo = "UNICODE//IGNORE";
	char *encTo = "UTF-16";
	/* 源编码 */
	char *encFrom = "UTF-8";

	/* 获得转换句柄
	*@param encTo 目标编码方式
	*@param encFrom 源编码方式
	*
	* */
	iconv_t cd = iconv_open(encTo, encFrom);
	if (cd == (iconv_t)-1)
	{
		perror("iconv_open");
	}

	/* 需要转换的字符串 */
	const char inbuf[1024]="学习unicode编码";
	size_t srclen=strlen(inbuf);
	cout<<"srclen= "<<srclen<<endl;

	/* 存放转换后的字符串 */
	size_t outlen = 1024;
	char outbuf[1024];
	size_t utf16_len = outlen;
	memset(outbuf, 0, outlen);

	/* 由于iconv()函数会修改指针,所以要保存源指针 */
	const char *srcstart = inbuf;
	char *tempoutbuf = outbuf;

	printf("utf8:\n");
	for (int i = 0; i < strlen(inbuf); i++)
	{
		printf("%02x ", inbuf[i]);
	}
	printf("\n");

	/* 进行转换
	*@param cd iconv_open()产生的句柄
	*@param srcstart 需要转换的字符串
	*@param srclen 存放还有多少字符没有转换
	*@param tempoutbuf 存放转换后的字符串
	*@param outlen 存放转换后,tempoutbuf剩余的空间
	*
	* */
	outlen = 20;
	printf("1 srcstart=%p, tempoutbuf=%p, srclen=%ld, outlen=%ld\n",
		srcstart, tempoutbuf, srclen, outlen);
	size_t ret = iconv(cd, &srcstart, &srclen, &tempoutbuf, &outlen);
	printf("2 srcstart=%p, tempoutbuf=%p, srclen=%ld, outlen=%ld\n",
		srcstart, tempoutbuf, srclen, outlen);

	if (ret == -1)
	{
		perror("iconv");
	}
	utf16_len = utf16_len - outlen;
	printf("inbuf=%s, srclen=%d, outbuf=%s, outlen=%d,  ret = %d\n",
		inbuf, srclen, outbuf, utf16_len, ret);

	printf("utf16:\n");
	for (int i = 0; i < utf16_len; i++)
	{
		printf("%02x ", outbuf[i]);
	}
	printf("\n");
	/* 关闭句柄 */
	iconv_close(cd);

	return 0;
}

最终结果如下:

参考链接:

开发文档:http://www.gnu.org/software/libiconv/documentation/libiconv-1.13/

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值