為什么要編碼?
計算機存儲信息的最小單位是1byte,即8bit,所能表示的字符個數(shù)為255個,但是人類要表示的符號太多,遠遠不止255個,西歐字符、中文等等符號。
解決這個問題,就必須要有一個新的數(shù)據(jù)結構,在java中就是char,從char到byte必須編碼(可以理解為翻譯)
常見編碼格式(字典)
- ASCⅡ
由于計算機最早是由美國發(fā)明的,根據(jù)他們的語言習慣,用1個字節(jié)可以表示128個字符,可以通過鍵盤輸入并且能夠顯示出來 - ISO-8859-1
應用最廣泛的編碼格式,占用2個字節(jié)可以表示256個字符,涵蓋大部分的西歐語言字符。 - 中文編碼
- GB2312
包含682個字符和6763個漢字 - GBK
總共有23940個碼位,能表示21003個漢字,兼容GB2312 - GB18030
國家標準,兼容GB2312,但是應用不廣泛
- GB2312
- Unicode
統(tǒng)一編碼,是計算機科學領域里的一項業(yè)界標準,包括字符集、編碼方案。 - UTF-16
用兩個字節(jié),將Unicode字符轉(zhuǎn)換為字節(jié)存儲。Java以UTF-16作為內(nèi)存的字符存儲格式 - UTF-8
變長,1-6個字節(jié)
Java中的編碼場景
磁盤I/O 和 網(wǎng)絡I/O
磁盤I/O
- 字符轉(zhuǎn)字節(jié):OutputStreamWriter作為橋梁,傳入字符集charset,委托StreamDecoder去做具體的字符轉(zhuǎn)字節(jié)的工作
- 字節(jié)轉(zhuǎn)字符:InputStreamReader 作為橋梁,傳入字符集charset,委托StreamDecoder去做具體的字節(jié)轉(zhuǎn)字符的工作
//字符轉(zhuǎn)字節(jié)
@Test
public void charToByteByDiskIo() {
String path = CodeProgram.class.getResource("").getPath();
File file = new File(path, "字符轉(zhuǎn)字節(jié)流,通過字符寫入磁盤文件.txt");
FileOutputStream fos = null;
OutputStreamWriter osw = null;
try {
fos = new FileOutputStream(file);
osw = new OutputStreamWriter(fos, "utf-8");
osw.write("我愛中國,我愛香港!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(null != osw) {
osw.close();
}
if(null != fos) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//字節(jié)轉(zhuǎn)字符:打印當前java類的內(nèi)容到控制臺(如果eclipse中的workspace文件編碼未改為utf-8或者測試方法所屬的java類的文件編碼沒有改為utf-8,則會亂碼)
@Test
public void byteToCharByDiskIo() {
File directory = new File("./src");
File file = new File(directory, "CodeProgram.java");
FileInputStream fis = null;
InputStreamReader isr = null;
try {
fis = new FileInputStream(file);
isr = new InputStreamReader(fis, "utf-8");
StringBuffer sb = new StringBuffer();
char[] cbuf = new char[1024];
int length = 0;
while((length = isr.read(cbuf)) != -1) {
sb.append(cbuf, 0, length);
}
System.out.println(sb.toString());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(null != isr) {
isr.close();
}
if(null != fis) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
網(wǎng)絡I/O
- URL的編解碼
- HTTP Header的編解碼
- POST表單的編解碼
- HTTP Body的編解碼
- 外部引入JS文件
- JS的URL編碼及服務端解碼
- 其他需要編碼的地方
以Tomcat服務器為例講解
- URL的編解碼
Get請求,URL的pathinfo(路徑)和Query String(參數(shù))的編碼字符集不同,瀏覽器將URL中非ASCⅡ碼字符按某種字符集轉(zhuǎn)換為16進制到字符加上%。
tomcat設置URI解碼字符集為UTF-8:<Connector URIEncoding="UTF-8" />
tomcat設置Query String解碼字符集為UTF-8:<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true" /> - HTTP Header的編解碼
默認為ISO-8859-1,且不提供編碼設置到方法,只能用URLEncoder編碼,服務端request.getHeader時,再相應到用URLEncoder解碼
- POST表單的編解碼
手動指定編碼格式, request.setCharacterEncoding(charset),post表單提交到數(shù)據(jù)就是以指定到編碼格式編碼,再調(diào)用request.getParameter(),會自動用設定到編碼去解碼。
一定要在調(diào)用request.getParameter()方法前調(diào)用request.setCharacterEncoding(charset),否則tomcat在解析前會檢測HTTP Header中到contentType,一般請求時,這個值是null,根據(jù)tomcat源碼,為空時,按默認到到字符集ISO-8859-1來編碼,導致亂碼 - HTTP Body的編解碼
請求資源成功獲取后,這些內(nèi)容將通過response返回給客戶端瀏覽器。通過response.setCharacterEncoding(charset)設置編解碼字符集,通過response Header的contentType返回給客戶端,瀏覽器根據(jù)contentType解碼,如果不存在,則根據(jù)html <meta/>中到charset來解碼,如果都不存在,則用默認到ISO-8859-1解碼
- 外部引入JS文件
如果在一個單獨到js腳本中包含中文輸出,需指定字符集<script src="" charset="gbk" />
如果被一個頁面引入到js腳本,則與外部頁面到編碼方式一致,若js文件本身到編碼格式與外部頁面編碼格式不一致,則會亂碼 - JS的URL編碼及服務端解碼
- encodeURI()
- encodeURIComponent()
除了特殊字符加英文字母不編碼加%外,其余到都編碼;后者到特殊字符范圍縮小,特別是&符號不包含在內(nèi),&符號也要編碼。所以后者常用來對一個URL傳遞一個參數(shù)值為URL的URL進行編碼。后臺對應到解碼JAVA類為URLDecoder。前端JS兩次編碼,后端request.getParameter()自動解一次碼,URLDecoder.decoder手動解一次碼。兩次編解碼避免了前后端第一次編解碼不一致的問題。
- 其他需要編碼的地方
- 數(shù)據(jù)庫連接JDBC URL傳遞characterEncoding=gbk,與數(shù)據(jù)庫內(nèi)置編碼格式要一直
- XML
- JSP