欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析Java中的編碼轉(zhuǎn)換以及編碼和解碼操作

 更新時(shí)間:2016年02月24日 08:57:58   作者:chenssy  
這篇文章主要介紹了Java中的編碼轉(zhuǎn)換以及編碼和解碼操作,文中詳細(xì)解讀了編碼解碼的相關(guān)IO操作以及內(nèi)存使用方面的知識(shí),需要的朋友可以參考下

一、Java編碼轉(zhuǎn)換過程
 我們總是用一個(gè)java類文件和用戶進(jìn)行最直接的交互(輸入、輸出),這些交互內(nèi)容包含的文字可能會(huì)包含中文。無論這些java類是與數(shù)據(jù)庫交互,還是與前端頁面交互,他們的生命周期總是這樣的:
 (1)、程序員在操作系統(tǒng)上通過編輯器編寫程序代碼并且以.java的格式保存操作系統(tǒng)中,這些文件我們稱之為源文件。
 (2)、通過JDK中的javac.exe編譯這些源文件形成.class類。
 (3)、直接運(yùn)行這些類或者部署在WEB容器中運(yùn)行,得到輸出結(jié)果。
 這些過程是從宏觀上面來觀察的,了解這個(gè)肯定是不行的,我們需要真正來了解java是如何來編碼和被解碼的:
 第一步:當(dāng)我們用編輯器編寫java源文件,程序文件在保存時(shí)會(huì)采用操作系統(tǒng)默認(rèn)的編碼格式(一般我們中文的操作系統(tǒng)采用的是GBK編碼格式)形成一個(gè).java文件。java源文件是采用操作系統(tǒng)默認(rèn)支持的file.encoding編碼格式保存的。下面代碼可以查看系統(tǒng)的file.encoding參數(shù)值。

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

 第二步:當(dāng)我們使用javac.exe編譯我們的java文件時(shí),JDK首先會(huì)確認(rèn)它的編譯參數(shù)encoding來確定源代碼字符集,如果我們不指定該編譯參數(shù),JDK首先會(huì)獲取操作系統(tǒng)默認(rèn)的file.encoding參數(shù),然后JDK就會(huì)把我們編寫的java源程序從file.encoding編碼格式轉(zhuǎn)化為JAVA內(nèi)部默認(rèn)的UNICODE格式放入內(nèi)存中。
 第三步:JDK將上面編譯好的且保存在內(nèi)存中信息寫入class文件中,形成.class文件。此時(shí).class文件是Unicode編碼的,也就是說我們常見的.class文件中的內(nèi)容無論是中文字符還是英文字符,他們都已經(jīng)轉(zhuǎn)換為Unicode編碼格式了。
 在這一步中對(duì)對(duì)JSP源文件的處理方式有點(diǎn)兒不同:WEB容器調(diào)用JSP編譯器,JSP編譯器首先會(huì)查看JSP文件是否設(shè)置了文件編碼格式,如果沒有設(shè)置則JSP編譯器會(huì)調(diào)用調(diào)用JDK采用默認(rèn)的編碼方式將JSP文件轉(zhuǎn)化為臨時(shí)的servlet類,然后再編譯為.class文件并保持到臨時(shí)文件夾中。
 第四步:運(yùn)行編譯的類:在這里會(huì)存在一下幾種情況
 (1)、直接在console上運(yùn)行。
 (2)、JSP/Servlet類。
 (3)、java類與數(shù)據(jù)庫之間。
 這三種情況每種情況的方式都會(huì)不同,
1.Console上運(yùn)行的類
 這種情況下,JVM首先會(huì)把保存在操作系統(tǒng)中的class文件讀入到內(nèi)存中,這個(gè)時(shí)候內(nèi)存中class文件編碼格式為Unicode,然后JVM運(yùn)行它。如果需要用戶輸入信息,則會(huì)采用file.encoding編碼格式對(duì)用戶輸入的信息進(jìn)行編碼同時(shí)轉(zhuǎn)換為Unicode編碼格式保存到內(nèi)存中。程序運(yùn)行后,將產(chǎn)生的結(jié)果再轉(zhuǎn)化為file.encoding格式返回給操作系統(tǒng)并輸出到界面去。整個(gè)流程如下:

