zoukankan      html  css  js  c++  java
  • 字符编码知识简介和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的个数不一样也不行,所以实际上可以利用的又缩小了,只有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("并不支持这种方式
    ");
    
                      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
    ",len);
    
     
    
     
    
             FILE *fp = fopen(argv[1],"r");
    
             if(fp == NULL)
    
             {
    
                      printf("文件打开失败!
    ");
    
                      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("读取文件有问题
    ");
    
                      free(src_s);
    
                      fclose(fp);
    
                      return -1;
    
             }
    
             printf("文件中转换前为:%s
    ",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
    ",out);
    
     
    
             fclose(fp);
    
             free(out);
    
             return 0;
    
    }
    
     
    
     
    
     
    
     
    
      

    最后附上源代码的链接

     链接:http://pan.baidu.com/s/1bpGVg8v 密码:t2xw

    如果你觉得对有用,请赞一个吧

         

  • 相关阅读:
    排列组合
    分治——最大连续数组和
    分治——最近点对
    Java数据类型
    4源代码的下载和编译
    3Git使用入门
    2.3搭建Android应用程序开发环境
    2.2安装JDK
    2.1Android底层开发需要哪些工具
    1.8小结
  • 原文地址:https://www.cnblogs.com/qingergege/p/7491590.html
Copyright © 2011-2022 走看看