字符编码知识简介和iconv函数的简单使用
字符编码知识简介和iconv函数的简单使用
字符编码知识简介
我们知道,在计算机的世界其实只有0和1。期初计算机主要用于科学计算,而我们知道一个数,除了用我们常用对10进制表示,也可以用2进制表示,所以只有0和1就可以进行科学计算,但是为了便于计算,大神们还是向计算机中引入的编码,比如通常我们用补码表示一个负数。所以编码这个东西,是从一开始就伴随着计算机的。到现在,我们的生活已经完全离不开计算机了,计算机也不仅仅用于科学计算了,更多地应用系信息处理。那计算机怎样表示与我们生活息息相关的事物呢,一个直接的办法就是编码。比如计算机中只有0和1,没有文字,那么我们就想办法用0和1的序列来代表文字,这就是文字编码。
ASCII编码
计算机这东西是美国人发明的,所以美国人也最先用0和1的序列给英文字母进行了编码(当然还有一些特殊字符或者用于控制字符)。英文只有26个字母,在加上那些特殊字符,也不多。所以美国人选择用8个0或1的序列来表示一个英文字母或者那些特殊字符。这就是ASCII码。
ASCII码一共规定了128个字符的编码,比如空格"SPACE"是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。
ISO-8859-1编码
计算机发展很快,很快欧洲人也开始尝试编码自己的文字,欧洲的语言大多都是拉丁语系的,和英语很像,而且部分重复,所以欧洲人就想到利用ASCII码没有利用的那一位来编码。所以ISO-8859-1仍采用单字节编码(8位),只是将ASCII没有利用的128个位置利用了起来。而且ISO-8859-1在设计时,前7为和ASCII码一致,也就是说ISO-8859-1是完全兼容ASCII的。
GB2312编码
很快,我们国家开始为汉字编码,由于汉字和拉丁系的文字完全不同,而且汉字的个数很多,所以如果像欧洲的ISO-8859-1那样,只利用ASCII没有利用的部分,只能多表示128个字符,而汉字的数量远远大于这个数,所以用单字节编码汉字是不可行的。于是我们国家在1980年发布了GB2312编码标准,采用两个字节来编码汉字。另外GB2312的两个字节中的每个字节都大于80H(ASCII码不超过80H),所以如果发现一个字节小于80H,那就按ASCII编码解析,如果大于80H,那就按照GB2312编码,两个字节一起处理。所以GB2312和ASCII码是没有冲突的,也可以理解为:GB2312是采用变长编码,单子节编码和ASCII完全相同,双子节则用于汉字的编码,而且双字节中的每个自己都不会和ASCII吗冲突。可以说GB2312是完全兼容ASCII编码的。
GBK编码
GB2312采用双字节编码汉字,双字节也就是16位,最多可以编码65536个字符,但是上面提到了,为了不和ASCII产生冲突,GB2312的每个字节都要大于80H,所以实际上可以利用的就只有2*7=14位,也就是128*128=16384,再加上很多编程语言中,都用0表示字符串的结束,所以编码的时候,全0就只能表示’\0’,即使0的个数不一样也不行,所以实际上可以利用的又缩小了,只有127*127=16129个,远小于65535。实际上GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时,GB 2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符,也就是说,可以利用的16129个位置也没有全部利用。据说,朱镕基的“镕”字并没有收录到GB2312中。为了表示更多的字符,GBK编码在BG2312的基础之上,加入了对更多字符的编码,利用的就是GB2312中没有利用的部分,而且,GB2312已经利用的部分,GBK和它保持一致,也就是说GBK是完全兼容GB2312的。可以认为GBK就是GB2312的超集。
Unicode字符集
前面提到了ISO-8859-1编码,是利用了ASCII没有利用的部分进行编码,但是,法国人用那部分表示法语,意大利人用那部分表示意大利语……这样由于同样的二进制序列表示的含义却不同,这样,用意大利人的编码解释法语就会乱码。所以大神们又想出了一种统一的编码方式,也就是说一个二进制序列表示的编码是唯一的,不会像ISO-8859-1那样同样的二进制序列即表示法语也表示意大利语。这就是Unicode。
UCS-2和UCS-4编码
需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字"严"的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。
为了解决怎样存储Unicode字符集,大神们又提出了两类方式编码方式存储Unicode字符集:定长方式和变长方式。定长方式就是UCS-2和UCS-4,分别用2个字节和4个字节存储。虽然Unicode字符集中对应的二进制数很多超过了2个字节,但是大部分常用的字符对应的二进制数都在2个字节内,所以常用的就是UCS-2。由于采用定长(而且非单字节)所以UCS-2和UCS-4并不兼容ASCII码,所以一段用ASCII编码的英文,如果用USC-2的方式解码,也不行。因为一个是单字节为单位,一个是双子节为单位(前面的GBK,只是用双字节编码汉字,对于半角的英文字母还是和ASCII一样,用单字节编码)。
UTF-8编码
UTF-8是变长字节的Unicode编码方式。
互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
跟据上表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。
下面,还是以汉字"严"为例,演示如何实现UTF-8编码。
已知"严"的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是"11100100 10111000 10100101",转换成十六进制就是E4B8A5。
总结一下:
GBK向下兼容GB2312,GB2312兼容ASCII
UTF-8 兼容 ASCII
UCS-2不兼容ASCII
所以,如果只是英文,GB2312、GBK、UTF-8都一样,但是和UCS-2不一样。
iconv函数简单使用
在linux C中可以使用iconv相关函数进行字符编码的转换。
一共用到三个相关的函数:
iconv_t iconv_open(const char *tocode, const char *fromcode); size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft); int iconv_close(iconv_t cd);
三个函数的声明都在头文件<iconv.h>中
inconv_open函数
iconv_t iconv_open(const char *tocode, const char *fromcode);
iconv_open函数用于创建一个转换描述符,参数为目的编码格式和源编码格式。
int iconv_close函数
int iconv_close(iconv_t cd);
iconv_close用于回收iconv_open分配的资源。
iconv函数
size_t iconv(iconv_t cd,
char **inbuf, size_t *inbytesleft,
char **outbuf, size_t *outbytesleft);
iconv函数用于转换,参数为iconv_open 产生的转换描述符、输入字符串、输入字符串的长度、输出字符串、输出字符串的长度。由于该函数中,参数的值都要发生变化,所以都是传递的指针。
iconv函数的处理过程有点类似于libz中的那个函数。iconv每次转换一个多字节字符(总之就是一次转换一个字符,但是由于编码不同,一个字符占的字节数也不同)。每转换一次都会增加 *inbuf、减少*inbytesleft,增加和减少的量都是被转换的字节数;每一次转换也会增加*outbuf、减少* outbytesleft,增加和减少的量都是转换成的字节数;每次转换也都会更新转换描述符cd。注意转换函数一次转换过程中可能没有任何输出,比如待转换的字符的字节没有全部到来的情况。
由于*inbuf和*outbuf都会变化,所以在调用iconv之前应当将这两个值复制一份,以便以后释放空间和返回正确的字符串。
iconv函数可能会因为下面四种情况而终止:
1、 发现非法的多字节序列,比如说发现不符合编码格式的序列,比如GBK要求每个字节都大于80H,但是发现了一个小于80H的字节,就是非法的,这个时候该函数就会停止并返回-1,而且设置errno为EILSEQ。
2、 输入的字节序列被全部转换,这个时候*inbytesleft已经减到了0,此时返回不可逆的转换的字节数(不太明白,反正不会是-1)
3、 出现了不完整的多字节序列,而且到了输入序列的结尾。什么意思呢,就是,比如说,UTF-8编码,第一个字节表明整个字符需要三个字节进行编码,但是只发现了两个字节,输入序列就结束了。此时返回-1,并且设置errno为EINVAL ,而且*inbuf会指向那个不完整的多字节序列的起始地址。
4、 输出缓冲区没有多余的空间了。此时返回-1,并且设置errno为E2BIG
一个源码:
代码的作用是:从文件中读取字符串(编码格式为UCS-2),转换成UTF-8编码。
#include <stdio.h> //标准输入输出头文件 #include <sys/stat.h> //stat结构体和stat函数所在的头文件 #include <sys/types.h> //基本系统数据类型 #include <iconv.h> #include <string.h> #include <stdlib.h> #include <errno.h> /**利用stat函数和stat结构体获取普通文件的长度 * 不用打开文件,访问文件的实际数据部分,只需访问文件的inode节点 * 效率较前面一个函数高 * 可以通过struct stat判断文件是否为普通文件,避免目录 * 成功返回长度,失败返回-1 * */ off_t get_flen(char *file_path) { struct stat st_buffer; int err = stat(file_path,&st_buffer); if(err != || !S_ISREG(st_buffer.st_mode)) { perror("读取文件状态出错或文件不是普通文件"); return -; } return st_buffer.st_size; } char *to_utf(char *src, size_t src_len,const char * toencode,const char *fromencode) { iconv_t cptr = iconv_open(toencode,fromencode); if(cptr == (iconv_t)-) { printf("并不支持这种方式\n"); return NULL; } size_t out_len = * src_len; char *out = (char *)malloc(out_len); if(out == NULL) { iconv_close(cptr); return NULL; } memset(out,,out_len); char *dest = out; size_t err = -; size_t inlen = src_len; char *in = src; err = iconv(cptr,&in,&inlen,&out,&out_len); if(err != (size_t)- ) { iconv_close(cptr); return dest; } free(dest); iconv_close(cptr); return NULL; } int main(int argc, char *argv[]) { off_t len = get_flen(argv[]); if(len == -) return -; printf("文件的长度为:%zd\n",len); FILE *fp = fopen(argv[],"r"); if(fp == NULL) { printf("文件打开失败!\n"); return -; } void *src = NULL; src = calloc(, len + ); if(src == NULL) { fclose(fp); return -; } void *src_s = src; if((size_t)len != fread(src,,len,fp)) { printf("读取文件有问题\n"); free(src_s); fclose(fp); return -; } printf("文件中转换前为:%s\n",src); //上面的代码都是从文件读入字符 char *out = NULL; out = to_utf((char *)src,(size_t)len,"UTF-8","UCS-2"); free(src_s); if(out == NULL) { fclose(fp); return -; } printf("转换后为:%s\n",out); fclose(fp); free(out); return ; }
最后附上源代码的链接
链接:http://pan.baidu.com/s/1bpGVg8v 密码:t2xw
如果你觉得对有用,请赞一个吧
字符编码知识简介和iconv函数的简单使用的更多相关文章
- php字符编码转换中的iconv与mb_convert_encoding用法
iconv ( 'UTF-8' , 'GBK' , $str ); //将$str字符串 utf-8 编码转换成 gbk: 另外,5.4.0 这个版本起,字符非法时候会返回 FALSE,除非在输出字符 ...
- Python自动化 【第三篇】:Python基础-集合、文件操作、字符编码与转码、函数
1. 集合 1.1 特性 集合是一个无序的,不重复的数据组合,主要作用如下: 去重,把一个列表变成集合实现自动去重. set可以看成数学意义上的无序和无重复元素的集合,因此,两 ...
- 字符编码知识:Unicode、UTF-8、ASCII、GB2312等编码之间是如何转换的?
转自: http://apps.hi.baidu.com/share/detail/17798660 字符编码是计算机技术的基石,想要熟练使用计算机,就必须懂得字符编码的知识.不注意的人可能对这个不 ...
- python开发基础之字符编码、文件处理和函数基础
字符编码 为什么要有字符编码? 字符编码是为了让计算机能识别我们人写的字符,因为计算机只认识高低电平,也就是二进制数"0","1". 一个文件用什么编码方式存储 ...
- Python开发(三):字符编码,文件操作,函数
一:三级菜单 If len(choice) == continue # 判断输入的是否为空,为空就跳出这次循环进行下次循环, exit(“bye”) :退出程序显示,bye 二:编码 最早的编码是as ...
- 关于Unicode,字符集,字符编码,每个程序员都应该知道的事
关于Unicode,字符集,字符编码,每个程序员都应该知道的事 作者:Jack47 李笑来的文章如何判断一个人是否聪明?中提到: 必要.清晰.且准确的概念,是一切思考的基石.所谓思考,很大程度上,就是 ...
- 关于Unicode,字符集,字符编码
基本概念 字符[character] 字符代表了字母表中的字符,标点符号和其他的一些符号.在计算机中,文本是由字符组成的. 字符集合[character set] 由一套用于特定用途的字符组成,例如支 ...
- PHP字符编码问题-总结
今天在网上看到一个人的对于php开发中字符编码的总结,感觉不错,摘录如下: 一,php编码转换 1.通过iconv()函数实现编码转换 语法:iconv(s ...
- [No0000137]字符编码详解
摘要 本文主要介绍了字符编码的基础知识,以及常见的字符编码类型,比如ASCII,Unicode,UTF-8,ISO 8859等,以及各种编码之间的关系,同时专门解释了中文字符相关的编码标准,包括GB2 ...
随机推荐
- Promise和异步编程
前面的话 JS有很多强大的功能,其中一个是它可以轻松地搞定异步编程.作为一门为Web而生的语言,它从一开始就需要能够响应异步的用户交互,如点击和按键操作等.Node.js用回调函数代替了事件,使异步编 ...
- ES2017中的async函数
前面的话 ES2017标准引入了 async 函数,使得异步操作变得更加方便.本文将详细介绍async函数 概述 async 函数是 Generator 函数的语法糖 使用Generator 函数,依 ...
- 用Python识别网站使用的技术
在进行爬虫之前,一般我们都会对要爬取的网站进行识别,识别我们要爬取的网站所使用到的技术,这样才能更有利于我们爬虫工作的进行.所以在此介绍以下如何用Python去识别一个网站所使用到的技术. 环境:Py ...
- 微信小程序-滚动消息通知
写在前面: 微信小程序学的不太多,做了一个简单的项目,回来很快时间内把在深圳两天的房租给赚回来了. 这次我主要想总结一下微信小程序实现上下滚动消息提醒,主要是利用swiper组件来实现,swiper组 ...
- .NET MVC与三层架构
虽然接触了两者有一段时间了,但是有时还是会混淆概念,在此处不打算说明二者的区别,因为二者都是架构模式,并且也有一定的共存度,在实际开发中,严格区分意义不大.基于最近涉及到这部分知识就在复习下,编程过程 ...
- 蓝桥杯比赛javaB组练习《方格填数》
方格填数 如下的10个格子 +--+--+--+ | | | |+--+--+--+--+| | | | |+--+--+--+--+| | | |+--+--+--+ ( ...
- 安徽省2016“京胜杯”程序设计大赛_D_梯田AGAIN
梯田AGAIN Time Limit: 5000 MS Memory Limit: 65536 KB Total Submissions: 95 Accepted: 21 Description 大家 ...
- 数据库db2错误代码大全
sqlcode sqlstate 说明000 00000 SQL语句成功完成01xxx SQL语句成功完成,但是有警告+012 01545 未限定的列名被解释为一个有相互关系的引用+098 01568 ...
- HTML <hr>标签
HTML的<hr>标签用来插入一个水平分割线以把文档分成几部分. 水平线通常显示为一条突出的或者凹下去的线条. 在页面中特定的位置插入水平线,既能使页面的结构清晰,又能使整个页面显得美观, ...
- NYOJ 69 数的长度(数学)
数的长度 时间限制:3000 ms | 内存限制:65535 KB 难度:1 描述 N!阶乘是一个非常大的数,大家都知道计算公式是N!=N*(N-1)······*2*1.现在你的任务是计算出 ...