精华内容
下载资源
问答
  • C++与字符集、字符编码

    千次阅读 2019-11-02 10:50:06
    字符编码 字符集与字符编码的关系 多种字符编码存在的意义 字符编码的发展历史 活动代码页 c++的多字节字符与宽字节字符 c++的多字节字符串与宽字节字符串 C++程序输出字符串的编码 字符串常量 参考文章 ...

     

    目录

     

    问题

    字符集

    字符编码

    字符集与字符编码的关系

    多种字符编码存在的意义

    字符编码的发展历史

    活动代码页

    c++的多字节字符与宽字节字符

    c++的多字节字符串与宽字节字符串

    C++程序输出字符串的编码

    字符串常量

    参考文章


    问题

    字符集和编码往往是IT菜鸟甚至是各种大神的头痛问题。当遇到纷繁复杂的字符集,各种火星文和乱码时,问题的定位往往变得非常困难。本文就将会从原理方面对字符集和编码做个简单的科普介绍,同时也会介绍C++中设置字符串编码以及编码转换的一些方法。

    字符集

    简单的说,字符集就规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。常见的字符集如ASCII、GB2312、GBK等。

    下面就是这个字在各种编码下的十六进制和二进制编码结果

    字符编码

    字符集只是一个规则集合的名字,对应到真实生活中,字符集就是对某种语言的称呼。例如:英语,汉语,日语。

    对于一个字符集来说要正确编码转码一个字符需要三个关键元素:字库表、编码字符集、字符编码。

    字库表是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。

    编码字符集,即用一个编码值来表示一个字符在字库中的位置。

    字符编码定义编码字符集和实际存储数值之间的转换关系。一般来说,都会直接将字符的编码值直接进行存储。例如在ASCII中A在表中排第65位,而编码后A的数值是0100 0001也即十进制的65的二进制转换结果。

    字符集与字符编码的关系

    一般一个字符集等同于一个编码方式,ANSI体系的字符集如ASCII、ISO 8859-1、GB2312、GBK等等都是如此。一般我们说一种编码都是针对某一特定的字符集。
    一个字符集上也可以有多种编码方式,例如UCS字符集(也是Unicode使用的字符集)上有UTF-8、UTF-16、UTF-32等编码方式。

    多种字符编码存在的意义

    既然字库表中的每一个字符都有一个自己的序号,直接把序号作为存储内容就好了。为什么还要多此一举通过字符编码把序号转换成另外一种存储格式呢?

    统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。例如中文地区的程序几乎不会需要日语字符,而一些英语国家甚至简单的ASCII字库表就能满足基本需求。而如果把每个字符都用字库表中的序号来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。于是就出现了UTF-8这样的变长编码。在UTF-8编码中原本只需要一个字节的ASCII字符,仍然只占一个字节。而像中文及日语这样的复杂字符就需要2个到3个字节来存储。

    字符编码的发展历史

    第一个阶段:ASCII字符集和ASCII编码
    计算机刚开始只支持英语(即拉丁字符),其它语言不能够在计算机上存储和显示。ASCII用一个字节(Byte)的7位(bit)表示一个字符,第一位置0。后来为了表示更多的欧洲常用字符又对ASCII进行了扩展,又有了EASCII,EASCII用8位表示一个字符,使它能多表示128个字符,支持了部分西欧字符。

    第二个阶段:ANSI编码(本地化)
    为使计算机支持更多语言,通常使用 0x80~0xFF 范围的 2 个字节来表示 1 个字符。比如:汉字 ‘中’ 在中文操作系统中,使用 [0xD6,0xD0] 这两个字节存储。
    不同的国家和地区制定了不同的标准,由此产生了 GB2312, BIG5, JIS 等各自的编码标准。这些使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码。
    不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

    第三个阶段:UNICODE(国际化)
    为了使国际间信息交流更加方便,国际组织制定了 UNICODE 字符集,为各种语言中的每一个字符设定了统一并且唯一的数字编号,以满足跨语言、跨平台进行文本转换、处理的要求。UNICODE 常见的有三种编码方式:UTF-8(1-4个字节表示)、UTF-16((2个字节表示))、UTF-32(4个字节表示)。

    活动代码页

    代码页是字符集编码的别名,也有人称"内码表"。

    早期,代码页是IBM称呼电脑BIOS本身支持的字符集编码的名称。当时通用的操作系统都是命令行界面系统,这些操作系统直接使用BIOS供应的VGA功能来显示字符,操作系统的编码支持也就依靠BIOS的编码。现在这BIOS代码页被称为OEM代码页。图形操作系统解决了此问题,图形操作系统使用自己字符呈现引擎可以支持很多不同的字符集编码。

    早期IBM和微软内部使用特别数字来标记这些编码,其实大多的这些编码已经有自己的名称了。虽然图形操作系统可以支持很多编码,很多微软程序还使用这些数字来点名某编码。
    下表列出了部分代码页及其国家(地区)或者语言:
    代码页 国家(地区)或语言
    437 美国
    932 日文(Shift-JIS)
    936 中国 - 简体中文(GB2312)
    950 繁体中文(Big5)
    1200 Unicode
    1201 Unicode (Big-Endian)
    50220 日文(JIS)
    50225 韩文(ISO)
    50932 日文(自动选择)
    50949 韩文(自动选择)
    52936 简体中文(HZ)
    65000 Unicode (UTF-7)
    65001 Unicode (UTF-8)

    c++的多字节字符与宽字节字符

    C++基本数据类型中表示字符的有两种:char、wchar_t。
    char叫多字节字符,一个char占一个字节,之所以叫多字节字符是因为它表示一个字时可能是一个字节也可能是多个字节。一个英文字符(如’s’)用一个char(一个字节)表示,一个中文汉字(如’中’)用2个char(二个字节)表示,看下面的例子。

    void TestChar()
    {
    	char ch1 = 's';				// 正确
    	cout << "ch1:" << ch1 << endl;
    	char ch2 = '中';				// 错误,一个char不能完整存放一个汉字信息
    	cout << "ch2:" << ch2 << endl;
    
    	char str[3] = "中";			//前二个字节存放汉字'中',最后一个字节存放字符串结束符\0
    	cout << "str:" << str << endl;
    }

    c++的多字节字符串与宽字节字符串

    字符数组可以表示一个字符串,但它是一个定长的字符串,我们在使用之前必须知道这个数组的长度。为方便字符串的操作,STL为我们定义好了字符串的类string和wstring。大家对string肯定不陌生,但wstring可能就用的少了。

    string是普通的多字节版本,是基于char的,对char数组进行的一种封装。

    wstring是Unicode版本,是基于wchar_t的,对wchar_t数组进行的一种封装。

    ###string 与 wstring的相关转换:
    以下的两个方法是跨平台的,可在Windows下使用,也可在Linux下使用。

    C++程序输出字符串的编码

    C++程序输出的字符串编码规则如下:

    1. 若程序中指定了字符串的编码,则输出字符串的编码与指定编码相同;
    2. 若程序中没有指定字符串的编码,则输出字符串的编码与程序在编译时Windows代码页编码相同(Linux情况尚未实验);
    3. 程序输出字符串的编码与程序源文件编码无关;

    对规则1的证实可以使用如下代码:

    #include <fstream>
    
    int main() 
    {
    	using namespace std;
    
    	ofstream OutFile("OutInUTF8.txt");
    	OutFile << "你" << endl;
    	OutFile.close();
    
    	return 0;
    }

    以上代码在活动代码页为GBK的Window中生成的txt文件的编码是UTF8。

    对规则2的证实可以使用如下代码:

    #include <fstream>
    
    int main() 
    {
    	using namespace std;
    
    	ofstream OutFile("Output.txt");
    	OutFile << "你" << endl;
    	OutFile.close();
    
    	return 0;
    }

    上面的程序如果编译时Window的代码页是936(GBK),结果生成的txt文件编码也是GBK。即使将该程序在Window的代码页为65001(UTF8)的Windows系统中运行,生成的txt文件编码依然是GBK。

    对规则3的证实

    仍然使用上面的程序。但是其源文件编码设成UTF8,然后在代码页是936(GBK)的Window系统上编译,结果生成的txt文件编码是GBK。

    字符串常量

    C++98标准中一些表示字符串常量的标识有:

    • "您好": string字符串常量(字节数组),使用当前系统代码页进行编码

    C++11标准中增加了一些表示字符串常量的标识,如下有:

    • L"您好!": wstring字符串常量,使用文件保存编码方式字符集
    • R"(您好 \n)": 原始字符串常量(字节数组),保留所有的字符
    • u8"您好!": string字符串常量(字节数组),使用UTF8进行编码保存

    可用以下程序进行验证

    #include <string>
    
    
    int main() 
    {
    	using namespace std;
    
    	string str;
    	wstring wstr;
    	
    	str = "你好";
    	wstr = "你好";	//编译报错——“初始化”: 无法从“const char [3]”转换为“std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t>>”
    					
    	str = L"你好";	//编译报错——无法从“const wchar_t [2]”转换为“std::basic_string<char,std::char_traits<char>,std::allocator<char>>”
    	wstr = L"你好";
    	
    	str = R"(你好)";
    	wstr = R"(你好)";//编译报错——二进制“=”: 没有找到接受“const char [5]”类型的右操作数的运算符(或没有可接受的转换)
    
    	return 0;
    }

    参考文章

    带你玩转Visual Studio——带你理解多字节编码与Unicode码

    十分钟搞清字符集和字符编码

    活动代码页简介

    Windows:如何设置Windows的默认代码页

    C++中wchar_t与wstring理解及中文编码的处理

    展开全文
  • 结合Java详谈字符编码和字符集

    万次阅读 多人点赞 2018-07-07 14:04:45
    字符编码和字符集是两个基础性的概念,很多开发人员对其都并不陌生,但是很少有人能将其讲得很准确。当应用出现乱码时,如何分析和定位原因,很多人仍是一头雾水。这篇文章,将从字符编码和字符集的相关概念开始讲解...

    字符编码和字符集是两个基础性的概念,很多开发人员对其都并不陌生,但是很少有人能将其讲得很准确。当应用出现乱码时,如何分析和定位原因,很多人仍是一头雾水。这篇文章,将从字符编码和字符集的相关概念开始讲解,然后结合Java进行实例分析。

    字符编码和字符集的概念

    字符集(character set)是一个系统支持的所有抽象字符的集合。字符(character)就是各种文字和符号,包括国家文字、标点符号、图形符号、数字等。

    如果仅仅是抽象的字符集,其实是顾名思义的,但是我们常说的字符集,其实是指编码字符集(coded character set),比如: Unicode、ASCII、GB2312、GBK等等。什么是编码字符集呢?编码字符集是指,这个字符集里的每一个字符,都对应到唯一的一个代码值,这些代码值叫做代码点(code point),可以看做是这个字符在编码字符集里的序号,字符在给定的编码方式下的二进制比特序列称为代码单元(code unit)。在Unicode字符集中,字母A对应的数值是十六进制下的0041,书写时前面加U+,所以Unicode里A的代码点是U+0041。

    常见的编码字符集有:

    • Unicode:也叫统一字符集,它包含了几乎世界上所有的已经发现且需要使用的字符(如中文、日文、英文、德文等)。
    • ASCII:早期的计算机系统只能处理英文,所以ASCII也就成为了计算机的缺省字符集,包含了英文所需要的所有字符。
    • GB2312:中文字符集,包含ASCII字符集。ASCII部分用单字节表示,剩余部分用双字节表示。
    • GBK:GB2312的扩展,完整包含了GB2312的所有内容。
    • GB18030:GBK字符集的超集,常叫大汉字字符集,也叫CJK(Chinese,Japanese,Korea)字符集,包含了中、日、韩三国语言中的所有字符。

    字符编码(character encoding),是编码字符集的字符和实际的存储值之间的转换关系。常见的编码方式有:UTF-8(Unicode字符集的编码方式)、UTF-16(Unicode字符集的编码方式)、UTF-32(Unicode字符集的编码方式)、ASCII(ASCII字符集的编码方式)等。

    Java的字符编码和字符集

    Java中char类型是16位无符号基本数据类型,用来存储Unicode字符。字符数据类型的范围为0到65535,可以存储65536个不同的Unicode字符,这在起初Unicode字符集不是很大的时候,是没问题的。然而随着Unicode字符集的增长,已经超过65536个了,根据Unicode标准,现在Unicode代码点的合法范围是U+0000到U+10FFFF,U+0000到U+FFFF称为Basic Multilingual Plane(BMP),代码点大于U+FFFF的字符称为增补字符。

    Java如何解决这个问题的呢?

    Java的char类型使用UTF-16编码描述一个代码单元。在这种表现形式下,增补字符用一对代码单元编码,即2个char,其中,第一个值取值自\uD800-\uDBFF(高代理项范围),第二个值取值自\uDC00-\uDFFF(低代理项范围)。Unicode规定,U+D800到U+DFFF的值不对应于任何字符,为代理区。因此,UTF-16利用保留下来的0xD800-0xDFFF区段的码位来对增补字符进行编码。具体的UTF-16编码格式,可见这篇文章:https://www.cnblogs.com/dragon2012/p/5020259.html

    所以,char值表示BMP代码点,包括代理项代码点和UTF-16编码的代码单元。而int值可以表示所有的Unicode代码点,包括增补代码点。int的21个低位表示Unicode代码点,且11个高位必须为0。

    Java字符串由char序列组成,上面我们已经说过,char数据类型是一个采用UTF-16编码表示Unicode代码点的代码单元,大多数的常用Unicode字符使用一个代码单元就可以表示,而增补字符需要一对代码单元表示。我们所熟知的String类型的length方法,它返回的是UTF-16编码表示的给定字符串的代码单元的数量,如果想要得到代码点的数量,可以调用codePointCount()方法,charAt方法返回位于指定位置的代码单元,codePointAt方法则返回指定位置的代码点。详细可见这篇文章:https://www.cnblogs.com/vinozly/p/5155304.html

    Java代码需要编译成class文件后由JVM运行,在class文件里,字符串使用UTF-8编码,保存于常量池中。

    Java里涉及到字符集和字符编码的一些接口

    实例化String对象的时候,可以指定字符集解码指定的字节数组。

    string.getBytes(Charset)方法,使用给定的charset将此String编码到byte序列,并将结果存储到新的byte数组。不带参数的getBytes()方法则使用平台默认的字符集将字符串编码成byte序列,并将结果存储到新的byte数组。

    java.nio.charset.Charset类,定义了用于创建解码器和编码器以及获取与charset关联的各种名称的方法。其使用方式可见这篇文章:https://blog.csdn.net/zmken497300/article/details/51914875

    InputStreamReader和OutputStreamWriter的构造函数,均可以指定该InputStreamReader/OutputStreamWriter使用的字符集。

    常用的一个InputStreamReader和OutputStreamWriter就是FileReader和FileWriter,可以看到这两个类的构造函数,均没有表示Charset的入参,即FileReader和FileWriter,只能以平台默认的字符集来解码和编码。

    这里提到了平台默认的字符集。什么是Java的默认字符集呢?在Java里,如果没有指定Charset的时候,比如new String(byte[] bytes),都会调用Charset.defaultCharset()的方法,该字符集默认跟操作系统字符集一致,也可以通过-Dfile.encoding=叉叉叉来手动设定,这个方法的具体实现如下:

    public static Charset defaultCharset() {
            if (defaultCharset == null) {
    	    synchronized (Charset.class) {
    		java.security.PrivilegedAction pa =
    		    new GetPropertyAction("file.encoding");
    		String csn = (String)AccessController.doPrivileged(pa);
    		Charset cs = lookup(csn);
    		if (cs != null)
    		    defaultCharset = cs;
                    else 
    		    defaultCharset = forName("UTF-8");
                }
    	}
    	return defaultCharset;
        }

    可以看到,defaultCharset只能被赋值一次,所以System.setProperty("file.encoding", "叉叉叉")并不能改变默认编码。

    来看一个具体的例子,在Windows下,打印System.getProperty("file.encoding")结果是GBK,现在创建一个文本文件,使用UTF-8编码,内容如下:

    测试文本。

    接下来创建Java文件,代码如下:

    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class EncodingTest {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		try {
    			System.out.println(System.getProperty("file.encoding"));
    			FileReader fr = new FileReader(new File("test.txt"));
    			System.out.println((char) fr.read());
    			fr.close();
    		} catch (FileNotFoundException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    }

    命令行执行javac EncodingTest.java和java EncodingTest,打印出如下结果:

    GBK
    娴

    通过java -Dfile.encoding=UTF-8 EncodingTest,我们就打印出了正确的文本:

    UTF-8
    测

    Java Web项目中的乱码问题,可参考:https://blog.csdn.net/u013905744/article/details/52456417

    指定编码下字符串所占的字节数

    在业务开发中一个常见的需求是计算字符串在指定编码方式下所占用的字节数,如上面所看到的,Java中可以使用string.getBytes(charsetName).length来实现。在前面的知识的基础上,我们来看下面的几个例子:

    import java.io.UnsupportedEncodingException;
    
    public class MyTest {
    
        public static void main(String[] args) throws UnsupportedEncodingException {
            String str = "a";
            System.out.println(str.getBytes("UTF-8").length);
            String str2 = "中";
            System.out.println(str2.getBytes("UTF-8").length);
        }
    
    }

    该例输出如下:

    1
    3
    
    Process finished with exit code 0

    这个例子中分别输出了只包含一个英文字符的字符串和只包含一个汉字的字符串在UTF-8编码下所占的字节数,可以看到一个英文占用1个字节,一个汉字占用了3个字节。这个例子非常简单。具体的UTF-8编码的字节数和Unicode代码点的对应关系可见下表:

    现在,我们把UTF-8编码换成UTF-16编码看看会输出什么:

    import java.io.UnsupportedEncodingException;
    
    public class MyTest {
    
        public static void main(String[] args) throws UnsupportedEncodingException {
            String str = "a";
            System.out.println(str.getBytes("UTF-16").length);
            String str2 = "中";
            System.out.println(str2.getBytes("UTF-16").length);
        }
    
    }

    输出:

    4
    4
    
    Process finished with exit code 0

    可以看到,输出都是4字节,这似乎和前面讲的不一致,因为不管是'a'还是'中',它们都是Unicode基本多语言平面内的字符,应该占用2字节才对,为什么会是4呢?

    我们继续增加几个字符看下字节数:

    import java.io.UnsupportedEncodingException;
    
    public class MyTest {
    
        public static void main(String[] args) throws UnsupportedEncodingException {
            String str = "a";
            System.out.println(str.getBytes("UTF-16").length);
            str = "ab";
            System.out.println(str.getBytes("UTF-16").length);
            str = "abc";
            System.out.println(str.getBytes("UTF-16").length);
            String str2 = "中";
            System.out.println(str2.getBytes("UTF-16").length);
            str2 = "中华";
            System.out.println(str2.getBytes("UTF-16").length);
            str2 = "中华人";
            System.out.println(str2.getBytes("UTF-16").length);
        }
    
    }

    输出:

    4
    6
    8
    4
    6
    8
    
    Process finished with exit code 0

    可以看到,每加一个BMP平面内的字符,字符串占用的总字节数会多2,这说明确实每个BMP平面内的字符在UTF-16下占用了两个字节,但为什么一开始只有一个字符的时候,占用长度是4呢?我们打印几个只包含一个字符的字符串的UTF-16编码字节序列出来看看,看看里面除了字符本身的编码序列外还有些啥。

    import java.io.UnsupportedEncodingException;
    
    public class MyTest {
    
        public static void main(String[] args) throws UnsupportedEncodingException {
            String str = "a";
            byte[] bytes = str.getBytes("UTF-16");
            for(byte byt: bytes){
                System.out.print(byt + " ");
            }
            System.out.println();
            String str2 = "中";
            byte[] bytes2 = str2.getBytes("UTF-16");
            for(byte byt: bytes2){
                System.out.print(byt + " ");
            }
            System.out.println();
            String str3 = " ";
            byte[] bytes3 = str3.getBytes("UTF-16");
            for(byte byt: bytes3){
                System.out.print(byt + " ");
            }
            System.out.println();
        }
    
    }

    输出如下:

    -2 -1 0 97 
    -2 -1 78 45 
    -2 -1 0 32 
    
    Process finished with exit code 0

    这里分别输出了只包含一个英文字符'a'、一个汉字'中'、一个空格的字符串在UTF-16编码下的字节序列,显而易见的,它们的前两个字节是相同的----十六进制下的FEFF,第三个字节与第四个字节的组合正是字符本身在UTF-16下的代码单元。那这里为什么会冒出一个0xFEFF呢?原来这个0xFEFF叫做“零宽度非换行空格”(ZERO WIDTH NO-BREAK SPACE),它用来标识编码顺序是“大头方式”(Big endian)还是“小头方式”(Little endian)。以字符'中'为例,它的Unicode代码点是4E2D,存储时4E在前,2D在后,就是Big endian;2D在前,4E在后,就是Little endian。FEFF表示存储采用大头方式,FFFE表示使用小头方式。

    最后我们再看看Unicode增补字符在UTF-16下所占的字节数:

    import java.io.UnsupportedEncodingException;
    
    public class MyTest {
    
        public static void main(String[] args) throws UnsupportedEncodingException {
            int ch = 0x2F81A;
            System.out.println(new String(Character.toChars(ch)).getBytes("UTF-16").length);
        }
    
    }

    输出:

    6
    
    Process finished with exit code 0

    因为多了编码顺序标识,所以这里是2+4=6,与我们之前所说的增补字符集占用4个字节符合。这里需要注意的是增补字符的书写方式,Character.toChars(int codePoint)方法可以根据指定的Unicode代码点返回其在UTF-16表现形式下的char数组,我们可以用它来获得增补字符的UTF-16代码单元。

    展开全文
  • 字符集和字符编码

    千次阅读 2017-03-18 12:44:47
    基本概念 字符 计算机科学和信息科学中字符(英语:character)是一...字符集(英语:character set)指的是指定若干字符组成的一个集合,通常这个集合具有一定的规模和合理性,比如囊括一个国家日常使用的所有可见文

    基本概念

    • 字符
      计算机科学和信息科学中字符(character,或译为字元)是一个信息单位,通常来说就是一个字母、一个汉字、一个数字、一个标点符号。另外,还存在着一些控制字符,通常是不可打印的(不可见的,或称为是功能性的),有特定用途,以及emoji(绘文字)之类特殊的符号。

    • 字符集
      字符集(character set)指的是指定若干字符组成的一个集合,通常这个集合具有一定的规模和合理性,比如囊括一个国家或地区日常使用的文字字符、数字、标点符号和控制字符等。不同的国家和地区由于历史和文化的原因,使用不同的语言文字,因此存在各种不同的字符集。

    • 字符编码
      字符编码(character encoding)是把字符集中的字符映射为指定集合中某一对象(例如数字系统中表示为某个特定的二进制数),以便文本在计算机中存储和在通信网络传递。

    • 乱码
      有字符的编码,就有相应的解码,解码出错就会导致乱码问题。乱码指的是由于使用不同的字符集或字符编码方式,导致显示的部分或全部字符无法被正常的阅读,如常见的文本、网页、邮件乱码。更多,可以参见cenalulus’s tech blog

    字符集和字符编码的关系
    字符集是书写系统字母与符号的集合,而字符编码则是将字符映射为一特定的字节或字节序列,是一种规则。通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码,但Unicode不是,它采用现代的模型),因此基本上可以将两者视为同义词。


    下面是关于常见字符集(字符编码)的介绍。
    字符集很多,我们可以参见一下表格:

    character codings

    图1: 字符编码


    早期:计算机时代之前

    密码学中加密,可以认为是一种广义的字符编码,但它的目的与本文所阐释的有所不同,因此不予展开。早期字符编码的一个最好的例子是在通信领域(无线电)存在的摩尔斯电码。

    摩尔斯电码(又译为摩斯密码,Morse code)是一种时通时断的信号代码,通过不同的排列顺序来表达不同的英文字母、数字和标点符号。它发明于1837年,以美国人塞缪尔·莫尔斯(Samuel F. B. Morse,1791年4月27日 – 1872年4月2日)命名。 摩尔斯电码代码包括五种: 点(dots或dits)、划(dashes或dashs)、点和划之间的停顿、每个字符间短的停顿(在点和划之间)、每个词之间中等的停顿以及句子之间长的停顿。

    有意思的是摩尔斯电码也存在不同的编码方式,有美式编码,德式和现在国际通行的ITU标准。下图是ITU标准的摩尔斯电码:

    morse code

    图2:ITU标准的摩尔斯电码

    补充:
    在电报的发展和传播是中,中国也出现了中文电码,最早是法国驻华人员威基杰(S.A. Viguer)于1873年参照《康熙字典》的部首排列方法,挑选了常用汉字6800多个,编成了第一部汉字电码本《电报新书》。它采用由四个阿拉伯数字代表一个汉字的方法,常称为“四码电报”。


    单字节:ASCII和它的继承者们

    ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)是 ANSI(American National Standard Institute,美国国家标准学会)为英语设计的单字节字符编码方案,主要包括英文字母、阿拉伯数字、英文标点符号和一些控制符号(来自早期的打字机),用于基于文本的数据。ASCII标准的工作由ASA( American Standards Association,美国标准联合会;即现在的ANSI)的下属部门于1960年开始,1963首版,1967经过重新修订,1986年是最新版本。ASCII总共包括128个字符,所以在只需要7位二进制数表示即可。由于计算机中1个字节是8位二进制数,一般表示时最高位设置为0。

    它起始于50年代后期,在1967年定案。最初是美国国家标准,供不同计算机在相互通信时用作共同遵守的西文字符编码标准,它已被ISO(International Organization for Standardization, 国际标准化组织)和IEC(International Electrotechnical Commission,国际电工委员会)定为国际标准,称为ISO/IEC 646标准。参见下图:

    ASCII编码表

    图3:ASCII编码表


    ASCII的编排顺序

    序号(十进制)十六进制范围说明
    0~310x00~0x1F控制字符
    32~1260x20~0xEE可显示(打印)字符,包括空格、标点符号、大小写字母和阿拉伯数字等
    1270xEF删除控制符(DEL)

    说明:
    ASCII的方案参考之前存在的DEC SIXBIT编码表,与老式的打字机有关。

    另外,IBM在1963-1964年推出过自己字符编码方案,主要是根据早期的打孔机BCD编码有关,叫EBCDIC(Extended Binary Coded Decimal Interchange Code,扩展二进制编码转换十进制代码)。


    对于非英语的欧洲国家,存在这不同的字母形式,因此需要扩充编码表,因此出现来EASCII(Extended ASCII,扩展ASCII)。EASCII使用8位二进制,可以表示256个不同字符,但现在很少使用了。

    国际标准化的相关组织在ASCII的基础上进行了扩展,形成了ISO/IEC 8859系列标准(简称ISO 8859),兼容ASCII,包括ISO 8859-1到ISO 8859-16(共15个编码方案/字符集,其中12是空缺的)。其中ISO 8859-1(完整:ISO/IEC 8859-1:1998)也称为Latin-1,扩展部分主要包括西欧国家的字符,第一版出版于1987年。方案利用了第8位编码,相比ASCII多了0x80-0xFF,其中的0xA0-0xFF部分,被ISO/IEC 8859编码所用到。

    ISO/IEC 6429编码标准是有关控制字符的编码标准,不仅定义了0x80-0x9F(称为C1控制字符),还定义了0x0-0x1F,即ASCII中的控制字符(称为C0控制字符)。

    ISO 8859和ISO-8859的区别和联系
    ISO 8859是ISO/IEC 8859标准集合的简称,对应包含了 ISO/IEC 8859-n,除去12之外的1到16,这共15种字符集合,是可见字符的编码。
    ISO-8859,是ISO-8859-n的简称,是IANA(The Internet Assigned Numbers Authority,互联网数字分配机构)根据ISO/IEC 8859-n的标准,加上对应的前面提到的普通的ASCII字符,和ISO/IEC 6429所定义的的控制字符,所制定的标准。
    不过,一般而言可是视为统一,即把包含所有的控制和可见字符。


    补充:一个字节为何是8位(1 byte = 8 bits)?
    总的而言是历史遗留问题,一方面考虑了二进制的特点(计算机原理),另一方面考虑了信息容量。8位表示1字节(1 byte = 8 bits)的标准最早来自与IBM system/360(1950年代到1960年代)。此外还设计早期存储介质的造价成本、商业利益。


    另:计算机中还存在这内存字节边界对齐的问题和CPU读取指令和数据大端和小端的问题,IP地址的位数(IPv4和IPv6),不过目前这方面的争论不多,不像程序员(开发者)之间的大括号和编辑器的争论。


    双字节:东方诸国的万“码”奔腾

    亚洲,尤其是东亚国家和地区(中国、日本等),使用的并不是拼音文字,基本的字符基数相当大,因此单字节根本不够用,于是双字节的表示出现。(汉字这种象形文字,对于计算机编码和存储是相当不友好的,因此才会有早年多种汉字输入法探索与发展)

    双字节的表示,可以表示65536个字符(2的16次方),因此一般情况下是够用的。与欧洲一样,亚洲各国和地区出现了各种互不相同的字符编码,如中国大陆的GB2312、港台的BIG-5、日本的Shift-JIS等。

    字符的编码不仅仅是将字符排一个序,然后就直接挨个编号。考虑易用性和兼容性,字符编码需要一套科学的设计方案。比如ASCII方案中,阿拉伯数字('0'-'9')、大写字母('A'-'Z')、小写字母('a'-'z')是连续排列的,同时在使用进制中,分别以0x300x410x61开始。


    下面细说以下国内的汉字编码方案
    由于使用的目的不同,存在几种编码方案:

    1. 汉字的外码(输入码)
      不同与英文,汉字数目众多,无法用按键一一表示,因此需要对键盘的输入进行额外的编码处理,于是有了外码。外码也叫输入码,是用来将汉字输入到计算机中的一组键盘符号。常用的输入码有拼音编码方案(如全拼输入法的智能ABC、双拼输入法的自然码等)、字形编码方案(如五笔输入法、郑码输入法等)、数字编码(如区位码)以及音形混合编码等。

    2. 汉字的交换码(国标码)
      信息交换码是为了应对人所使用是的符号(文字)和计算机所处理的符号(二进制)侧重不同而产生的。《信息交换用汉字编码字符集》是由中国国家标准总局(国家标准化管理委员会)1980年发布,1981年5月1日开始实施的一套国家标准,标准号是GB 2312-1980,也称为国标码。此编码重要通行于中国大陆。

    3. 汉字的机内码(机器码)
      根据国标码的规定,每一个汉字都有了确定的二进制代码,在计算机的内部汉字代码都用机内码,在磁盘上记录汉字代码也使用机内码。

    4. 汉字的字形码(输出码)
      字形码是汉字的输出码,主要用于显示或打印汉字。每一个汉字按照图形用一个点阵图表示(如16×16或24×24),由此得到的对应汉字的点阵代码(即字型码)。16×16的点阵表示一个汉字需要使用32个字节存储。通常用于显示的汉字字体集合称为显示字库,用于打印的字体集合叫打印字库,两者统称为汉字字库。另外字库可以分为软字库和硬字库:软字库以文件方式存在硬盘中,即通常的字体文件;硬字库则是将字库固化在单独的只读存储芯片中,通过接口设备与计算机连接,如早期计算机使用的汉卡。

    输出汉字时都采用图形方式,无论汉字的笔画多少,每个汉字都可以写在同样大小的方块中。通常用16×16点阵来显示汉字。

    几者的关系
    通常而言机内码的基础是交换码(即国标码,也是本文讨论的字符编码),它是字符在计算机中二进制的表示形式。外码(输入码)是为了键盘等设备输入汉字而设计的编码方案,如全拼、五笔、区位码等。而字形码(输出码)是为了显示(或打印)汉字而使用的编码,等同于字体库。

    中国大陆的字符编码方案

    中国大陆的字符编码方案的主要有GB2312, GBK,GB1300, GB10830。相关文件:

    1. GB 2312-80《信息交换用汉字编码字符集-基本集》(1980年)
    2. GB 13000.1-93《信息技术 通用多八位编码字符集(UCS) 第一部分:体系结构和基本多文种平面》(1993年)
    3. GBK 《汉字内码扩展规范》1.0版(1995年)
    4. GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》(2000年)
    5. GB 18030-2005《信息技术中文编码字符集》(2005年)


    国标码 GB 2312:

    GB 2312基本集共收入字符7445个,其中汉字6763个(其中一级汉字3755个,二级汉字3008个)和非汉字图形字符682个(包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。)。缺陷在于仅收录了常用字,对于人名、古汉语中的罕见字缺少表示。

    整个国标码由一个94×94的方阵构成,分为94个“区”,每区包含94个“位”,其中“区”的序号由01至94,“位”的序号也是从01至94。94个区中位置总数=94×94=8836个,其中7445个汉字和图形字符中的每一个占一个位置后,还剩下1391个空位,这1391个位置空下来保留备用。每个区位上只有一个字符,因此可用所在的区和位来对汉字进行编码,称为区位码,可以用作输入码。


    GB 2312区位布局

    区域范围说明
    01-09区收录除汉字外的682个字符,有164个空位
    10-15区空白区,没有使用
    16-55区收录3755个一级汉字(简体),按拼音排序
    56-87区收录3008个二级汉字(简体),按部首/笔画排序
    88-94区空白区,没有使用


    GB2312编码范围:A1A1-FEFE,其中汉字编码范围:B0A1-F7FE。
    举例来说,“啊”字是GB2312编码中的第一个汉字,它位于16区的01位,所以它的区位码就是1601(十进制)。


    国标码的发展:

    • GB13000
      1993年,国际标准Unicode 1.1版本推出,收录中国大陆、台湾、日本及韩国通用字符集的汉字,总共有20,902个。
      中国大陆订定了等同于Unicode 1.1版本的“GB 13000.1-93”,简称为GB13000。它兼容GB2312,同时收录更多汉字,包括新简化字、生僻字以及台湾和香港使用的繁体字,日语朝鲜语中的汉字等,共20,902个字符。

    • GBK
      1995年12月颁布的《汉字编码扩展规范》(GBK,国标扩),它只是一个“技术规范指导性文件”。它共包含23940个码位,收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。

    • GB18030
      国家标准GB18030-2005《信息技术中文编码字符集》是我国继GB2312-1980和GB13000.1-1993之后最重要的汉字编码标准,是我国计算机系统必须遵循的基础性标准之一。
      GB18030有两个版本:GB18030-2000(《信息交换用汉字编码字符集基本集的补充》)和GB18030-2005。GB18030-2000是GBK的取代版本,它的主要特点是在GBK基础上增加了CJK统一汉字扩充A的汉字,2000版本仅规定了常用非汉字符号和27533个汉字(包括部首、部件等)的编码。GB18030-2005是以汉字为主并包含多种我国少数民族文字(如藏、蒙古、傣、彝、朝鲜、维吾尔文等)的超大型中文编码字符集强制性标准,是在GB18030-2000基础上增加了CJK统一汉字扩充B的汉字,其中收入汉字70000余个,。


    其他相关

    半角和全角
    全角符号的问题主要来自于双字节编码的历史遗留。印刷上纵横比为1:1的字符(块)称为全角,而早期为了在纯文本界面(终端)中将英文等单字节字符和中日韩的方块字符对齐,使得西文的字母、数字、标点等占用一个汉字的宽度。这与编程中常见的等宽字体类似。目前而言,这些等宽的的西文字母等几乎不再使用,但在Unicode字符集中进行了吸纳。

    全角和半角的切换:
    部分输入法如搜狗拼音中有设置按钮,也可以使用快捷键【Shift+空格】。


    东亚的其他国家

    台湾、香港、澳门地区

    台湾、香港、澳门通常采用的字符编码是Big5(大五码)。大五码是由资策会于1984年策划制定,拥有13053个中文字(繁体中文)、408个字符以及33个控制字符的字集,是早期中文电脑的业界标准和中文网络社区常用的电子汉字字集标准。

    日本

    日本常见的字符编码是Shift JIS(Shift Japanese Industrial Standards,也简称SJIS, MIME中称为Shift_JIS)。它是日本计算机系统中常用的编码表,能容纳全形及半形拉丁字母、平假名、片假名、符号及日本汉字。

    韩国

    韩国以前使用KS编码,如KS X 1001是用于书写的谚文和汉字的字符编码规格,它包含谚文2350个、汉字4888个、英文字母、数字和假名合计8226字。

    CJKV

    CJKV是Chinese、Japanese、Korean、Vietnamese这4种语言的合称,历史上,它们都属于东亚的汉字文化圈和儒家文化圈,日常使用涉及汉字或类似汉字的自造字(hànzì in Chinese, kanji, kana in Japanese, hanja, hangul in Korean, and Hán tự, chữ Nôm in Vietnamese)。

    EUC

    EUC(Extended Unix Code,扩展Unix系统代码),是一个使用8位编码来表示字符的方法。EUC最初是针对Unix系统,由一些Unix公司所开发,于1991年标准化。EUC基于ISO/IEC 2022的7位编码标准,因此单字节的编码空间为94,双字节的编码空间(区位码)为94x94。把每个区位加上0xA0来表示,以便符合ISO 2022。它主要用于表示及储存汉语文字、日语文字及朝鲜文字。包括EUC-CN,EUC-TW,EUC-JP,EUC-KR,EUC-KP。


    多字节:Unicode分久必合的大一统梦想

    互联网的出现,信息技术的高速发展,不同地域的资讯交流愈加频繁。不同国家和地区的计算机在交换数据的过程由于字符编码格式的不同,造成数据解析乱码,因此需要有一个统一的编码方式以方便大家使用。Unicode(统一码,或译为万国码、国际码、单一码)应运而生。它对世界上大部分的文字系统进行了整理、编码,使得计算机系统可以用更为简单有效的方式来呈现和处理文字.

    unicode logo

    图4:Unicode的徽标

    历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和统一码联盟(The Unicode Consortium,非正式译名)。

    前者开发了ISO/IEC 10646项目(简称为ISO 10646),此标准所定义的字符集,称作为通用字符集(Universal Character Set,UCS)。后者成员主要是计算机软硬件厂商,它开发了Unicode项目。他们在项目启动不久便发现对方的存在,并有着相同的目的,于是两个组织便共同合作开发适用于各国语言的通用码,但依旧各自发表The Unicode Standard和ISO 10646字符集。虽然实质上两者确实为两个不同的标准,但实际上两者的字集编码相同,可以合称为Unicode/UCS,只是The Unicode Standard包含了更详尽的实现信息、涵盖了更多细节的主题。

    2016年6月21日公布了Unicode 9.0.0版本,新增支持7500种新字符,总数达到128,172种,本次包括72种新emoji表情和、19种新电视符号和一些小众语言等。


    Unicode的实现方式

    Unicode只是一个字符集,规定了字符的二进制代码,而具体的实现方式并没有规定,因此出现了UTF-8、UTF-16和UTF-32等编码实现方式,其中UTF是指Unicode Transformation Format(统一码转换格式)。

    • UTF-8
      UTF-8是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,优点是兼容ASCII,但表示汉字通常需要3个字节。UTF-8的编码规则很简单,只有二条:

      1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的Unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的
      2. 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的Unicode码
    • UTF-16
      UTF-16是Unicode字符编码五层次模型的第三层:字符编码表(Character Encoding Form,也称为 “storage format”)的一种实现方式。即把Unicode字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位,需要1个或者2个16位长的码元来表示,因此这是一个变长表示。另:根据16位中两个字节的排序顺序可分为UTF-16BE和UTF-16LE。

    • UTF-32
      UTF-32 (或 UCS-4)是一种将Unicode字符编码的协议,对每一个Unicode码位使用恰好32位,只在0到10FFFF的字码空间(百万个码位)。UTF 只有此一种定长编码。
      UTF-32 原本是一个 UCS-4 的子集,但就现状而言,除了 UTF-32 标准包含额外的 Unicode 意义,UCS-4 和 UTF-32 大体是相同的。


    其他(操作系统、编程语言)

    代码页(Code Page)是操作系统中字符编码的另一种说法。它起源于IBM,并被微软等厂商采用。不同的厂商通常会有自己定义的代码页,不同的代码页对应不同的字符集。

    Windows系统的字符编码

    Windows中的Code Page,按照应用领域来划分,可以分为两类:Windows Code Page和 OEM Code Page。其中最广泛的是给予ANSI草案的Windows 1252,用于英语和西欧语言字符。

    • Windows Code Page(也叫ANSI Code Page),用于Windows系统中,本地编码是非Unicode的,图形用户界面(GUI)程序。

    • OEM Code Page主要Windows系统中的命令行界面,常见的cp 437(原始的IBM PC代码页,实现了扩展ASCII字符集), cp936(对应简体中文的GBK字符集), cp 65001(UTF-8实现的Unicode字符集)。

    Windows记事本中的BOM问题
    BOM(Byte Order Mark,字节顺序标记),出现在文本文件头部,Windows系统中用于标识文件采用的编码格式、文件/字符流的大小端(字节序),通常不可见。但对于一些编程语言而言,会误认为特殊字符而试图做出解析从而导致意想不到的后果。因此,编写程序不要使用Windows自带的记事本。


    其他操作系统

    Linux系统、macOS(OS X, Mac OS X)的本地文本编码实现一般是UTF-8。其他Unix系统以及衍生BSD系统不是很清楚,可以自己查询。

    编程语言的支持

    略。

    传输协议中设计的编码

    • Base 64
    • MIME
    • Quoted-Printable编码

    参考

    参考资料:

    • 主要是维基百科、百度百科、知乎以及CNBLOG、简书等博客。

    延伸阅读:

    插图说明:

    • 图1(多种字符编码的表格)和图3(ASCII)来自维基百科相关英文词条的截图
    • 图2( ITU标准的莫尔斯电码)来自维基百科英文词条(Morse Code)
    • 图4(Unicode logo)来自维基百科英文词条(Unicode)

    文件说明:

    • 国内关于中文编码字符集的几个相关文件,在国家相关政府部分网站(标准化委员会、工信部等)并没有找到(可能是不得其法),但在网上搜一搜还是可以得到一些。
    展开全文
  • Python 中的字符串与字符编码

    千次阅读 2018-03-06 11:20:11
    阅读目录: 一、前言 二、相关概念 1. 字符与字节 2. 编码与解码 ...三、Python中的默认编码 1. Python源代码文件的执行过程 ...Python2中的字符串进行字符编码转换过程是: Python3中定义的字符串默认就是unic...

    阅读目录:

    一、前言

    Python中的字符编码是个老生常谈的话题,同行们都写过很多这方面的文章。有的人云亦云,也有的写得很深入。近日看到某知名培训机构的教学视频中再次谈及此问题,讲解的还是不尽人意,所以才想写这篇文字。一方面,梳理一下相关知识,另一方面,希望给其他人些许帮助。

    Python2的 默认编码 是ASCII,不能识别中文字符,需要显式指定字符编码;Python3的 默认编码 为Unicode,可以识别中文字符。

    相信大家在很多文章中都看到过类似上面这样“对Python中中文处理”的解释,也相信大家在最初看到这样的解释的时候确实觉得明白了。可是时间久了之后,再重复遇到相关问题就会觉得貌似理解的又不是那么清楚了。如果我们了解上面说的默认编码的作用是什么,我们就会更清晰的明白那句话的含义。

    二、相关概念

    1. 字符与字节

    一个字符不等价于一个字节,字符是人类能够识别的符号,而这些符号要保存到计算的存储中就需要用计算机能够识别的字节来表示。一个字符往往有多种表示方法,不同的表示方法会使用不同的字节数。这里所说的不同的表示方法就是指字符编码,比如字母A-Z都可以用ASCII码表示(占用一个字节),也可以用UNICODE表示(占两个字节),还可以用UTF-8表示(占用一个字节)。字符编码的作用就是将人类可识别的字符转换为机器可识别的字节码,以及反向过程。

    UNICDOE才是真正的字符串,而用ASCII、UTF-8、GBK等字符编码表示的是字节串。关于这点,我们可以在Python的官方文档中经常可以看到这样的描述”Unicode string” , ” translating a Unicode string into a sequence of bytes”

    我们写代码是写在文件中的,而字符是以字节形式保存在文件中的,因此当我们在文件中定义个字符串时被当做字节串也是可以理解的。但是,我们需要的是字符串,而不是字节串。一个优秀的编程语言,应该严格区分两者的关系并提供巧妙的完美的支持。JAVA语言就很好,以至于了解Python和PHP之前我从来没有考虑过这些不应该由程序员来处理的问题。遗憾的是,很多编程语言试图混淆“字符串”和“字节串”,他们把字节串当做字符串来使用,PHP和Python2都属于这种编程语言。最能说明这个问题的操作就是取一个包含中文字符的字符串的长度:

    • 对字符串取长度,结果应该是所有字符串的个数,无论中文还是英文
    • 对字符串对应的字节串取长度,就跟编码(encode)过程使用的字符编码有关了(比如:UTF-8编码,一个中文字符需要用3个字节来表示;GBK编码,一个中文字符需要2个字节来表示)

    注意:Windows的cmd终端字符编码默认为GBK,因此在cmd输入的中文字符需要用两个字节表示

    >>> # Python2
    >>> a = 'Hello,中国'  # 字节串,长度为字节个数 = len('Hello,')+len('中国') = 6+2*2 = 10
    >>> b = u'Hello,中国'  # 字符串,长度为字符个数 = len('Hello,')+len('中国') = 6+2 = 8
    >>> c = unicode(a, 'gbk')  # 其实b的定义方式是c定义方式的简写,都是将一个GBK编码的字节串解码(decode)为一个Uniocde字符串
    >>>
    >>> print(type(a), len(a))
    (<type 'str'>, 10)
    >>> print(type(b), len(b))
    (<type 'unicode'>, 8)
    >>> print(type(c), len(c))
    (<type 'unicode'>, 8)

    Python3中对字符串的支持做了很大的改动,具体内容会在下面介绍。

    2. 编码与解码

    先做下科普:UNICODE字符编码,也是一张字符与数字的映射,但是这里的数字被称为代码点(code point), 实际上就是十六进制的数字。

    Python官方文档中对Unicode字符串、字节串与编码之间的关系有这样一段描述:

    Unicode字符串是一个代码点(code point)序列,代码点取值范围为0到0x10FFFF(对应的十进制为1114111)。这个代码点序列在存储(包括内存和物理磁盘)中需要被表示为一组字节(0到255之间的值),而将Unicode字符串转换为字节序列的规则称为编码。

    这里说的编码不是指字符编码,而是指编码的过程以及这个过程中所使用到的Unicode字符的代码点与字节的映射规则。这个映射不必是简单的一对一映射,因此编码过程也不必处理每个可能的Unicode字符,例如:

    将Unicode字符串转换为ASCII编码的规则很简单–对于每个代码点:

    • 如果代码点数值<128,则每个字节与代码点的值相同
    • 如果代码点数值>=128,则Unicode字符串无法在此编码中进行表示(这种情况下,Python会引发一个UnicodeEncodeError异常)

    将Unicode字符串转换为UTF-8编码使用以下规则:

    U+ 0000 ~ U+ 007F: 0XXXXXXX 
    U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX 
    U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX 
    U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
    • 如果代码点数值<128,则由相应的字节值表示(与Unicode转ASCII字节一样)
    • 如果代码点数值>=128,则将其转换为一个2个字节,3个字节或4个字节的序列,该序列中的每个字节都在128到255之间。

    举个例子:
    根据上表中的编码规则,之前的「知」字的码位 U+77E5 属于第三行的范围:

           7    7    E    5
        0111 0111 1110 0101    二进制的 77E5
    --------------------------
        0111   011111   100101 二进制的 77E5
    1110XXXX 10XXXXXX 10XXXXXX 模版(上表第三行)
    11100111 10011111 10100101 代入模版
       E   7    9   F    A   5

    这就是将 U+77E5 按照 UTF-8 编码为字节序列 E79FA5 的过程。
    简单总结:

    • 编码(encode):将Unicode字符串(中的代码点)转换特定字符编码对应的字节串的过程和规则
    • 解码(decode):将特定字符编码的字节串转换为对应的Unicode字符串(中的代码点)的过程和规则

    可见,无论是编码还是解码,都需要一个重要因素,就是特定的字符编码。因为一个字符用不同的字符编码进行编码后的字节值以及字节个数大部分情况下是不同的,反之亦然,另附知乎链接:Unicode 和 UTF-8 有何区别

    三、Python中的默认编码

    1. Python源代码文件的执行过程

    我们都知道,磁盘上的文件都是以二进制格式存放的,其中文本文件都是以某种特定编码的字节形式存放的。对于程序源代码文件的字符编码是由编辑器指定的,比如我们使用Pycharm来编写Python程序时会指定工程编码和文件编码为UTF-8,那么Python代码被保存到磁盘时就会被转换为UTF-8编码对应的字节(encode过程)后写入磁盘。当执行Python代码文件中的代码时,Python解释器在读取Python代码文件中的字节串之后,需要将其转换为UNICODE字符串(decode过程)之后才执行后续操作。

    上面已经解释过,这个转换过程(decode,解码)需要我们指定文件中保存的字节使用的字符编码是什么,才能知道这些字节在UNICODE这张万国码和统一码中找到其对应的代码点是什么。这里指定字符编码的方式大家都很熟悉,如下所示:

    # -*- coding:utf-8 -*-

    2. 默认编码

    那么,如果我们没有在代码文件开始的部分指定字符编码,Python解释器就会使用哪种字符编码把从代码文件中读取到的字节转换为UNICODE代码点呢?就像我们配置某些软件时,有很多默认选项一样,需要在Python解释器内部设置默认的字符编码来解决这个问题,这就是文章开头所说的“默认编码”。因此大家所说的Python中文字符问题就可以总结为一句话:当无法通过默认的字符编码对字节进行转换时,就会出现解码错误(UnicodeEncodeError)

    Python2和Python3的解释器使用的默认编码是不一样的,我们可以通过sys.getdefaultencoding()来获取默认编码:

    >>> # Python2
    >>> import sys
    >>> sys.getdefaultencoding()
    'ascii'
    
    >>> # Python3
    >>> import sys
    >>> sys.getdefaultencoding()
    'utf-8'

    因此,对于Python2来讲,Python解释器在读取到中文字符的字节码尝试解码操作时,会先查看当前代码文件头部是否有指明当前代码文件中保存的字节码对应的字符编码是什么。如果没有指定则使用默认字符编码”ASCII”进行解码导致解码失败,导致如下错误:

    SyntaxError: Non-ASCII character '\xc4' in file xxx.py on line 11, but no encoding declared;
    see http://python.org/dev/peps/pep-0263/ for details

    对于Python3来讲,执行过程是一样的,只是Python3的解释器以”UTF-8”作为默认编码,但是这并不表示可以完全兼容中文问题。比如我们在Windows上进行开发时,Python工程及代码文件都使用的是默认的GBK编码,也就是说Python代码文件是被转换成GBK格式的字节码保存到磁盘中的。Python3的解释器执行该代码文件时,试图用UTF-8进行解码操作时,同样会解码失败,导致如下错误:

    SyntaxError: Non-UTF-8 code starting with '\xc4' in file xxx.py on line 11, but no encoding declared;
    see http://python.org/dev/peps/pep-0263/ for details

    3. 最佳实践

    • 创建一个工程之后先确认该工程的字符编码是否已经设置为UTF-8
    • 为了兼容Python2和Python3,在代码头部声明字符编码:-*- coding:utf-8 -*-

    四、Python2与Python3中对字符串的支持

    其实Python3中对字符串支持的改进,不仅仅是更改了默认编码,而是重新进行了字符串的实现,而且它已经实现了对UNICODE的内置支持,从这方面来讲Python已经和JAVA一样优秀。下面我们来看下Python2与Python3中对字符串的支持有什么区别:

    Python2

    Python2中对字符串的支持由以下三个类提供

    class basestring(object)
    class str(basestring)
    class unicode(basestring)

    执行help(str)和help(bytes)会发现结果都是str类的定义,这也说明Python2中str就是字节串,而后来的unicode对象对应才是真正的字符串。

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    a = '你好'
    b = u'你好'
    
    print(type(a), len(a))
    print(type(b), len(b))
    
    输出结果:
    
    (<type 'str'>, 6)
    (<type 'unicode'>, 2)

    Python3

    Python3中对字符串的支持进行了实现类层次的上简化,去掉了unicode类,添加了一个bytes类。从表面上来看,可以认为Python3中的str和unicode合二为一了。

    class bytes(object)
    class str(object)

    实际上,Python3中已经意识到之前的错误,开始明确的区分字符串与字节。因此Python3中的str已经是真正的字符串,而字节是用单独的bytes类来表示。也就是说,Python3默认定义的就是字符串,实现了对UNICODE的内置支持,减轻了程序员对字符串处理的负担。

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    a = '你好'
    b = u'你好'
    c = '你好'.encode('gbk')
    
    print(type(a), len(a))
    print(type(b), len(b))
    print(type(c), len(c))
    
    输出结果:
    
    <class 'str'> 2
    <class 'str'> 2
    <class 'bytes'> 4

    五、字符编码转换


    上面提到,UNICODE字符串可以与任意字符编码的字节进行相互转换,如图:

    那么大家很容易想到一个问题,就是不同的字符编码的字节可以通过Unicode相互转换吗?答案是肯定的。

    Python2中的字符串进行字符编码转换过程是:

    字节串–>decode(‘原来的字符编码’)–>Unicode字符串–>encode(‘新的字符编码’)–>字节串

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    
    utf_8_a = '我爱中国'
    gbk_a = utf_8_a.decode('utf-8').encode('gbk')
    print(gbk_a.decode('gbk'))
    
    输出结果:
    
    我爱中国
    Python3中定义的字符串默认就是unicode,因此不需要先解码,可以直接编码成新的字符编码:

    字符串–>encode(‘新的字符编码’)–>字节串

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    
    utf_8_a = '我爱中国'
    gbk_a = utf_8_a.encode('gbk')
    print(gbk_a.decode('gbk'))
    
    输出结果:
    
    我爱中国

    最后需要说明的是,Unicode不是有道词典,也不是google翻译器,它并不能把一个中文翻译成一个英文。正确的字符编码的转换过程只是把同一个字符的字节表现形式改变了,而字符本身的符号是不应该发生变化的,因此并不是所有的字符编码之间的转换都是有意义的。怎么理解这句话呢?比如GBK编码的“中国”转成UTF-8字符编码后,仅仅是由4个字节变成了6个字节来表示,但其字符表现形式还应该是“中国”,而不应该变成“你好”或者“China”。

    原博文:请猛击整理

    展开全文
  • C语言字符编码

    千次阅读 2019-04-11 15:55:02
    以C语言程序在windows控制台中的输入输出为例,阐述程序在执行环境中字符编码的过程: 1.假设用户键入拼音nihao,那么输入法根据用户输入的拼音,给出字符候选列表。 2.用户阅读完候选列表后从中选择词语“你好” 3....
  • Unicode字符编码标准

    千次阅读 2018-08-16 16:46:05
    Unicode ...如何解释这些整数是由字符集(character set)、编码(encoding)决定的。  文 本主要是由字符(character)组成。在格式文本(fancy text, or rich text)中包括显示属性,如颜色、斜...
  • oracle 修改字符编码

    千次阅读 2019-04-03 12:02:56
    1. 问题描述 在安装oracle时设置的编码是ZHS16GBK , ...这时需要修改oracle的字符编码 2. 解决问题 第一步: 查看oracle的字符编码 SQL> select * from v$nls_parameters; PARAMETER VALUE --------...
  • JAVA核心知识点--字符编码

    万次阅读 2017-06-18 16:47:13
    为什么要编码? 常用编码格式 ASCII码 ISO-8859-1 GB2312 GBK UTF-16 UTF-8 在I/O操作中存在的编码 在内存操作中的编码 在Java Web中涉及的编解码 URL的编解码 HTTP Header的编解码 POST表单的编解码 ...
  • Java字符编码详解

    千次阅读 2017-08-18 15:15:13
    java采用unicode(《java核心技术卷一》里面有详细说明),2个字节(16位)来表示一个字符。而Unicode编码包括汉字,所以也可以给char赋值汉字。 那么我们在程序中的char =‘\123’和char = ‘\u0023’甚至’\”’...
  • 彻底搞懂Python的字符编码

    万次阅读 多人点赞 2016-12-26 21:11:29
    前言:中文编码问题一直是程序员头疼的问题,而Python2中的字符编码足矣令新手抓狂。本文将尽量用通俗的语言带大家彻底的了解字符编码以及Python2和3中的各种编码问题。一、什么是字符编码。要彻底解决字符编码的...
  • 本篇文章将对常见的字符编码进行介绍,并重点总结gb2312、utf-8和数字证书中常使用的base64编码。
  • 文章目录数据校验字符编码本质字符编码类别介绍多媒体格式 本篇主要是对计算机常识中的信息编码方面的知识进行学习的总结的第二部分 上一篇传送门 数据校验 后面再补齐 字符编码 本质 计算机存储介质中实际存储的都...
  • 解决python的中文字符编码问题

    万次阅读 2018-07-15 17:00:23
    摘要:最近在做自然语言处理相关的项目,发现中文编码的问题实在需要好好学习下,我用python为例,简单介绍下python编程时如何处理好中文编码的问题。
  • 计算机系统之字符编码

    千次阅读 2018-05-17 15:56:44
    一、关于字符编码字符编码(英语:Character encoding)也称字集码,是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。...
  • char类型与字符编码

    千次阅读 2018-06-30 18:38:36
    本文要点:java的内码为UTF-16;...一、字符编码 字符编码有一个发展过程。最初的ASCII码,使用1个byte,表示出英文中所有需要的字符(缺点:非英文的字符都无法表示)非英语系的国家,分别做出各种字符编码,满...
  • 本文介绍了MySQL数据库中默认字符编码的设置方法,如何设置与修改mysql默认编码,my.ini设置字符编码的教程,需要的朋友参考下。 本节重点: mysql基础配置之mysql的默认字符编码的设置(my.ini设置字符编码) ...
  • 字符编码笔记汇总:ASCII、GBXXXX、Unicode、UTF-8等 版权声明:本文系个人经多处资料学习、吸收、整理而得,如需转载,请注明出处:作者名+链接。 内容说明:本系列内容大致包括基本概念(字符集、字符编码)、...
  • 因为golang中的字符编码格式是utf-8格式的,如果是其他类型的编码,例如gbk,那么直接转码时出现乱码也就理所当然了。 GBK编码格式 为了更好地说明GBK的编码方式,首先在这里先说明一下ASCII码,GB231...
  • 字符编码的常用种类介绍

    万次阅读 2018-11-06 14:31:10
    字符编码的常用种类介绍 第一种:ASCII码 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今...
  • C++中字符编码的转换(Unicode、UTF-8、ANSI)

    万次阅读 多人点赞 2018-09-25 14:11:09
    C++的项目,字符编码是一个大坑,不同平台之间的编码往往不一样,如果不同编码格式用一套字符读取格式读取就会出现乱码。因此,一般都是转化成UTF-8这种平台通用,且支持性很好的编码格式。 Unicode、UTF-8的概念不...
  • Python3 判断系统、文件、字符编码

    千次阅读 2019-06-24 10:24:52
    Python3 判断系统、文件、字符编码 一、判断系统编码 import locale print('系统编码为:', locale.getpreferredencoding()) 说明:CP936 其实就是 GBK,IBM 在发明 Code Page 的时候将GBK放在第 936 页,所以叫...
  • 火狐浏览器错误提示:纯文本文件的字符编码未声明。如果该文件包含 US-ASCII 范围之外的字符,该文件将在某些浏览器配置中呈现为乱码。该文件的字符编码需要在传输协议层声明,或者在文件中加入一个 BOM(字节顺序...
  • vim 字符编码设置 及 修改编码

    万次阅读 2017-07-13 15:35:18
    和所有的流行文本编辑器一样,Vim 可以很好的编辑各种字符编码的文件,这当然包括UCS-2、UTF-8 等流行的 Unicode 编码方式。然而不幸的是,和很多来自 Linux 世界的软件一样,这需要你自己动手设置。
  • SpringBoot字符编码处理

    千次阅读 2019-03-03 12:28:54
    SpringBoot字符编码处理 Springboot处理字符编码可以通过Filter过滤器进行拦截实现,使用的是SpringWeb提供的CharacterEncodingFilter(字符编码过滤器类),将其FilterRegistrationBean注册,设置拦截路径就可以了。...
  • Visual studio 与字符编码浅析

    千次阅读 2018-03-19 15:08:57
    关于字符编码1. 西方文字的编码。1.1 ASCII 码,ASCII是通用的英文字符的编码,对于英文字符,他采用7位2进制数来表示一个英文字符,我们知道1个byte包含8个bit,对于ASCII码来说,最高bit为0.1.2 ISO 8859,西方...
  • java自动识别文本文件字符编码

    千次阅读 2018-04-19 11:46:03
    编程开发中,免不了要读取磁盘中的文本文件,目前文本文件最常用的是使用“utf-8”及“gbk”字符编码,如果使用了错误的字符编码格式,就会发生乱码的问题,因而在读取前,需要约定好要读取的文本文件内容与工程代码...
  • 浅谈C/C++编程中的字符编码转换

    万次阅读 2017-03-02 15:11:48
    导致这种现象的根源就在于字符编码不匹配导致,本文将探索隐藏在编程过程中鲜为人知的字符集转换问题,如果你彻底理解了以下几个字符集的概念,以及编程过程中哪些因素会影响这些字符集,将有助于你从根
  • Python常见字符编码及其之间的转换

    万次阅读 2018-03-29 17:28:36
    参考:Python常见字符编码 + Python常见字符编码间的转换一、Python常见字符编码字符编码的常用种类介绍第一种:ASCII码ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是...
  • 字符编码与霍夫曼编码

    千次阅读 2018-09-04 11:22:56
    哈夫曼编码是哈夫曼树的一个应用。哈夫曼编码应用广泛,如JPEG中就应用了哈夫曼编码。 首先介绍什么是哈夫曼树。哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶...
  • 计算机字符编码详解——从理论到实践

    万次阅读 多人点赞 2016-09-24 17:52:39
    最近在看《深入理解计算机系统》,读到“字符编码”时不禁想起了初学时那段痛苦的岁月,同时又没找到一篇将理论和实践结合在一起的文章,为此决定自己写一份。希望能把我走过的弯路总结出来,能帮助一些还在路上的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,146,667
精华内容 458,666
关键字:

字符编码