猫猫哥

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

编码小结

1 初识编码

所谓编码,是信息从一种形式或格式转换为另一种形式的过程。

字符编码,从自然语言的字符的一个集合(如字母表或音节表),到其他东西的一个集合(如号码或电脉冲)的映射

Notepad++中编码

ANSI:windows特有,在中国大陆即为GBK (DBCS Double Byte Charecter Set,双字节字符集)
UCS-2:即Unicode,(Universal Multiple-Octet Coded Character Set)
UTF:(UCS Transfer Format,用以存储和传输的格式)
BOM头:纯文本文件开始的几个表示编码格式的字节(Byte Order Mark)
编码方式 BOM
UTF-8 0xEFBBBF
UnicodeBig 0xFEFF
UnicodeSmall 0xFFFE

2 关于ANSI

ANSI不是某种特定的编码,而是windows在不同系统中表示不同的编码。

特别地,美国系统就是ASCII编码;韩国系统就是EUC-KR编码;中国系统就是GBK编码。

graph LR A(ASCII)==>B(扩展ASCII) B==>C(GB2312) C==>E(GBK) E==>F(GB18030) B==>D(Big5)

Winodows怎么区别ANSI背后真正的编码?

Windows code pages,即我们经常见到的cpxxx

cp936表示GBK,cp950表示Big5,cp437表示ASCII

3 系统编码的查看与修改

windows

查看:可在命令行下执行chcp来查看当前的code page
修改终端的active cp:在命令行输入chcp xxx(只在终端起作用,不影响系统默认的ANSI编码)
修改系统的cp(根据当前系统的locale来设置,控制面板=>区域=>修改系统区域设置)

Windows下系统编码修改

Linux

查看:locale


LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=zh_CN.UTF-8
LC_TIME=zh_CN.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=zh_CN.UTF-8
LC_MESSAGES="en_US.UTF-8"
LC_PAPER=zh_CN.UTF-8
LC_NAME=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
LC_TELEPHONE=zh_CN.UTF-8
LC_MEASUREMENT=zh_CN.UTF-8
LC_IDENTIFICATION=zh_CN.UTF-8
LC_ALL=

修改:export LC_ALL=zh_CN.GBK

变量优先级

LC_ALL > LC_* >LANG

LANG:所有没有设置的locale变量的默认locale
LANGUAGE:设置应用程序的界面语言

4 国标

graph LR A(区位码)==>B(GB2312) B==>C(GBK) C==>D(GB18030)

区位码

早年的中国编码标准,由4个十进制数字表示一个字符,前两位为"区",后两位为"位"。汉字区号从16开始,位号从1开始。
区位码节选表

01-09区为特殊符号
16-55区为一级汉字,按拼音排序
56-87区为二级汉字,按部首笔画排序

GB2312

基于区位码,用双字节表示汉字和汉字字符,0xA0+区号,0xA0+位号。

以汉字“安”为例
区位码是1618(十进制)
GB2312编码是 0xA0 +16 0xA0 +18 ==> 0xB0 0xB2

GB2312编码范围是0xB0A1~0xF7FE(收录汉字6763+其他字符682)

全角半角

GB编码兼容ASCII,数字2有两个编码。

其中ASCII编码是0x32,由区位码(0218)而来的编码是0xA3B2。

前者单字节的是半角,后者双字节的是全角。

GBK

首字节在0x810xFE,尾字节在0x400xFE,剔除xx7F一条线

GBK和ASCII码区分

ASCII只有0-127(0x00~0x7F),高字节的最高位为0则为ASCII,为1则为中文

GB18030

  • 变长编码
  • 支持少数民族文字
  • 收录范围包括繁体汉字以及日韩汉字
单字节 0x00-0x7F,与ASCII兼容
双字节 首字节0x81到0xFE,尾字节0x40到0xFE(不包括0x7F),与GBK兼容
四字节 第一个字节0x81到0xFE,第二个字节0x30到0x39,第三个字节0x81-0xFE,第四个字节0x30-0x39

5 UnicodeUTF-8

各国编码标准互不兼容,推出统一标准Unicode

两者关系可以类比成区位码同GB2312的关系

绝大多数程序只支持双字节,即UCS-2

UTF-8:针对Unicode的可变长字符编码(多字节串,第一个字节在C0到FD之间,后面的字节在80到BF之间)

Unicode与UTF-8的转换

Unicode UTF-8 有效位数 编码范围
U-00000000 ~ U-0000007F 0xxxxxxx 7 0x00-0x7F
U-00000080 ~ U-000007FF 110xxxxx 10xxxxxx 11 0xC080-0xDFBF
U-00000800 ~ U-0000FFFF 1110xxxx 10xxxxxx 10xxxxxx 16 0xE08080-0xEFBFBF
U-00010000 ~ U-001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 21 0xF0…-0xF7…
U-00200000 ~ U-03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 26 0xF8…-0xFB…
U-04000000 – U-7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 31 0xFC…-0xFD…