201622485227284.png (775×426)

在上面整個(gè)流程中,凡是涉及的編碼轉(zhuǎn)換都不能出現(xiàn)錯(cuò)誤,否則將會(huì)產(chǎn)生亂碼。
2.Servlet類
 由于JSP文件最終也會(huì)轉(zhuǎn)換為servlet文件(只不過存儲(chǔ)的位置不同而已),所以這里我們也將JSP文件納入其中。
 當(dāng)用戶請(qǐng)求Servlet時(shí),WEB容器會(huì)調(diào)用它的JVM來運(yùn)行Servlet。首先JVM會(huì)把servlet的class加載到內(nèi)存中去,內(nèi)存中的servlet代碼是Unicode編碼格式的。然后JVM在內(nèi)存中運(yùn)行該Servlet,在運(yùn)行過程中如果需要接受從客戶端傳遞過來的數(shù)據(jù)(如表單和URL傳遞的數(shù)據(jù)),則WEB容器會(huì)接受傳入的數(shù)據(jù),在接收過程中如果程序設(shè)定了傳入?yún)?shù)的的編碼則采用設(shè)定的編碼格式,如果沒有設(shè)置則采用默認(rèn)的ISO-8859-1編碼格式,接收的數(shù)據(jù)后JVM會(huì)將這些數(shù)據(jù)進(jìn)行編碼格式轉(zhuǎn)換為Unicode并且存入到內(nèi)存中。運(yùn)行Servlet后產(chǎn)生輸出結(jié)果,同時(shí)這些輸出結(jié)果的編碼格式仍然為Unicode。緊接著WEB容器會(huì)將產(chǎn)生的Unicode編碼格式的字符串直接發(fā)送置客戶端,如果程序指定了輸出時(shí)的編碼格式,則按照指定的編碼格式輸出到瀏覽器,否則采用默認(rèn)的ISO-8859-1編碼格式。整個(gè)過程流程圖如下:

201622485254541.png (813×274)

3.數(shù)據(jù)庫部分
 我們知道java程序與數(shù)據(jù)庫的連接都是通過JDBC驅(qū)動(dòng)程序來連接的,而JDBC驅(qū)動(dòng)程序默認(rèn)的是ISO-8859-1編碼格式的,也就是說我們通過java程序向數(shù)據(jù)庫傳遞數(shù)據(jù)時(shí),JDBC首先會(huì)將Unicode編碼格式的數(shù)據(jù)轉(zhuǎn)換為ISO-8859-1的編碼格式,然后在存儲(chǔ)在數(shù)據(jù)庫中,即在數(shù)據(jù)庫保存數(shù)據(jù)時(shí),默認(rèn)格式為ISO-8859-1。

201622485346322.png (1012×570)

二、編碼&解碼
下面將結(jié)束java在那些場合需要進(jìn)行編碼和解碼操作,并詳序中間的過程,進(jìn)一步掌握java的編碼和解碼過程。在java中主要有四個(gè)場景需要進(jìn)行編碼解碼操作:
 (1):I/O操作
 (2):內(nèi)存
 (3):數(shù)據(jù)庫
 (4):javaWeb
 下面主要介紹前面兩種場景,數(shù)據(jù)庫部分只要設(shè)置正確編碼格式就不會(huì)有什么問題,javaWeb場景過多需要了解URL、get、POST的編碼,servlet的解碼,所以javaWeb場景下節(jié)LZ介紹。
