package com.google.common.base;

今天在找base包下的源码阅读时,看到了Charsets,肯定是定义字符集的类,本来就想简单的看一下。(部分内容摘抄自:http://blog.csdn.net/sundaysunshine/article/details/53954813)

随后想到在web工程里一直会遇到编码问题,于是想总结一下编码的问题。

先看看Charsets的定义的一些常量吧。

public static final Charset US_ASCII = Charset.forName("US-ASCII");

public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");

public static final Charset UTF_8 = Charset.forName("UTF-8");

public static final Charset UTF_16BE = Charset.forName("UTF-16BE");

public static final Charset UTF_16LE = Charset.forName("UTF-16LE");

public static final Charset UTF_16 = Charset.forName("UTF-16");

从源码中,我们可以看到它定义的一些字符集。


我们在编写web工程时,肯定会遇到:1、页面乱码,2、servlet获取数据乱码,3、数据库乱码

那么我们是怎么解决的呢,大约就是这些转码方式:

1、在jsp页面头设置编码方式:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
2、纯粹html乱码:
最好是在<head></head>之间添加<meta charset="utf-8">
3、<%request.setCharacterEncoding("utf-8");%>
4、response.setContentType("text/hmtl;charset=UTF-8");
5、使用getBytes()重新构造接收的String: new String(username.getBytes("ISO-8859-1"), "UTF-8");
6、另外就是编码与解码
比如存储cookie信息时:
Cookie userNameCookie = new Cookie("userName", URLEncoder.encode(user.getUserName(), "utf-8")); //存入信息时编码 userName = URLDecoder.decode(cookies[i].getValue(), "utf-8"); //取信息时,进行解码

那么疑问来了,为什么会出现乱码的情况呢?

答案就是只要有I/O操作的地方都会可能有乱码发生。

那么再看看为什么会出现乱码?(下面部分内容摘抄自:http://blog.csdn.net/sundaysunshine/article/details/53954813)

我们知道I/O有四大家族InputStream, OutputStream,Writer,Reader前两个是基于字节的操作,后两个是基于字符的操作。由于我们平时是使用字符的方式进行记录信息,但由于计算机只认识0和1,所以I/O作为人机交互的手段,它的底层一定是以字节的方式进行传送的,编码问题其实就是发生在字符和字节之间相互转换的时候。

读操作:

写操作:

也就是说如果我们用错了字典,那么编码或解码出来的数据就会可能出现问题。


下面介绍一下各个编码:

  • ASCII 码

学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。

  • ISO-8859-1

128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。

  • GB2312

它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

  • GBK

全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。

  • GB18030

全称是《信息交换用汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。

  • UTF-16

说到 UTF 必须要提到 Unicode(Universal Code 统一码),ISO 试图想创建一个全新的超语言字典,世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典是多么的复杂,关于 Unicode 的详细规范可以参考相应文档。Unicode 是 Java 和 XML 的基础,下面详细介绍 Unicode 在计算机中的存储形式。

UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。

  • UTF-8

UTF-16 统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。

UTF-8 有以下编码规则:

  1. 如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
  2. 如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。
  3. 如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节

  0xxxxxxx
  110xxxxx 10xxxxxx
  1110xxxx 10xxxxxx 10xxxxxx
  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx


网络端的I/O主要就拿以https作为基点说,因为https(安全为目标的HTTP通道,简单讲是HTTP的安全版,数据传输时用SSL加锁)是未来的一种趋势。HTTPS的I/O场景如下:

网络端的https请求实质是通过URL链接的。所以网络I/O的更多的情况下依赖于URL的实现的。
一个简单的URL包括如下图:

https是协议名,www.baidu.com是域名,紫色部分就是请求时携带的信息。可以看到路径中出现了中文,所以如果服务器端解码时使用了错误的编码格式,将会导致请求失败,同时当服务器端发送返回数据时如果浏览器端采用了错误的编码方式也会导致乱码。具体来说包括(这里忽略https里面的SSL加密和解密的过程):

HTTPS Header (报头)的编解码

通常被分为4个部分:general  header, request header, response header, entity header。

当客户端发起一个 HTTPS请求除了上面的 URL 外还可能会在 Header 中传递其它参数如 Cookie、redirectPath 等,这些用户设置的值很可能也会存在编码问题,服务器(以Tomcat为例) 对它们又是怎么解码的呢?

对 Header 中的项进行解码也是在调用 request.getHeader
是进行的,如果请求的 Header 项没有解码则调用 MessageBytes 的 toString 方法,这个方法将从 byte 到 char
的转化使用的默认编码也是 ISO-8859-1,而我们也不能设置 Header 的其它解码格式,所以如果你设置 Header 中有非
ASCII 字符解码肯定会有乱码。

我们在添加 Header 时也是同样的道理,不要在 Header 中传递非
ASCII 字符,如果一定要传递的话,我们可以先将这些字符用 org.apache.catalina.util.URLEncoder
编码然后再添加到 Header 中,这样在浏览器到服务器的传递过程中就不会丢失信息了,如果我们要访问这些项时再按照相应的字符集解码就好了。

POST 表单的编解码

在前面提到了 POST 表单提交的参数的解码是在第一次调用
request.getParameter 发生的,POST 表单参数传递方式与 QueryString 不同,它是通过 HTTPS 的 BODY
传递到服务端的。当我们在页面上点击 submit 按钮时浏览器首先将根据 ContentType 的 Charset
编码格式对表单填的参数进行编码然后提交到服务器端,在服务器端同样也是用 ContentType 中字符集进行解码。所以通过 POST
表单提交的参数一般不会出现问题,而且这个字符集编码是我们自己设置的,可以通过
request.setCharacterEncoding(charset) 来设置。

另外针对 multipart/form-data
类型的参数,也就是上传的文件编码同样也是使用 ContentType
定义的字符集编码,值得注意的地方是上传文件是用字节流的方式传输到服务器的本地临时目录,这个过程并没有涉及到字符编码,而真正编码是在将文件内容添加到
parameters 中,如果用这个编码不能编码时将会用默认编码 ISO-8859-1 来编码。

HTTP BODY 的编解码

当用户请求的资源已经成功获取后,这些内容将通过 Response
返回给客户端浏览器,这个过程先要经过编码再到浏览器进行解码。这个过程的编解码字符集可以通过
response.setCharacterEncoding 来设置,它将会覆盖 request.getCharacterEncoding
的值,并且通过 Header 的 Content-Type 返回客户端,浏览器接受到返回的 socket 流时将通过 Content-Type 的
charset
来解码,如果返回的 HTTP Header 中 Content-Type 没有设置 charset,那么浏览器将根据 Html 的
<meta HTTP-equiv="Content-Type" content="text/html; charset=GBK"
/> 中的 charset 来解码。如果也没有定义的话,那么浏览器将使用默认的编码来解码。

所以服务器端和客户端必须协调好编码格式,通常将其编码设置为utf-8,同时浏览器也最好将编码格式改为utf-8。

这里也提出I/O乱码问题的解决思路就是:先找出存在读和写操作的地方,然后逐一进行编码格式的排查。

为了支持国际化以及防止乱码建议使用utf-8编码。如果你不指定解析时的编码格式,那么编码器和解码器会使用系统默认的编码格式,这就相当于将你的数据与你的系统强行绑定,不利于跨平台操作。

Guava源码阅读-base-Charsets的更多相关文章

  1. Guava源码阅读-base-CharMatcher

    package com.google.common.base; (部分内容摘自:http://blog.csdn.net/idealemail/article/details/53860439) 之前 ...

  2. Guava源码阅读-base-Enums

    package com.google.common.base; guava源码中对这个类的方法介绍只有一句话: Utility methods for working with {@link Enum ...

  3. Guava源码阅读-base-Strings

    package com.google.common.base; 今天阅读的是Srings类,这在程序中经常使用. 比如判断字符串是否为空,我们在之前用jdk方法判断是会用下面这个判断语句. if( i ...

  4. Guava源码阅读-collect-Multiset

    package com.google.common.collect; 我们在进行字符统计时,同常采用的方法就是: String[] text=new String[]{"the weathe ...

  5. Guava源码阅读-io-Files

    package com.google.common.io; 今天阅读一个非常常用的类Files,文件操作类. readLines(File file, Charset charset),这个方法将Fi ...

  6. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

  7. Android源码阅读 – Zygote

    @Dlive 本文档: 使用的Android源码版本为:Android-4.4.3_r1 kitkat (源码下载: http://source.android.com/source/index.ht ...

  8. 源码阅读系列:EventBus

    title: 源码阅读系列:EventBus date: 2016-12-22 16:16:47 tags: 源码阅读 --- EventBus 是人们在日常开发中经常会用到的开源库,即使是不直接用的 ...

  9. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是“引导”文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.c ...

随机推荐

  1. [2019HDU多校第一场][HDU 6580][C. Milk]

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6580 题目大意:\(n\times m\)大小的方格上有\(k\)瓶水,喝完每瓶水都需要一定的时间.初 ...

  2. The method setCharacterEncoding(String) is undefined for the type HttpServletResponse

    今天将以前做的一个web项目从不笔记本上移到台式机上,import项目后出现“The method setCharacterEncoding(String) is undefined for the ...

  3. 关于不用Hashtable

    hashmap 与hashtable 很类似,主要区别是hashtable 有用synchronized进行线程同步,hashmap没有.然而,建议少用hashtable,在单线程中,无需做线程控制, ...

  4. fputcsv

    1.a是從結尾添加,w是從頭添加,不知爲什麽 這樣寫最後一行會是錯的,似乎不會自動添加換行符,只有在最後以行寫上"\r\n",才會正確. $fp = sugar_fopen('fi ...

  5. junit3和junit4的使用区别如下

    junit3和junit4的使用区别如下1.在JUnit3中需要继承TestCase类,但在JUnit4中已经不需要继承TestCase2.在JUnit3中需要覆盖TestCase中的setUp和te ...

  6. mysql报错码code=exited,status=2的解决方案

    由于电脑死机,导致MySQL无法重启. 解决方案看官方文档,设置完后重启失败,再把innodb_force_recovery = 1去掉就可以了 https://dev.mysql.com/doc/r ...

  7. Flask-login Question

    1 未登录访问鉴权页面如何处理? 如果未登录访问了一个作了 login_required 限制的 view,那么 Flask-Login 会默认 flash一条消息,并且将重定向到login_view ...

  8. 测试puppeteer模拟度检测

    var puppeteer = require('puppeteer'); const devices = require('puppeteer/DeviceDescriptors'); const ...

  9. centos7远程服务器中redis的安装与java连接

    1.下载安装redis 在远程服务器中你想下载的位置执行以下命令来下载redis文件到服务器中 $ wget http://download.redis.io/releases/redis-4.0.9 ...

  10. pwn学习日记Day17 《程序员的自我修养》读书笔记

    静态链接章小结 本章首先学习了静态链接的第一步骤,即目标文件在被链接成最终可执行文件时,输入目标文件中的各段是如何被合并到输出文件中的,链接器如何为它们分配在输出文件中的空间和地址.一旦输入段中的最终 ...