常见编码及乱码的处理

常见编码及乱码的处理

本文解决的问题

  • 编码是什么
  • 字符是什么
  • 字符集是什么
  • 编码用在哪
  • 我们常出现的错误(乱码)
  • 怎么正确的使用编码

前言

我们日常接触到的文件分ASCII是“美国信息交换标准编码”的英文字头缩写,可称之为“美标”。美标规定了用从0到127的128个数字来代表信息的规范编码,其中包括33个控制码,一个空格码,和94个形象码。形象码中包括了英文大小写字母,阿拉伯数字,标点符号等。我们平时阅读的英文电脑文本,就是以形象码的方式传递和存储的。美标是国际上大部分大小电脑的通用编码。

然而电脑中的一个字符大都是用一个八位数的二进制数字表示。这样就有256个不同的数值,可以用来表示256个不同的字符。由于美标只规定了128个编码,剩下的另外128个数码没有规范,各家用法不一。另外美标中的33个控制码,各厂家用法也不尽一致。这样我们在不同电脑间交换文件的时候,就有必要区分两类不同的文件。第一类文件中每一个字都是美标形象码或空格码。这类文件称为“美标文本文件”(ASCII Text Files),或略为“文本文件”,通常可在不同电脑系统间直接交换。第二类文件,也就是含有控制码或非美标码的文件,通常不能在不同电脑系统间直接交换。这类文件有一个通称,叫“二进制文件”(Binary Files)。

20191112140923.png

本文讨论的内容只在可读文本即ASCII系列

一、字符集与编码

1、字符集简介

**字符(Character)**是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
**字符集(Character set)**是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。

**字符编码(Character encoding)**是把字符集中的某个字符编码为指定字符集中字符,以便文本在计算机中存储和通过通信网络的传递。常见的例子包括将拉丁字母表编码成ASCII,ASCII将字母、数字和其它符号编号,并用7比特的二进制来表示。

**字符序 (collation)**是指同一个字符集内字符之间的比较规则。只有确定字符序后,才能在一个字符集上定义什么是等价的字符,以及字符之间的大小关系。一个字符可以包含多种字符序。MySQL字符序命名规则是:以字符序对应的字符集名称开头,以国家名居中(或以general居中),以ci、cs、或bin结尾。以ci结尾的字符序表示大小写不敏感,以cs结尾的字符序表示大小写敏感,以bin结尾的字符序表示按二进制编码值比较。

2、ASCII编码

ASCII既是编码字符集,又是字符编码,ASCII直接将字符在编码字符集中的序号作为字符在计算机中存储从数值。
例如:在ASCII中A字符在表中排第65位,序号是65,而编码后A的数值是0100 0001,即十进制的65的二进制转换结果。

3、Latin1字符集

Latin1字符集在ASCII字符集基础上进行了扩展,仍然使用一个字节表示字符,但启用了高位,扩展了字符集的表示范围。

4、UTF-8编码

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,又称万国码。由Ken Thompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码Unicode字符。
UTF-8是一种变长字节编码方式。对于某一个字符的UTF-8编码,如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的位数,其余各字节均以10开头。UTF-8最多可用到6个字节。 如表:
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
因此UTF-8中可以用来表示字符编码的实际位数最多有31位,即上表中x所表示的位。除去控制位(每字节开头的10等),x表示的位与UNICODE编码是一一对应的,位高低顺序也相同。
实际将UNICODE转换为UTF-8编码时应先去除高位0,然后根据所剩编码的位数决定所需最小的UTF-8编码位数。 因此基本ASCII字符集中的字符(UNICODE兼容ASCII)只需要一个字节的UTF-8编码(7个二进制位)便可以表示。

5、字符集兼容性

对于中文字符来说,UTF-8、GBK、GB2312、BIG5四种编码之间是互不兼容的,直接相互转换会导致乱码;当UTF-8、GBK、GB2312、BIG5四种编码转换为ASCII编码和Latin1编码格式时,每个中文字符会被转换为0x3F,即中文字符’?’。
GB2312支持简体中文,BIG5支持繁体中文,GBK支持简体中文及繁体中文,UTF-8支持几乎所有字符。
GBK是国家标准GB2312基础上扩容后兼容GB2312的标准。GB2312是GBK的子集,GBK是GB18030的子集。

GB编码标准中,比较常用的是GB2312和GBK两种,GB2312是GBK的一个子集,GB2312编码范围是 0xA1A1 - 0xFEFE ,如果纯粹的 GB2312编码,处理起来是十分简单的,但处理GBK字符集时有些小的提示,先说说GBK编码的标准吧:

GBK 采用双字节表示,总体编码范围为 8140-FEFE,首字节在 81-FE 之间,尾字节在 40-FE 之间,剔除 xx7F 一条线。总计 23940 个码位,共收入 21886 个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号 883 个。

6、文件编码从哪看?

以notepad++ 为例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6en5xDiL-1574241894791)(https://i.loli.net/2019/11/12/kxESrPKRHlXATBY.png)]

我们打开一个文本文件

20191112140214.png

一般默认会指定一个正确的编码去读

20191112140331.png

如果我们以二进制的方式打开文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fksApxjd-1574241894792)(https://i.loli.net/2019/11/12/aTyPWf2QOJcnrAx.png)]

ASCII码表

二进制十进制十六进制缩写名称/意义
0000 000000NUL空字符(Null)
0000 000111SOH标题开始
0000 001022STX本文开始
0000 001133ETX本文结束
0000 010044EOT传输结束
0000 010155ENQ请求
0000 011066ACK确认回应
0000 011177BEL响铃
0000 100088BS退格
0000 100199HT水平定位符号
0000 1010100ALF换行键
0000 1011110BVT垂直定位符号
0000 1100120CFF换页键
0000 1101130DCR归位键
0000 1110140ESO取消变换(Shift out)
0000 1111150FSI启用变换(Shift in)
0001 00001610DLE跳出数据通讯
0001 00011711DC1设备控制一(XON 启用软件速度控制)
0001 00101812DC2设备控制二
0001 00111913DC3设备控制三(XOFF 停用软件速度控制)
0001 01002014DC4设备控制四
0001 01012115NAK确认失败回应
0001 01102216SYN同步用暂停
0001 01112317ETB区块传输结束
0001 10002418CAN取消
0001 10012519EM连接介质中断
0001 1010261ASUB替换
0001 1011271BESC跳出
0001 1100281CFS文件分割符
0001 1101291DGS组群分隔符
0001 1110301ERS记录分隔符
0001 1111311FUS单元分隔符
0111 11111277FDEL删除
二进制十进制十六进制图形
0010 00003220(空格)(␠)
0010 00013321!
0010 00103422"
0010 00113523#
0010 01003624$
0010 01013725%
0010 01103826&
0010 01113927
0010 10004028(
0010 10014129)
0010 1010422A*
0010 1011432B+
0010 1100442C,
0010 1101452D-
0010 1110462E.
0010 1111472F/
0011 000048300
0011 000149311
0011 001050322
0011 001151333
0011 010052344
0011 010153355
0011 011054366
0011 011155377
0011 100056388
0011 100157399
0011 1010583A:
0011 1011593B;
0011 1100603C<
0011 1101613D=
0011 1110623E>
0011 1111633F?
0100 00006440@
0100 00016541A
0100 00106642B
0100 00116743C
0100 01006844D
0100 01016945E
0100 01107046F
0100 01117147G
0100 10007248H
0100 10017349I
0100 1010744AJ
0100 1011754BK
0100 1100764CL
0100 1101774DM
0100 1110784EN
0100 1111794FO
0101 00008050P
0101 00018151Q
0101 00108252R
0101 00118353S
0101 01008454T
0101 01018555U
0101 01108656V
0101 01118757W
0101 10008858X
0101 10018959Y
0101 1010905AZ
0101 1011915B[
0101 1100925C\
0101 1101935D]
0101 1110945E^
0101 1111955F_
0110 00009660`
0110 00019761a
0110 00109862b
0110 00119963c
0110 010010064d
0110 010110165e
0110 011010266f
0110 011110367g
0110 100010468h
0110 100110569i
0110 10101066Aj
0110 10111076Bk
0110 11001086Cl
0110 11011096Dm
0110 11101106En
0110 11111116Fo
0111 000011270p
0111 000111371q
0111 001011472r
0111 001111573s
0111 010011674t
0111 010111775u
0111 011011876v
0111 011111977w
0111 100012078x
0111 100112179y
0111 10101227Az
0111 10111237B{
0111 11001247C|
0111 11011257D}
0111 11101267E~

二、乱码

1、文件乱码

当使用编辑器打开文本文件时(如:txt、jsp、HTML、java、js、css……)发生乱码,可能是由于打开时使用了错误的字符集

如下

20191113103700.png

解决方案

切换正确的字符集即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yTMZYue-1574241894793)(https://i.loli.net/2019/11/13/EPZtvHOQzVI7Yum.png)]

也存在一种可能,文本写入时即发生乱码写入了错误的字符。此种方式不可逆,基本无法还原。所以,写入文本时一定确保写入的内容编码正确

2、HTML乱码

20191112142930.png

检查浏览器使用的编码

20191112142826.png

查看源文件指定的字符集为UTF-8

20191112143402.png

指定浏览器编码

20191112143049.png

** 解决方案**

开发时HTML在<meta>中明确指定字符集

3、JSP乱码

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        ...
    </head>
    ...
</html>

JSP在部署后提供给用户使用,会经过三个阶段

  1. JSP生成java文件:这个阶段会使用pageEncoding所定义的编码格式进行转换
  2. java文件生成class文件:这个阶段由服务器tomcat自动使用utf-8编码把java文件转换成字节码class文件
  3. 通过读取class文件展现给用户:这个阶段由tomcat服务器获取字节码内容,通过使用contentType所定义的编码格式展现给用户

20191112151803.png

解决方案

请在各个环节指定正确的字符集,最好全部指定utf-8

carbon (1).png

4、GET,POST请求乱码

form表单有两种方法把数据提交给服务器:

1)get提交

20191120140024.png

get请求时,在前端URIEncode编码两次;

容器接收时,默认ISO-8859-1解码一次;

手动再解码一次。

前端:

url1=encodeURI(url);   
url2=encodeURI(url1);

后台:

String name1=request.getParameter("name");   
String name2 = java.net.URLDecoder.decode(name1,"UTF-8"); 

不管容器用的什么编码,都不会有乱码:

url1会把url中的中文编为ASCII码。

url2对url1进行再编码,由于是对ASCII码进行编码,在request.getParameter(“name”)解码的时候,不管是按 GBK 还是 UTF-8 还是 ISO-8859-1 都好,都能够正确的得到url1。

因为ASCII码用GBK、UTF-8、ISO-8859-1编码的结果是相同的

首先说下客户端(浏览器)的form表单用get方法是如何将数据编码后提交给服务器端的

​ 对于get方法来说,都是把数据串联在请求的url后面作为参数,如:http://localhost:8080/servlet?msg=abc(很常见的一个乱码问题就要出现了,如果url中出现中文或其它特殊字符的话,如:http://localhost:8080/ /servlet?msg=南京,服务器端容易得到乱码),url拼接完成后,浏览器会对url进行URL encode,然后发送给服务器,URL encode的过程就是把部分url做为字符,按照某种编码方式(如:utf-8,gbk等)编码成二进制的字节码,然后每个字节用一个包含3个字符的字符串 “%xy” 表示,其中xy为该字节的两位十六进制表示形式。具体介绍可以看下java.net.URLEncoder类的介绍。

​ 了解了 URL encode的过程,我们能看到2个很重要的问题:

​ 第一:需要URL encode的字符一般都是非ASCII的字符(笼统的讲),再通俗的讲就是除了英文字母以外的文字(如:中文,日文等)都要进行URL encode,所以对于我们来说,都是英文字母的url不会出现服务器得到乱码问题,出现乱码都是url里面带了中文或特殊字符造成的;

​ 第二:URL encode到底按照那种编码方式对字符编码?这里就是浏览器的事情了,而且不同的浏览器有不同的做法,中文版的浏览器一般会默认的使用GBK,通过设置浏览器也可以使用UTF-8,可能不同的用户就有不同的浏览器设置,也就造成不同的编码方式,所以很多网站的做法都是先把url里面的中文或特殊字符用 javascript做URL encode,然后再拼接url提交数据,也就是替浏览器做了URL encode,好处就是网站可以统一get方法提交数据的编码方式。 完成了URL encode,那么现在的url就成了ASCII范围内的字符了,然后以iso-8859-1的编码方式转换成二进制随着请求头一起发送出去。这里想多说几句的是,对于get方法来说,没有请求实体,含有数据的url都在请求头里面,之所以用URL encode,我个人觉的原因是:对于请求头来说最终都是要用iso-8859-1编码方式编码成二进制的101010…的纯数据在互联网上传送,如果直接将含有中文等特殊字符做iso-8859-1编码会丢失信息,所以先做URL encode是有必要的。
服务器端(tomcat)是如何将数据获取到进行解码的。
第一步是先把数据用iso-8859-1进行解码,对于get方法来说,tomcat获取数据的是ASCII范围内的请求头字符,其中的请求url里面带有参数数据,如果参数中有中文等特殊字符,那么目前还是URL encode后的%XY状态,先停下,我们先说下开发人员一般获取数据的过程。通常大家都是request.getParameter(“name”)获取参数数据,我们在request对象或得的数据都是经过解码过的,而解码过程中程序里是无法指定,这里要说下,有很多新手说用 request.setCharacterEncoding(“字符集”)可以指定解码方式,其实是不可以的,看servlet的官方API说明有对此方法的解释:Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader().可以看出对于get方法他是无能为力的。那么到底用什么编码方式解码数据的呢,这是tomcat的事情了,默认缺省用的是 iso-8859-1,这样我们就能找到为什么get请求带中文参数为什么在服务器端得到乱码了,原因是在客户端一般都是用UTF-8或GBK对数据 URL encode,这里用iso-8859-1方式URL decoder显然不行,在程序里我们可以直接

Java代码

new String(request.getParameter("name").getBytes("iso-8859-1"),"客户端指定的URL encode编码方式")

还原回字节码,然后用正确的方式解码数据,通常是在tomcat里面做个配置
Xml代码

<Connector port="8080" protocol="HTTP/1.1" maxThreads="150" connectionTimeout="20000" redirectPort="8443" URIEncoding="GBK"/>

20191113105344.png

这样是让tomcat在获取数据后用指定的方式URL decoder,URL decoder的介绍在这里

http://blog.csdn.net/vickyway/article/details/46375971

http://www.jb51.net/article/80181.htm

2)post提交

20191120140007.png

客户端(浏览器)的form表单用post方法是如何将数据编码后提交给服务器端的。
在post方法里所要传送的数据也要URL encode,那么他是用什么编码方式的呢?
在form所在的html文件里如果有段<meta http-equiv="Content-Type" content="text/html; charset=字符集(GBK,utf-8等)"/>,那么post就会用此处指定的编码方式编码。一般大家都认为这段代码是为了让浏览器知道用什么字符集来对网页解释,所以网站都会把它放在html代码的最前端,尽量不出现乱码,其实它还有个作用就是指定form表单的post方法提交数据的 URL encode编码方式。从这里可以看出对于get方法来数,浏览器对数据的URL encode的编码方式是有浏览器设置来决定,(可以用js做统一指定),而post方法,开发人员可以指定。
服务器端(tomcat)是如何将数据获取到进行解码的。
如果用tomcat默认缺省设置,也没做过滤器等编码设置,那么他也是用iso-8859-1解码的,但是request.setCharacterEncoding("字符集")可以派上用场。

我发现上面说的tomcat所做的事情前提都是在请求头里没有指定编码方式,如果请求头里指定了编码方式将按照这种方式编码。
有2篇文章推荐下,地址分别是
深入浅出URL编码:http://www.cnblogs.com/yencain/articles/1321386.html;
表单用post方法提交数据时乱码问题:http://wanghuan8086.javaeye.com/blog/173869

用post很重要的在form所在的html文件里如果有段<meta http-equiv="Content-Type" content="text/html; charset=字符集(GBK,utf-8等)"/>
强烈建议使用post提交

也可以通过base64编码传输,服务端base64解码拿到正确的字符

5、URL包含特殊字符

在HTTP请求时会遇到以下几种情况:

  1. 当字符串数据以url的形式传递给web服务器时,字符串中是不允许出现空格和特殊字符串的
  2. url对字符有限制,比如把一个邮箱放入url,就需要使用urlencode函数。
  3. url转义其实也只是为了符合url的规范而已。因为在标准的url规范中中文和很多的字符是不允许出现在url中的。

解决方案

使用 urlencode (上文已介绍过)

java:

java.net.URLEncoder.encode("要转码的内容","UTF-8"); // 原来单参数方法已经过时。

javaScript:

encodeURI()

6、数据库乱码

1、MySQL环境变量

Session会话变量:
使用show variables like '%char%';可以查看Session会话的字符集变量:

c600be454fc86ec01c6cb3f5c472b0a3.png

set character_set_server=utf8;
set character_set_database=utf8;
使用SET可以设置不同字符集。但是使用SET设置的字符集都是Session会话级别的,如果新打开一个会话,新会话使用的是默认的字符集。
Global全局变量:
使用show global variables like '%char%';可以查看Global的字符集变量:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZLuomhT7-1574241894801)(https://i.loli.net/2019/11/12/AiQSf3M6umIK82O.png)]

set global character_set_database=utf8;
set global character_set_server=utf8;
使用SET GLOBAL可以设置多个会话的字符集。
使用show charset;查看MySQL支持的字符集和对应字符集的字符序。
MySQL服务重启后,Global的值会被重置为默认值。永久修改Global的值的方法如下:
修改mysql配置文件/etc/my.cnf

[mysqld]
character-set-server=utf8
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
2、MySQL字符集

MySQL服务器可以支持多种字符集,提供了不同级别的设置,包括server级、database级、table级、column级。
MySQL数据库的环境变量查看使用SQL语句show variables like '%char%';
character_set_client:客户端使用的字符集,当客户端向服务器发送请求时,请求以客户端字符集进行编码。
character_set_connection :客户端/数据库建立的通信连接使用的字符集,MySQL服务器接收客户端的查询请求后,将其转换为character_set_connection变量指定的字符集。
character_set_database:数据库服务器中某个数据库的字符集,如果没有默认数据库字符集,使用 character_set_server指定的字符集。
character_set_results:数据库给客户端返回时的字符集,MySQL数据库把结果集和错误信息转换为character_set_results指定的字符集,并发送给客户端。
character_set_server:数据库服务器的字符集,内部操作字符集。
character_set_system:系统元数据(字段名等)使用的字符集
当客户端连接服务器的时候,客户端会将自己想要的字符集名称发给MySQL服务端,然后服务端就会使用字符集去设置character_set_connection、character_set_client、character_set_results。
创建数据库时如果不指定数据库的字符集,默认会使用character_set_server字符集。
创建表时如果不指定表的字符集,默认使用当前数据库字符集。
创建列时如果不指定字符集,默认使用当前表的字符集。

3、MySQL字符集的设置

A、MySQL服务器级字符集
修改MySQL服务器配置文件/etc/my.cnf文件。

[mysqld]
character_set_server=utf8

重启MySQL数据库服务生效。
B、MySQL数据库级字符集:
创建数据库时指定:

CREATE DATABASE db_name
[[DEFAULT] CHARACTER SET charset_name]
[[DEFAULT] COLLATE collation_name]

修改已有的数据库的字符集:

ALTER DATABASE db_name
[[DEFAULT] CHARACTER SET charset_name]
[[DEFAULT] COLLATE collation_name]

ALTER修改只对修改后在数据库上的操作有效。
C、MySQL表级字符集:
创建表时指定:

CREATE TABLE tbl_name (column_list)
[[DEFAULT] CHARACTER SET charset_name]
[COLLATE collation_name]]

修改表的字符集:

ALTER TABLE tbl_name
[[DEFAULT] CHARACTER SET charset_name]
[COLLATE collation_name]

D、MySQL字段级字符集:
修改已有字段的字符集:

ALTER TABLE table_name MODIFY
column_name {CHAR | VARCHAR | TEXT} (column_length)
  [CHARACTER SET charset_name]
  [COLLATE collation_name]

MySQL客户端设置:set names utf8;等价于:
set character_set_client=utf8;
set character_set_connection=utf8;
set character_set_results=utf8;
E、客户端字符集
修改MySQL服务器配置文件/etc/my.cnf文件。

[client]
default-character-set=utf8

等价于set names utf8;
会影响会话中的变量character_set_client,character_set_connection 和character_set_results的值。
修改后无需重启MySQL数据库服务即可生效。

4、MySQL字符集的转换过程

20191112175310.png
A、MySQL服务端收到请求时将请求数据从character_set_client字符集转换为character_set_connection字符集。
B、进行内部操作前将请求数据从character_set_connection字符集转换为内部操作字符集。确定步骤:
–使用每个数据字段的CHARACTER SET设定值;
–若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值;
–若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
–若上述值不存在,则使用character_set_server字符集设定值;
C、将操作结果从内部操作字符集转换为character_set_results字符集。
D、将character_set_results字符集的执行结果转换为character_set_client字符集,发送到客户端,客户端使用设置的字符集展示结果。

5、MySQL乱码产生的原因

乱码产生的原因如下:
A、存入和取出时对应环节的编码不一致。
B、如果两个字符集之间无法进行无损编码转换,一定会出现乱码。

6、编码无损转换

如果一个使用编码A表示的字符X,转化为编码B的表示形式,而编码B的字符集中并没有X字符,则编码转换是有损的,否则编码转换就是无损的。
由于每个字符集所支持的字符数量是有限的,并且各个字符集涵盖的字符之间存在差异。将UTF-8字符转换为GBK字符时,MySQL内部如果无法在GBK字符集找到一个UTF8字符集中的字符时,就会转换成一个错误标记(0x3F,问号)。
编码无损转换的条件:
A、被转换的字符是否同时在两个字符集中。
B、目标字符集是否能够对不支持字符,保留其原有表达形式。

解决方案

从创建数据库,创建表到连接数据库全部指定统一字符集,并从一而终。


©️ 框架组 以上信息如有不足,请斧正 📧 mengfanding@aliyun.com

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值