1.I/O操作
 在前面LZ就提過亂碼問題無非就是轉(zhuǎn)碼過程中編碼格式的不統(tǒng)一產(chǎn)生的,比如編碼時(shí)采用UTF-8,解碼采用GBK,但最根本的原因是字符到字節(jié)或者字節(jié)到字符的轉(zhuǎn)換出問題了,而這中情況的轉(zhuǎn)換最主要的場景就是I/O操作的時(shí)候。當(dāng)然I/O操作主要包括網(wǎng)絡(luò)I/O(也就是javaWeb)和磁盤I/O。網(wǎng)絡(luò)I/O下節(jié)介紹。
 首先我們先看I/O的編碼操作。

201622485412859.png (637×393)

InputStream為字節(jié)輸入流的所有類的超類,Reader為讀取字符流的抽象類。java讀取文件的方式分為按字節(jié)流讀取和按字符流讀取,其中InputStream、Reader是這兩種讀取方式的超類。
 按字節(jié)
 我們一般都是使用InputStream.read()方法在數(shù)據(jù)流中讀取字節(jié)(read()每次都只讀取一個(gè)字節(jié),效率非常慢,我們一般都是使用read(byte[])),然后保存在一個(gè)byte[]數(shù)組中,最后轉(zhuǎn)換為String。在我們讀取文件時(shí),讀取字節(jié)的編碼取決于文件所使用的編碼格式,而在轉(zhuǎn)換為String過程中也會(huì)涉及到編碼的問題,如果兩者之間的編碼格式不同可能會(huì)出現(xiàn)問題。例如存在一個(gè)問題test.txt編碼格式為UTF-8,那么通過字節(jié)流讀取文件時(shí)所獲得的數(shù)據(jù)流編碼格式就是UTF-8,而我們?cè)谵D(zhuǎn)化成String過程中如果不指定編碼格式,則默認(rèn)使用系統(tǒng)編碼格式(GBK)來解碼操作,由于兩者編碼格式不一致,那么在構(gòu)造String過程肯定會(huì)產(chǎn)生亂碼,如下:

File file = new File("C:\\test.txt"); 
InputStream input = new FileInputStream(file); 
StringBuffer buffer = new StringBuffer(); 
byte[] bytes = new byte[1024]; 
for(int n ; (n = input.read(bytes))!=-1 ; ){ 
 buffer.append(new String(bytes,0,n)); 
} 
System.out.println(buffer); 

輸出結(jié)果為亂碼....
test.txt中的內(nèi)容為:我是 cm。
 要想不出現(xiàn)亂碼,在構(gòu)造String過程中指定編碼格式,使得編碼解碼時(shí)兩者編碼格式保持一致即可:

buffer.append(new String(bytes,0,n,"UTF-8")); 

 按字符
 其實(shí)字符流可以看做是一種包裝流,它的底層還是采用字節(jié)流來讀取字節(jié),然后它使用指定的編碼方式將讀取字節(jié)解碼為字符。在java中Reader是讀取字符流的超類。所以從底層上來看按字節(jié)讀取文件和按字符讀取沒什么區(qū)別。在讀取的時(shí)候字符讀取每次是讀取留個(gè)字節(jié),字節(jié)流每次讀取一個(gè)字節(jié)。
 字節(jié)&字符轉(zhuǎn)換
 字節(jié)轉(zhuǎn)換為字符一定少不了InputStreamReader。API解釋如下:InputStreamReader 是字節(jié)流通向字符流的橋梁:它使用指定的 charset 讀取字節(jié)并將其解碼為字符。它使用的字符集可以由名稱指定或顯式給定,或者可以接受平臺(tái)默認(rèn)的字符集。 每次調(diào)用 InputStreamReader 中的一個(gè) read() 方法都會(huì)導(dǎo)致從底層輸入流讀取一個(gè)或多個(gè)字節(jié)。要啟用從字節(jié)到字符的有效轉(zhuǎn)換,可以提前從底層流讀取更多的字節(jié),使其超過滿足當(dāng)前讀取操作所需的字節(jié)。API解釋非常清楚,InputStreamReader在底層讀取文件時(shí)仍然采用字節(jié)讀取,讀取字節(jié)后它需要根據(jù)一個(gè)指定的編碼格式來解析為字符,如果沒有指定編碼格式則采用系統(tǒng)默認(rèn)編碼格式。

