乱码问题(二)常见乱码情况分析与解决方法

在上一篇我们把编码的理论知识讲了一遍,在这篇我将会根据这列理论知识对一些乱码问题进行具体分析并给出解决方法

操作系统中的编码

在window中默认文件编码格式是ANSI,而ANSI并不特指哪一个具体的编码,它在不同的语言环境会对应不同的编码,比如在简体中文系统中就是GBK编码,而在繁体中文就是Big5编码。我们可以在Windows记事本中通过另存为来指定保存时所用的编码,如果不知道默认会以GBK编码保存(简体中文环境)


而在Liunx系统中文件默认编码为UTF-8,所以会经常出现一个文件在两个操作系统之间转移的时候发生乱码,其实解决方法很简单

  1. 更改文件的读取编码为原系统的默认编码
  2. 在原系统中按照目标系统的编码重新保存

也可以先1,之后直接把文件转换成当前系统默认编码,这样就不用每次打开文件时重新选择读取编码

java中字符串乱码

java类中字符串乱码

在java中,一个程序需要先编译成class文件,然后在由java虚拟机进行执行。而在class文件中,字符串都是以UTF-8编码表示的,这个是java虚拟机的规范。Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。而表结构中存在着一个叫CONSTANT_Utf8_info的表,用来存放字符串,而且是按UTF-8编码。所以乱码实际上是编译的时候你的文件编码跟javac使用的文件编码不一致而导致的。
javac默认的编码为操作系统的编码,这个编码在windows下位GBK,Linux下为UTF-8。在这里我用UTF-8编码格式把下面代码保存在windows系统中。

public class EncodingTest
{
    public static void main(String[] args)
    {
        String s1 = "啊哈哈哈";
        System.out.println(s1);
    }
}

编译,运行,结果为乱码


这是因为javac以GBK编码格式来读取我们的文件。
解决方法也很简单,只需要在编译时加上一个-encoding utf-8参数就行了


而在IDE里,不管文件用什么编码,运行时我们基本都没有发现过乱码,其实IDE在编译时会帮助我们加上编码参数。
我们经常听说java中一个字符占两个字节,可是刚才又提到class文件中为UTF-8编码,这不是矛盾吗。其实我们所说的java中一个字符占两个字节指的是我们的java程序在运行时一个字符占用两个字节的空间,而着两个字节的内容正式其对应的UTF-16BE编码值。我们知道java虚拟机不能直接运行class文件,一个class能运行需要经过类的加载链接初始化三个阶段,而在这个过程过包括了把class文件的字符串字面量读取并放入java堆中的字符串常量池,而在常量池里都是按照UTF-16BE编码存在。

文件读取乱码

文件读取乱码的原因相信大家也很容易猜到了,就是读取编码和文件实际编码不一致。但是实际上会有各种复杂的情况。比如下面的代码,我们在Intellij idea里运行,其中1.txt是在windows系统里以UTF-8编码的文件,我们用NIO的api直接把文件内容读取到一个byte数组里,然后用这个byte数组构造一个字符串。

@Test
public void testFieEncoding() throws IOException
{
    Path path = Paths.get("1.txt");
    byte[] bytes = Files.readAllBytes(path);
    String s1 = new String(bytes);
    System.out.println(s1); 
}

按道理来说在这里由于没有指定编码,所以为默认的GBK,应该得到乱码输出,可是并没有

这与源文件内容完全相同。但是当我们在命令行运行时,会得到乱码输出(因为命令行运行junit不太方便,我把Test函数的内容放到主函数里了)

是不是很神奇,其实这都是IDE搞的鬼,Intellij idea在运行程序时会加上一个-Dfile.encoding虚拟机参数,其值为项目使用的编码。

jvm中的默认编码

在jvm进行需要编码的相应操作时,如果我们没有指定,其便会采用默认的编码进行处理,而这个默认的编码我们可以通过以下代码获得

System.out.println(System.getProperty("file.encoding"));

或者用下面jdk1.4新增的方法

System.out.println(Charset.defaultCharset());

其中JVM获取默认编码的方式如下

  1. 首先去查看是否在启动jvm时指定了-Dfile.encoding参数,如果指定,则为其指定的编码
  2. 如果1失败了,则会去环境变量里去找JAVA_TOOLS_OPTIONS这个环境变量。在这个环境变量里我们指定jvm默认的编码,比如设置其值为-Dfile.encoding="UTF-16"
  3. 如果1和2都失败了,则会去读取操作系统的默认编码,并将其设置为默认编码