openssl实现(crypto/asn1/a_utf8.c)

  • Unicode转UTF-8
int UTF8_putc(unsigned char *str, int len, unsigned long value)
{
    if (!str)
        len = 6;                /* Maximum we will need */
    else if (len <= 0)
        return -1;
    if (value < 0x80) {
        if (str)
            *str = (unsigned char)value;
        return 1;
    }
    if (value < 0x800) {
        if (len < 2)
            return -1;
        if (str) {
            *str++ = (unsigned char)(((value >> 6) & 0x1f) | 0xc0);
            *str = (unsigned char)((value & 0x3f) | 0x80);
        }
        return 2;
    }
    if (value < 0x10000) {
        if (len < 3)
            return -1;
        if (str) {
            *str++ = (unsigned char)(((value >> 12) & 0xf) | 0xe0);
            *str++ = (unsigned char)(((value >> 6) & 0x3f) | 0x80);
            *str = (unsigned char)((value & 0x3f) | 0x80);
        }
        return 3;
    }
    if (value < 0x200000) {
        if (len < 4)
            return -1;
        if (str) {
            *str++ = (unsigned char)(((value >> 18) & 0x7) | 0xf0);
            *str++ = (unsigned char)(((value >> 12) & 0x3f) | 0x80);
            *str++ = (unsigned char)(((value >> 6) & 0x3f) | 0x80);
            *str = (unsigned char)((value & 0x3f) | 0x80);
        }
        return 4;
    }
    if (value < 0x4000000) {
        if (len < 5)
            return -1;
        if (str) {
            *str++ = (unsigned char)(((value >> 24) & 0x3) | 0xf8);
            *str++ = (unsigned char)(((value >> 18) & 0x3f) | 0x80);
            *str++ = (unsigned char)(((value >> 12) & 0x3f) | 0x80);
            *str++ = (unsigned char)(((value >> 6) & 0x3f) | 0x80);
            *str = (unsigned char)((value & 0x3f) | 0x80);
        }
        return 5;
    }
    if (len < 6)
        return -1;
    if (str) {
        *str++ = (unsigned char)(((value >> 30) & 0x1) | 0xfc);
        *str++ = (unsigned char)(((value >> 24) & 0x3f) | 0x80);
        *str++ = (unsigned char)(((value >> 18) & 0x3f) | 0x80);
        *str++ = (unsigned char)(((value >> 12) & 0x3f) | 0x80);
        *str++ = (unsigned char)(((value >> 6) & 0x3f) | 0x80);
        *str = (unsigned char)((value & 0x3f) | 0x80);
    }
    return 6;
}
  • UTF-8转Unicode
int UTF8_getc(const unsigned char *str, int len, unsigned long *val)
{
    const unsigned char *p;
    unsigned long value;
    int ret;
    if (len <= 0)
        return 0;
    p = str;

    /* Check syntax and work out the encoded value (if correct) */
    if ((*p & 0x80) == 0) {
        value = *p++ & 0x7f;
        ret = 1;
    } else if ((*p & 0xe0) == 0xc0) {
        if (len < 2)
            return -1;
        if ((p[1] & 0xc0) != 0x80)
            return -3;
        value = (*p++ & 0x1f) << 6;
        value |= *p++ & 0x3f;
        if (value < 0x80)
            return -4;
        ret = 2;
    } else if ((*p & 0xf0) == 0xe0) {
        if (len < 3)
            return -1;
        if (((p[1] & 0xc0) != 0x80)
            || ((p[2] & 0xc0) != 0x80))
            return -3;
        value = (*p++ & 0xf) << 12;
        value |= (*p++ & 0x3f) << 6;
        value |= *p++ & 0x3f;
        if (value < 0x800)
            return -4;
        ret = 3;
    } else if ((*p & 0xf8) == 0xf0) {
        if (len < 4)
            return -1;
        if (((p[1] & 0xc0) != 0x80)
            || ((p[2] & 0xc0) != 0x80)
            || ((p[3] & 0xc0) != 0x80))
            return -3;
        value = ((unsigned long)(*p++ & 0x7)) << 18;
        value |= (*p++ & 0x3f) << 12;
        value |= (*p++ & 0x3f) << 6;
        value |= *p++ & 0x3f;
        if (value < 0x10000)
            return -4;
        ret = 4;
    } else if ((*p & 0xfc) == 0xf8) {
        if (len < 5)
            return -1;
        if (((p[1] & 0xc0) != 0x80)
            || ((p[2] & 0xc0) != 0x80)
            || ((p[3] & 0xc0) != 0x80)
            || ((p[4] & 0xc0) != 0x80))
            return -3;
        value = ((unsigned long)(*p++ & 0x3)) << 24;
        value |= ((unsigned long)(*p++ & 0x3f)) << 18;
        value |= ((unsigned long)(*p++ & 0x3f)) << 12;
        value |= (*p++ & 0x3f) << 6;
        value |= *p++ & 0x3f;
        if (value < 0x200000)
            return -4;
        ret = 5;
    } else if ((*p & 0xfe) == 0xfc) {
        if (len < 6)
            return -1;
        if (((p[1] & 0xc0) != 0x80)
            || ((p[2] & 0xc0) != 0x80)
            || ((p[3] & 0xc0) != 0x80)
            || ((p[4] & 0xc0) != 0x80)
            || ((p[5] & 0xc0) != 0x80))
            return -3;
        value = ((unsigned long)(*p++ & 0x1)) << 30;
        value |= ((unsigned long)(*p++ & 0x3f)) << 24;
        value |= ((unsigned long)(*p++ & 0x3f)) << 18;
        value |= ((unsigned long)(*p++ & 0x3f)) << 12;
        value |= (*p++ & 0x3f) << 6;
        value |= *p++ & 0x3f;
        if (value < 0x4000000)
            return -4;
        ret = 6;
    } else
        return -2;
    *val = value;
    return ret;
}