String file = "C:\\test.txt"; 
   String charset = "UTF-8"; 
   // 寫字符換轉(zhuǎn)成字節(jié)流 
   FileOutputStream outputStream = new FileOutputStream(file); 
   OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset); 
   try { 
   writer.write("我是 cm"); 
   } finally { 
   writer.close(); 
   } 
   
   // 讀取字節(jié)轉(zhuǎn)換成字符 
   FileInputStream inputStream = new FileInputStream(file); 
   InputStreamReader reader = new InputStreamReader( 
   inputStream, charset); 
   StringBuffer buffer = new StringBuffer(); 
   char[] buf = new char[64]; 
   int count = 0; 
   try { 
   while ((count = reader.read(buf)) != -1) { 
    buffer.append(buf, 0, count); 
   } 
   } finally { 
   reader.close(); 
   } 
   System.out.println(buffer); 

2.內(nèi)存
 首先我們看下面這段簡單的代碼

String s = "我是 cm"; 
byte[] bytes = s.getBytes(); 
String s1 = new String(bytes,"GBK"); 
String s2 = new String(bytes); 

 在這段代碼中我們看到了三處編碼轉(zhuǎn)換過程(一次編碼,兩次解碼)。先看String.getTytes():

public byte[] getBytes() { 
  return StringCoding.encode(value, 0, value.length); 
 } 

 內(nèi)部調(diào)用StringCoding.encode()方法操作:

static byte[] encode(char[] ca, int off, int len) { 
  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); 
  } 
  try { 
   return encode("ISO-8859-1", ca, off, len); 
  } catch (UnsupportedEncodingException x) { 
   // If this code is hit during VM initialization, MessageUtils is 
   // the only way we will be able to get any kind of error message. 
   MessageUtils.err("ISO-8859-1 charset not available: " 
        + x.toString()); 
   // If we can not find ISO-8859-1 (a required encoding) then things 
   // are seriously wrong with the installation. 
   System.exit(1); 
   return null; 
  } 
 } 

 encode(char[] paramArrayOfChar, int paramInt1, int paramInt2)方法首先調(diào)用系統(tǒng)的默認(rèn)編碼格式,如果沒有指定編碼格式則默認(rèn)使用ISO-8859-1編碼格式進(jìn)行編碼操作,進(jìn)一步深入如下:

String csn = (charsetName == null) ? "ISO-8859-1" : charsetName; 

 同樣的方法可以看到new String 的構(gòu)造函數(shù)內(nèi)部是調(diào)用StringCoding.decode()方法:

public String(byte bytes[], int offset, int length, Charset charset) { 
  if (charset == null) 
   throw new NullPointerException("charset"); 
  checkBounds(bytes, offset, length); 
  this.value = StringCoding.decode(charset, bytes, offset, length); 
 } 

 decode方法和encode對(duì)編碼格式的處理是一樣的。
 對(duì)于以上兩種情況我們只需要設(shè)置統(tǒng)一的編碼格式一般都不會(huì)產(chǎn)生亂碼問題。
3.編碼&編碼格式
 首先先看看java編碼類圖

201622485554674.jpg (576×243)

首先根據(jù)指定的chart設(shè)置ChartSet類,然后根據(jù)ChartSet創(chuàng)建ChartSetEncoder對(duì)象,最后再調(diào)用 CharsetEncoder.encode 對(duì)字符串進(jìn)行編碼,不同的編碼類型都會(huì)對(duì)應(yīng)到一個(gè)類中,實(shí)際的編碼過程是在這些類中完成的。下面時(shí)序圖展示詳細(xì)的編碼過程:

201622485616131.jpg (577×410)

通過這編碼的類圖和時(shí)序圖可以了解編碼的詳細(xì)過程。下面將通過一段簡單的代碼對(duì)ISO-8859-1、GBK、UTF-8編碼