不过,最好的方法应该时在构造字符串时指定编码。像下面这样,只要读取文件编码为UTF-8,不管怎么搞,都不会出现乱码。

public void testFieEncoding() throws IOException
{
    Path path = Paths.get("1.txt");
    byte[] bytes = Files.readAllBytes(path);
    String s1 = new String(bytes, "UTF-8");
    System.out.println(s1);
}

http请求中的编码

URLEncode

我们知道URL只能包含英文字母、阿拉伯数字和某些标点符号,当我们要在Url中传递非英文字符的话,就必须要进行编码,而这个编码并没有具体的标准,有的浏览器默认按照UTF-8,有的则默认跟随系统编码。 一个URL编码过程是这样的,如我我要向localhost:8080以GET传递name=张三这个参数,首先它的URL将会是这个样子http://localhost:8080?name=张三,既然URL不允许有中文,那么需要对特殊字符“编码”做编码转换成字节,假设我们用UTF-8来进行Encoder,“张三”两个字通过UTF-8编码后的字节用16进制表示就是E5BCA0E4B889。UrlEncode方法在每个字节前面添加一个百分号,形成最终的地址:http://localhost:8080?name=%E5%BC%A0%E4%B8%89
我们可以借助URLEncoder这个类来帮助我们完成

System.out.println(URLEncoder.encode("http://localhost:8080?name=张三", "UTF-8"));

浏览器端的处理

刚才我们说了浏览器会对非英文字符进行Encoder处理,那个Encoder使用的编码又是如何确定的呢

  • 如果设置<meta charset="">标签,则会按照其设置的值进行Encoder
  • 如果没有设置,则会使用一种编码进行Encoder,前面我们说了,这个默认编码在不同浏览器上会有区别,有的会使用UTF-8,有的会使用操作系统默认的
  • 对于ajax请求,不管是否指定<meta charset="">标签,都是按照UTF-8来进行Encoder的

服务端的处理

尽然浏览器传过来的URL是编码过的,那么服务器端必然要对请求进行解码。而在Tomcat中默认的解码编码为ISO-8859-1。刚才我们讲了浏览器编码的编码一般不是UTF-8,就是操作系统的GBK,服务端解码是按照ISO-8859-1,所以很容易发生乱码.
解决方案

  • 转换编码
    假设项目采用UTF-8编码
String name = request.getParameter("name");
name = new String(name.getBytes("ISO-8859-1"),"UTF-8")
  • 修改tomcat的默认URIEncoding为UTF-8
    我们修改server.xml中的Connector标签,加上URIEncoding="UTF-8"
<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000" URIEncoding="UTF-8" 
               redirectPort="8443" />
  • 编写拦截器,指定编码
    比如在spring中使用的编码拦截器
<filter>
    <filter-name>charsetFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
   <filter-name>charsetFilter</filter-name>
   <url-pattern>*</url-pattern>
</filter-mapping>

post请求中的编码

对于post请求,其参数在请求体里,而不在URL中,所以不存在Encoder的过程,因此处理相对容易一些。除了上面的解决方法外,我们还可以通过request.setCharacterEncoding()方法来设置读取请求体时使用的编码,所以此方法对GET请求时无效的,而网上经常有人说拿它作为GET请求乱码的解决方案,是在误导别人。注意此方法要在获取参数之前调用。

常见的一些乱码

  • "??"乱码分析:
    这种情况一般是用不支持中文的编码去编码中文字符,例如ISO-8859-1 仅能编码非英文字符,所以非英文字符被其编码时会被转换为 0x3F(即?的 ASCII 编码),这时编码已经真被转成不可逆的乱码了。之后无论用兼容 ASCII 的哪种编码方案解码还原出的字符串都是"?"。

  • "²âÊÔ"乱码分析:

这种一般是用不支持中文的编码去解码含有中文的编码造成的,ISO-8859-1 仅能表示非英文字符,所以使用其解码时会严格按一个字节一个字节地进行解析(这种操作其实对编码没构成破坏,还可以重新用 ISO-8859-1 获取字节流后再用正确的编码方式解码得到正确的字符串)。

  • "����"乱码分析:

这种一般是GBK编码后的内容用UTF-8进行解码时发现四个字节均为 UTF-8 非法字节流,所以直接转化为了�。

乱码问题(一)编码那些事

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容