字符编码知识简介和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 != 0  || !S_ISREG(st_buffer.st_mode))

         {

                  perror("读取文件状态出错或文件不是普通文件");

                  return -1;

         }

         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)-1)

         {

                  printf("并不支持这种方式\n");

                  return NULL;

         }

         size_t out_len = 2 * src_len;

         char *out = (char *)malloc(out_len);

         if(out == NULL)

         {

                  iconv_close(cptr);

                  return NULL;

         }

         memset(out,0,out_len);

         char *dest = out;

         size_t err = -1;

         size_t inlen = src_len;

         char *in = src;

         err = iconv(cptr,&in,&inlen,&out,&out_len);

         if(err != (size_t)-1 )

         {

                  iconv_close(cptr);

                  return dest;

         }

         free(dest);

         iconv_close(cptr);

         return NULL;

}

int main(int argc, char *argv[])

{

         off_t len = get_flen(argv[1]);

         if(len == -1)

                  return -1;

         printf("文件的长度为:%zd\n",len);

         FILE *fp = fopen(argv[1],"r");

         if(fp == NULL)

         {

                  printf("文件打开失败!\n");

                  return -1;

         }

         void *src = NULL;

         src = calloc(1, len + 1);

         if(src == NULL)

         {

                  fclose(fp);

                  return -1;

         }

         void *src_s = src;

         if((size_t)len != fread(src,1,len,fp))

         {

                  printf("读取文件有问题\n");

                  free(src_s);

                  fclose(fp);

                  return -1;

         }

         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 -1;

         }

         printf("转换后为:%s\n",out);

         fclose(fp);

         free(out);

         return 0;

}
时间: 2024-10-26 05:46:37

字符编码知识简介和iconv函数的简单使用的相关文章

python学习笔记(集合的使用、文件操作、字符编码与转码、函数)

集合 集合(set):把不同的元素组成一起形成集合,是python基本的数据类型. 集合元素(set elements):组成集合的成员 为什么需要集合? 集合的作用 1 .列表去重复数据 按照现有知识的解决思路:先设置空列表,然后使用for寻获,把需要去重的列表的第一个数据放到新列表中,然后依次取出第二个数据,把第二个数据和第一个数据作比较,如果不一样,则存入新列表中:以此类推,每取一次都要和新列表中的数据作对比,不一样的则添加入新列表中. 2. 关系测试 比如有学员同时报了python班和l

字符编码、文件操作、函数定义

一.字符编码 字符串是一种数据类型,但是,字符串比较特殊的是还有一个编码问题. 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理.最早的计算机在设计时采用8个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是255(二进制11111111=十进制255),如果要表示更大的整数,就必须用更多的字节.比如两个字节可以表示的最大整数是65535,4个字节可以表示的最大整数是4294967295. 由于计算机是美国人发明的,因此,最早只有127个字符被编

python第三天:字符编码、文件操作、函数

作业问题回顾 表格化输出 主要考察字符串的expandtabs的方法,使用空格替换TAB. 百鸡百钱 百鸡百钱的主要是用进行多次判断,然后输出打印. 上课内容 字符编码 概念 编码是计算机用来将人类可读的字符存储成二进制信息而使用的一种格式.字符编码主要针对字符的编码. python中相关的方法 decode:解码,将其他格式的数据转化为unicode格式的数据.转化以后就是Bytes类型的数据.Bytes的数据汉字会转化成字节码,而ascii码会转化成b'meg'的形式.可以接参数,接的参数就

python字符编码、文件处理、函数

一.字符编码1.什么是字符编码 把字符转换成计算机可识别的机器码(0,1)的过程,称之为字符编码. 2.字符编码的类型 1)现代计算机起源于美国,最早诞生也是基于英文考虑的ASCII ASCII:一个Bytes(字节)代表一个字符(英文字符.键盘上的所有其它字符),1Bytes=8bit,8bit可以表示为2的8次方种变化,即可以表示256个字符. ASCII最初只用了后攻位,127个数字,已经完全能够代表键盘上所有的字符了(英文字符/键盘的所有其他字符),后期将拉丁文也编码进了ASCII表,占

字符编码知识

一直对字符的各种编码方式懵懵懂懂,什么ANSI UNICODE UTF-8 GB2312 GBK DBCS UCS--是不是看的很晕,假如您细细的阅读本文你一定可以清晰的理解他们.Let's go! 很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物.他们看到8个开关状态是好的,于是他们把这称为"字节".?再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去.他们看到这样是好的,于是它们就这机器

Python字符编码与函数基本使用-day3

解决Python2和Python3中字符编码的问题 补充Python2中文件操作的说明 函数使用基础 函数的类型 一.Python2中的字符存在的解码编码问题 如果是现在正在用Python2的人应该都知道存在字符编码问题,就举一个最简单的例子吧:Python2是无法在命令行直接打印中文的,当然他也是不会报错的,顶多是一堆你看不懂的乱码.如果想在直接显示中文,我们是可以在Python2文件头部申明字符编码的格式.如下图 这里    #-*-coding:utf-8 -*- 是用来申明下面的代码是用

关于Unicode,字符集,字符编码

基本概念 字符[character] 字符代表了字母表中的字符,标点符号和其他的一些符号.在计算机中,文本是由字符组成的. 字符集合[character set] 由一套用于特定用途的字符组成,例如支持西欧语言的字符集合,支持中文的字符集合.字符集合只定义了符号和他们的语意,其实跟计算机没有直接关系. 现实生活中,不同的语系有自己的字符集合,例如藏文有自己的字符集合,汉文有自己的字符集合.到计算机的世界中,也有各种字符集合,例如ASCII字符集合,GB2312字符集合,GBK字符集合.还有一个其

php学习之道:php中iconv函数 详解

iconv函数库能够完成各种字符集间的转换,是php编程中不可缺少的基础函数库. 用法如下: $string = "亲爱的朋友欢迎访问胡文芳的博客,希望给您带来一点点的帮助!"; iconv("utf8","gbk",$string)//将字符串string  编码由utf8转变成gbk: 扩展如下: echo $str= '你好,欢迎访问胡文芳的博客,该博客记录一个程序员的成长过程!'; echo ' '; echo iconv('GB2312

Gnu Linux下文件的字符编码及转换工具

/*********************************************************************  * Author  : Samson  * Date    : 07/03/2014  * Test platform:  *              3.11.0-12-generic #19-Ubuntu  *              GNU bash, version 4.2.45  * ****************************