public class Test02 { 
 public static void main(String[] args) throws UnsupportedEncodingException { 
  String string = "我是 cm"; 
  Test02.printChart(string.toCharArray()); 
  Test02.printChart(string.getBytes("ISO-8859-1")); 
  Test02.printChart(string.getBytes("GBK")); 
  Test02.printChart(string.getBytes("UTF-8")); 
 } 
  
 /** 
  * char轉(zhuǎn)換為16進(jìn)制 
  */ 
 public static void printChart(char[] chars){ 
  for(int i = 0 ; i < chars.length ; i++){ 
   System.out.print(Integer.toHexString(chars[i]) + " "); 
  } 
  System.out.println(""); 
 } 
  
 /** 
  * byte轉(zhuǎn)換為16進(jìn)制 
  */ 
 public static void printChart(byte[] bytes){ 
  for(int i = 0 ; i < bytes.length ; i++){ 
   String hex = Integer.toHexString(bytes[i] & 0xFF); 
    if (hex.length() == 1) { 
    hex = '0' + hex; 
    } 
    System.out.print(hex.toUpperCase() + " "); 
  } 
  System.out.println(""); 
 } 
} 

輸出:

6211 662f 20 63 6d 
3F 3F 20 63 6D 
CE D2 CA C7 20 63 6D 
E6 88 91 E6 98 AF 20 63 6D 

 通過程序我們可以看到“我是 cm”的結(jié)果為:

 char[]:6211 662f 20 63 6d
 ISO-8859-1:3F 3F 20 63 6D 
 GBK:CE D2 CA C7 20 63 6D 
 UTF-8:E6 88 91 E6 98 AF 20 63 6D

 圖如下:

201622485708131.png (693×549)

相關(guān)文章

  • list的4種遍歷方式(實(shí)例講解)

    list的4種遍歷方式(實(shí)例講解)

    下面小編就為大家?guī)硪黄猯ist的4種遍歷方式(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-08-08
  • 解決mybatis-plus自定義xml的坑

    解決mybatis-plus自定義xml的坑

    這篇文章主要介紹了解決mybatis-plus自定義xml的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • java實(shí)現(xiàn)支付寶支付接口的調(diào)用

    java實(shí)現(xiàn)支付寶支付接口的調(diào)用

    本文主要介紹了java實(shí)現(xiàn)支付寶支付接口的調(diào)用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • ManyToMany單向、雙向:@JoinTable的使用

    ManyToMany單向、雙向:@JoinTable的使用

    這篇文章主要介紹了ManyToMany單向、雙向:@JoinTable的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Hibernate懶加載之<class>標(biāo)簽上的lazy

    Hibernate懶加載之<class>標(biāo)簽上的lazy

    這篇文章主要介紹了Hibernate懶加載之<class>標(biāo)簽上的lazy,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • Spring?Security?OAuth?Client配置加載源碼解析

    Spring?Security?OAuth?Client配置加載源碼解析

    這篇文章主要為大家介紹了Spring?Security?OAuth?Client配置加載源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • springboot調(diào)用支付寶第三方接口(沙箱環(huán)境)

    springboot調(diào)用支付寶第三方接口(沙箱環(huán)境)

    這篇文章主要介紹了springboot+調(diào)用支付寶第三方接口(沙箱環(huán)境),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • SpringBoot跨域問題的解決方法實(shí)例

    SpringBoot跨域問題的解決方法實(shí)例

    這篇文章主要給大家介紹了關(guān)于SpringBoot跨域問題的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • Java判斷List中有無重復(fù)元素的方法

    Java判斷List中有無重復(fù)元素的方法

    今天小編就為大家分享一篇Java判斷List中有無重復(fù)元素的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • Maven中dependency和plugins的繼承與約束

    Maven中dependency和plugins的繼承與約束

    這篇文章主要介紹了Maven中dependency和plugins的繼承與約束,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12

最新評(píng)論