Guava源码阅读-base-Charsets

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编码。如果你不指定解析时的编码格式,那么编码器和解码器会使用系统默认的编码格式,这就相当于将你的数据与你的系统强行绑定,不利于跨平台操作。

时间: 2024-10-10 15:36:40

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

Guava源码阅读-base-Enums

package com.google.common.base; guava源码中对这个类的方法介绍只有一句话: Utility methods for working with {@link Enum} instances. 意思就是给Enum实例提供的工具方法. 枚举是什么? 枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合.举个例子,颜色也可以定义成枚举类型,它可以包含你定义的任何颜色,当需要的时候,只需要通过枚举调用即可,另外比如说季

guava源码阅读

Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] .缓存 [caching] .原生类型支持 [primitives support] .并发库 [concurrency libraries] .通用注解 [common annotations] .字符串处理 [string processing] .I/O 等等. 所有这些工具每天都在被Google的工程师应用在产品服务中. Guava源码的包图如下:  源码包的简单说明: co

[Guava源码阅读笔记]-Basic Utilities篇-1

写该系列文章的目的是记录Guava源码中个人感觉不错且值得借鉴的内容. 一.MoreObjects类 //MoreObjects.ToStringHelper类的toString()方法:对于字符串拼接的写法蛮不错的,此前本人一直用比较挫的方式:不管三七二一,先拼接然后再subString() @Override public String toString() { // create a copy to keep it consistent in case value changes bool

Flume-NG源码阅读之HBaseSink

关于HBase的sink的所有内容均在org.apache.flume.sink.hbase包下. 每个sink包括自己定制的,都extends AbstractSink implements Configurable. 一.首先是configure(Context context)方法.该方法是对HBaseSink的参数初始化.主要包括以下几个: tableName:要写入的HBase数据表名,不能为空: columnFamily:数据表对应的列簇名,这个sink目前只支持一个列簇,不能为空:

android插件化-apkplugdemo源码阅读指南-10

阅读本节内容前可先了解 apkplug基础教程 本教程是基于apkplug V1.6.8 版本编写  最新开发方式以官网为准 可下载最新的apkplugdemo源码http://git.oschina.net/plug/apkplugDemos apkplugdemo演示图 一 apkplugdemo工程源码结构 src |-com.apkplugdemo.adapter             --插件列表Adapter |-com.apkplugdemo.adapter.base     

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

到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.com/usr/reg 经过引导文件,实际上会交给Application中的UsrController控制器的reg方法去处理. 这之中,CodeIgniter.php做了哪些工作?我们一步步来看. 1.    导入预定义常量.框架环境初始化 之前的一篇博客(CI框架源码阅读笔记2 一切的入

Nutch源码阅读进程4---parseSegment

前面依次看了nutch的准备工作inject和generate部分,抓取的fetch部分的代码,趁热打铁,我们下面来一睹parse即页面解析部分的代码,这块代码主要是集中在ParseSegment类里面,Let‘s go~~~ 上期回顾:上回主要讲的是nutch的fetch部分的功能代码实现,主要是先将segments目录下的指定文件夹作为输入,读取里面将要爬取的url信息存入爬取队列,再根据用户输入的爬取的线程个数thread决定消费者的个数,线程安全地取出爬取队列里的url,然后在执行爬取页

Android系统源码阅读(12):InputChannel的注册过程

Android系统源码阅读(12):InputChannel的注册过程 请对照AOSP版本:6.0.1_r50. InputManager可以获得输入事件并分发,Activity需要处理这些输入事件.那么,这两者之间如何建立的连接呢?这就需要InputChannel作为桥梁建立两者之间的通道. 1. ViewRootImpl创建InputChannel 这里ViewRoot类已经消失了,由ViewRootImpl替代.Activity在创建时会将自己的DecorView设置给对应的ViewRoo

Android系统源码阅读(13):Input消息的分发过程

Android系统源码阅读(13):Input消息的分发过程 请对照AOSP版本:6.0.1_r50.学校电脑好渣,看源码时卡半天 先回顾一下前两篇文章.在设备没有事件输入的时候,InputReader和InputDispatcher都处于睡眠状态.当输入事件发生,InputReader首先被激活,然后发送读取消息,激活Dispatcher.Dispatcher被激活以后,将消息发送给当前激活窗口的主线程,然后睡眠等待主线程处理完这个事件.主线程被激活后,会处理相应的消息,处理完毕后反馈给Dis