6 区分不同编码

  • 有BOM头

直接根据BOM头区分

  • 没有BOM头

需要大量的编码分析
通常应用会有自己庞大的词库,常见词组编码组合,匹配度越高,越有可能是该编码。

7 Urlencode

不同于上面的编码,上面的编码时字符和数字的对应,url编码是字符替换,将非ASCII字符和一些容易引起问题的字符替换。

uri允许的字符分为保留字符和未保留字符

未保留字符

保留字符

8 X509证书中DN项的string类型(crypto/asn1/a_mbstr.c)

  • 输入编码控制

      openssl中定义了以下编码格式
      MBSTRING_ASC(ASCII),MBSTRING_BMP(UCS-2),MBSTRING_UNIV(UCS-4), MBSTRING_UTF8(UTF-8)
      默认是MBSTRING_ASC,可以在证书请求命令中加-utf8设置为MBSTRING_UTF8
      `openssl req -new -key test.key-config test.conf -out test.req -utf8`
      注意输入编码格式要跟配置文件实际的编码一致
    
  • 输出编码控制

    输出的编码格式由String类型决定

    ASN1 String类型 编码
    Numeric,Printable,IA5,T61 MBSTRING_ASC
    BMP MBSTRING_BMP
    Universal MBSTRING_UNIV
    UTF8 MBSTRING_UTF8

    可以通过配置文件中string_mask控制证书DN项的String类型

    string_mask 支持的String类型
    default PrintableString, T61String, BMPString
    pkix PrintableString, BMPString
    utf8only UTF8Strings
    nombstr PrintableString, T61String
  • openssl中生成证书请求时,String类型的确认

    Numeric < Printable < IA5 < T61 < BMP < Universal < UTF8

    根据DN项的字符范围,选择最小的符合类型

  • 避免证书中出现中文乱码

  1. 将配置文件转码成UTF-8格式(GBK转UTF-8)

    iconv -f GBK -t UTF-8 old.conf -o new.conf

  2. 证书请求时加上-utf8

    openssl req -new -key test.key -config new.conf -out test.req -utf8

9 vim中的各种encoding

支持中文编码的基础

  • 编译时包含+multi_byte和+iconv两个特性,可以用:version命令查看

编码设置项

  • encoding(enc)

      vim内部的使用编码,影响vim内部的buffer,菜单文本,消息文本等
      Unix下默认等于locale,Windows下则是当前code page
      只在启动的时候设置一次,建议始终设置为utf-8
    
  • fileencodings(fencs)

      打开文件时,会从此列表中所列选项逐一探测文件编码,并且将fileencoding设置为最终探测到的字符编码方式。最好将unicode放到最前面,latin1放到最后面。
    
  • fileencoding(fenc)

      打开文件时,会根据所识别的编码设置
      保存文件时,会根据filecoding的设置值来保存
    
  • termencoding(tenc)

      在终端环境下使用时,用来告诉vim当前终端所使用的编码,用来显示
    

vim中编码转换

graph LR A(打开时fileencodings探测)==>B(fileencoding) B==>C(内部编码encoding) C==>D(根据termencoding转码显示)
  • 打开文件,从fileencoding转成encoding,然后将转换后的内容放到buffer里面。在转换过程中如果含有不支持的字符,会丢失
  • 保存文件,相反的过程
  • 终端使用vim的时候,将内部编码转换为termencoding显示。如果含有不支持的字符会显示问号,但是不影响编辑。如果没有设置,则直接使用encoding,不转换

推荐设置

:set encoding=utf-8
:set termencoding=utf-8
:set fileencoding=utf-8
:set fileencodings=ucs-bom,utf-8,cp936,gb18030,big5,euc-jp,euc-kr,latin1

fencview

内置的编码识别机制,识别率是很低的

推荐使用fencview,该插件使用词频统计的方式识别编码,正确率非常高。

posted on 2018-07-28 15:53  猫猫哥  阅读(2051)  评论(0编辑  收藏  举报