由于在项目中遇到小语种的坑,要在文本中筛选出来做特殊处理。本以为很快解决的问题,各种google,百度后还是糊里糊涂,一知半解,没有很好找出来,于是决定仔细研究一下字符编码。
藏文:དུས་དབང
维文:ئوغلانلىرى
首先要区分编码和编码方式这两个概念
我们常说的的ascii码,unicode码,gbk码是编码(可以理解为一个码表),而utf8,utf16是编码方式(就像视频压缩方式一样,将视频转化为mp4格式、rmvb格式来存储)
学计算机的都知道,计算机信息都是以二进制的方式存储的,为了在屏幕上显示这些信息(字符串),60年代美国指定了一套字符编码,对,就是ASCII码,它是编码表,用8位来表示,可以表示256种字符,这个是一一对应的,比如字符0010 0001 就表示‘!’ ,而0100 0001 就表示‘A’ ;
明显256种是不够的,中文呢?日文呢(やめて)?韩文呢(오빠)?所以计算机科学领域的专家就提出来了Unicode编码,这位兄弟用16位来表示,可以标志65536种字符,我们伟大的中文的unicode编码是0x4E00~0x9FA5;
具体的码表http://blog.csdn.net/hherima/article/details/9045861
同样GBK码就是中国人制定,它向下与 GB 2312 编码兼容, 亦采用双字节表示,总体编码范围为 8140-FEFE, 首字节在 81-FE 之间,尾字节在 40-FE 之间,剔除 xx7F 一条线。总计 23940 个码位,共收入 21886 个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号 883 个。
具体的码表http://www.qqxiuzi.cn/zh/hanzi-gbk-bianma.php
但是unicode编码带来了一些问题,怎么区分assii?如果都用两个字节来表示,那就很浪费存储。
这个时候就需要utf编码方式出现了。
我们动手做个实验
分别以utf8编码和unicode编码来输入字符串:jeff是中国人(可以用iconv -c -f utf8 -t unicode 来转换, )然后用od命令来查看存储的二进制文件。
看不清楚?这样呢?
看到没有,这是极度的浪费存储,因为欧美基本都是英文字符,所以这样肯定不能广泛应用。
UTF-8是UNICODE的一种变长字符编码,优点就不多说了,自行百度,它和unicode不是一对一的转换的,而是有规则算法转换的,utf8分为1~6字节来存储,具体的转换规则如下
现在是不是阔然开朗了?
实践
那现在来考考你
1、根据上面的表,怎么判断utf8里面那些是中文或者说剔除非中文?
答案:第一步,先将utf8转化unicode ; 第二步,判断unicode码的大小是否在0x4E00~0x9FA5;在实践中,我取了巧,只选取第三行的(三个字节)来判断是否为 中文,主要代码如下。
/获取UTF8码字的长度
uint16_t UTF8GetWordLength(unsigned char cFirst)
{
if (cFirst < 0x80 ) return 1;
if (cFirst >= 0xC0 && cFirst < 0xE0) return 2;
if (cFirst >= 0xE0 && cFirst < 0xF0) return 3;
if (cFirst >= 0xF0 && cFirst < 0xF8) return 4;
if (cFirst >= 0xF8 && cFirst < 0xFC) return 5;
if (cFirst >= 0xFC ) return 6;
//码字错误也返回 1 ,依次跳过错误码字使之恢复到正常码字
return 1;
}
//转换为unicode判断是否为中文
bool UTF8ChineseChar(unsigned char uc1st, unsigned char uc2nd, unsigned char uc3rd)
{
uint64_t ddwUnicode;
ddwUnicode = (uc1st & 0x0F) << 12;
ddwUnicode |= (uc2nd & 0x3F) << 6;
ddwUnicode |= (uc3rd & 0x3F);
return (ddwUnicode >= 0x4E00 && ddwUnicode <= 0x9FA5);
}
2、怎么将GBK 转化为utf8?(其实中文存储两者大小一样)
答案:没有捷径,只能先将GBK转化为unicode,再将unicode转化为UTF8;GBK和unicode只两种不同的编码,所以可以通过查表的方式来转换。
static const unsigned short tab_GBK_to_UCS2[][2] =
{
/* GBK Unicode 字 */
{0x8140, 0x4E02}, // 丂
{0x8141, 0x4E04}, // 丄
{0x8142, 0x4E05}, // 丅
{0x8143, 0x4E06}, // 丆
{0x8144, 0x4E0F}, // 丏
... ...
{0x817F, 0x0001}, // XXXXX
... ...
};
3、怎么找到我文本的问题,小语种?
提示在这里可以查询到https://unicode-table.com/cn/blocks/*/
藏文的unicode编码是0x0F00—0x0FFF
维文的unicode编码0x0600-0x06FF
4、那linux下怎么读取utf8和unicode文件?
答:用可以用字符串的方式打开文件然后用fgets来读取utf8 但是不用用字符串的方式打开unicode文件来读取,必须用二进制的方式然后用fread来读取,因为有的字节是0x00,字符串方式读取就截断。另外不能用strlen来判断中文字数的多少~~
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
void PrintHex(const char *src,int len )
{
for(int i=0;i<len;i++){
printf(" %02x",(unsigned char )src[i]);
}
printf("\n");
}
int main(int argc,char ** argv)
{
FILE *fp;
if((fp=fopen(argv[1],"r"))==NULL)
{
printf("open file error %s\n",argv[1]);
exit(0);
}
char szLine[512];
char c;
while(!feof(fp))
{
if(fgets(szLine,512,fp)!=NULL){
printf("%s len %d \n",szLine,strlen(szLine));
PrintHex(szLine,strlen(szLine));
}
}
return 1;
}