引子
为什么要编码
首先理解计算机的基本存储单元(byte),其最多能表达256个字符,而世界语言何其复杂,是远远大于256的,
所以为了让计算机能表现各种语言,就需要编码。
怎么编码:
也就是约定翻译规范,下面简要概述一些我们比较常用的字符编码
字符编码:
1、ASICII:一个字节,128个码
2、ISO-8859-1:一个字节,256个码
GBK:两个字节,是中国的汉字编码规范
UTF-16:两个字节,无论什么字符均用两个字节表示,(简化了字符串操作,也作为java内存字符存储格式)
UTF-8:变长编码技术。
编码场景
I/O编码
首先关于数据流,在java中有两种形式:
1、字节流 inputStream、OutputStream
2、字符流 Reader、Writer
这两个是传输数据的格式。
而数据的持久化或网络数据的传输都是以字节的形式进行的。
java中字节是一个字节,字符是两个字节,所以必须需要进行字节与字符之间的转化。、
而字符的存在便是为了方便不同语种的程序员操作数据的直观与便捷,这就涉及到字符编码。
Reader是读字符的接口,InputStream是读字节的接口,InputStreamReader同时实现了InputStream与Reader,所以我们可以说InputStreamReader是关联字节到字符的桥梁。
而InputStreamReader也以聚合的方式关联了一个StreamDecoder,这个StreamDecoder聚合的方式关联一个Charset,这个Charset是在new InputStreamReader的时候指定的,如果没有指定则使用系统默认的字符集。StreamDecoder根据Charset的字符集解码字节到字符。
写的情形与读是相同的,这里只展示类关系图
使用InputStreamReader的时候注意指定字符集,而不要使用系统默认的编码。
在内存操作中的编码
在这里我们首先做一个操作,
String str = "I am 君山";
printHex(str.toCharArray());
static void printHex(char[] chars) {
ByteBuffer bb = ByteBuffer.allocate(chars.length*2);
for (int i = 0; i < chars.length; i++){
bb.putChar(chars[i]);
}
bb.flip();
for (int i = 0; i < bb.limit(); i++){
System.out.print(String.format("%x ", bb.get()));
}
System.out.println();
}
程序输出:0 49 0 20 0 61 0 6d 0 20 54 1b 5c 71
而我们看String类的toCharArray代码
char result[] = new char[value.length];
System.arraycopy(value, 0, result, 0, value.length);
return result;
这里就是返回String中存储的value内存的拷贝。所以我们可以了解到一个字符数据在String中是以UTF-16的字符编码的方式存储的。
解析来我们看看str.getBytes();
String csn = Charset.defaultCharset().name();
try {
// use charset name encode() variant which provides caching.
return encode(csn, ca, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
str.getBytes("UTF-8");
1、根据传入的字符集名称找到该名称对应的Charset
Charset cs = lookupCharset(csn);
if (cs != null)
se = new StringEncoder(cs, csn)
//根据字符集解码为字节数据
se.encode(ca, off, len);
没有指定字符集的会采用系统默认的字符集,指定字符集会根据指定字符集。
字符在String中的存储形式是两个字节的UTF-16,所有不管什么字符集编码解码到字符的时候都会转成UTF-16的形式
JAVA web涉及编解码
一次http请求,涉及到很多地方编解码操作。
从提交请求到通过网络传输,请求数据中的URL部分,http header,表单数据等,均需要编码,服务端接收到请求后,需要针对这些部分一一进行解码。
如何涉及到保存数据库,也存在到数据库的编码。
下面我们将对各处编码进行剖析:
1、URL编解码
我们先来了解一下URL组成
http://localhost:8080/a/君山?auth=君山
URL(Uniform Resource Locator):http://localhost:8080/a/君山,称统一资源定位符。
URL(Uniform Resource Identifier):/a/君山,统一资源标识符
queryString:auth=君山,传递的参数
下面是经过浏览器的请求地址:
http://localhost:8080/a/%E5%90%9B%E5%B1%B1?auth=%E5%90%9B%E5%B1%B1
君山的编码位E5909B E5B1B1,可知pathInfo与QueryString的编码都采用了UTF-8的编码形式(他们可以采用不一样的编码形式)
我们来看看服务端的解码:URL的URI部分的解码部分在<Connector URIEncoding="UTF-8"/>中定义,如果没有定义将默认采用ISO-8850-1的字符集。
针对QueryString的编解码,get与post都在服务端用RequestParameter保存,解码在request.getParameter获取参数值时发生。queryString的编码方式通过http的header中的ContentType:Charset中带过来,
QueryString的解码要么采用header中指定的要么采用默认的Iso8859-1,要采用上送的字符集编码需要在<Connect URIEncoding="UTF-8" userBodyEncodingForURI="true:/>
2、Http Header的编解码
header默认采用iso-8859-1,且不可配置,所以header中不要有中文
3、POST表单的编解码
可通过request.setCharacterEncoding()配置,一定要在request.getParameter之前配置
4、httpBody的编解码
应答给客户端的http body的服务端编码设置response.setCharacterEncoding配置,客户端通过content-type中的charset,解码,没有设置则采用<meta中的charset>,否则采用系统默认
5、JS编解码
前端可以使用encodeURI()和encodeURLComponent();
encodeURI():主要用于整个URI,其不会对本身属于URI的特殊字符进行编码。例如冒号、正斜杠、问好和井字号
encodeURLComponent():主要用于URI中的某一字段进行编码,其会对发现的任何非标准字符进行编码。
java服务端解码:
java段的URLEncoder与URLDecoder分别对应前端的encodeURLComponent/decodeURLComponent