整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          一個WEB請求中涉及到的編解碼

          一個WEB請求中涉及到的編解碼

          者:君山,出處已不可考,我看到的都是轉的,但是確實是好文

          Java Web 涉及到的編碼

          用戶從瀏覽器端發起一個 HTTP 請求,需要存在編碼的地方是 URL、Cookie、Parameter。服務器端接受到 HTTP 請求后要解析 HTTP 協議,其中 URI、Cookie 和 POST 表單參數需要解碼,服務器端可能還需要讀取數據庫中的數據,本地或網絡中其它地方的文本文件,這些數據都可能存在編碼問題,當 Servlet 處理完所有請求的數據后,需要將這些數據再編碼通過 Socket 發送到用戶請求的瀏覽器里,再經過瀏覽器解碼成為文本。這些過程如下圖所示:

          圖 3. 一次 HTTP 請求的編碼示例(查看大圖)

          如上圖所示一次 HTTP 請求設計到很多地方需要編解碼,它們編解碼的規則是什么?下面將會重點闡述一下:

          URL 的編解碼

          用戶提交一個 URL,這個 URL 中可能存在中文,因此需要編碼,如何對這個 URL 進行編碼?根據什么規則來編碼?有如何來解碼?如下圖一個 URL:

          圖 4.URL 的幾個組成部分

          上圖中以 Tomcat 作為 Servlet Engine 為例,它們分別對應到下面這些配置文件中:

          Port 對應在 Tomcat 的 <Connector port="8080"/> 中配置,而 Context Path 在 <Context path="/examples"/> 中配置,Servlet Path 在 Web 應用的 web.xml 中的

           <servlet-mapping> 
           <servlet-name>junshanExample</servlet-name> 
           <url-pattern>/servlets/servlet/*</url-pattern> 
           </servlet-mapping> 
          

          <url-pattern> 中配置,PathInfo 是我們請求的具體的 Servlet,QueryString 是要傳遞的參數,注意這里是在瀏覽器里直接輸入 URL 所以是通過 Get 方法請求的,如果是 POST 方法請求的話,QueryString 將通過表單方式提交到服務器端,這個將在后面再介紹。

          上圖中 PathInfo 和 QueryString 出現了中文,當我們在瀏覽器中直接輸入這個 URL 時,在瀏覽器端和服務端會如何編碼和解析這個 URL 呢?為了驗證瀏覽器是怎么編碼 URL 的我們選擇 FireFox 瀏覽器并通過 HTTPFox 插件觀察我們請求的 URL 的實際的內容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author=君山在中文 FireFox3.6.12 的測試結果

          圖 5. HTTPFox 的測試結果

          君山的編碼結果分別是:e5 90 9b e5 b1 b1,be fd c9 bd,查閱上一屆的編碼可知,PathInfo 是 UTF-8 編碼而 QueryString 是經過 GBK 編碼,至于為什么會有“%”?查閱 URL 的編碼規范 RFC3986 可知瀏覽器編碼 URL 是將非 ASCII 字符按照某種編碼格式編碼成 16 進制數字然后將每個 16 進制表示的字節前加上“%”,所以最終的 URL 就成了上圖的格式了。

          默認情況下中文 IE 最終的編碼結果也是一樣的,不過 IE 瀏覽器可以修改 URL 的編碼格式在選項 -> 高級 -> 國際里面的發送 UTF-8 URL 選項可以取消。

          從上面測試結果可知瀏覽器對 PathInfo 和 QueryString 的編碼是不一樣的,不同瀏覽器對 PathInfo 也可能不一樣,這就對服務器的解碼造成很大的困難,下面我們以 Tomcat 為例看一下,Tomcat 接受到這個 URL 是如何解碼的。

          解析請求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,這個方法把傳過來的 URL 的 byte[] 設置到 org.apache.coyote.Request 的相應的屬性中。這里的 URL 仍然是 byte 格式,轉成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:

           1 protected void convertURI(MessageBytes uri, Request request) 
           2 throws Exception { 
           3 ByteChunk bc=uri.getByteChunk(); 
           4 int length=bc.getLength(); 
           5 CharChunk cc=uri.getCharChunk(); 
           6 cc.allocate(length, -1); 
           7 String enc=connector.getURIEncoding(); 
           8 if (enc !=null) { 
           9 B2CConverter conv=request.getURIConverter(); 
          10 try { 
          11 if (conv==null) { 
          12 conv=new B2CConverter(enc); 
          13 request.setURIConverter(conv); 
          14 } 
          15 } catch (IOException e) {...} 
          16 if (conv !=null) { 
          17 try { 
          18 conv.convert(bc, cc, cc.getBuffer().length - 
          19 cc.getEnd()); 
          20 uri.setChars(cc.getBuffer(), cc.getStart(), 
          21 cc.getLength()); 
          22 return; 
          23 } catch (IOException e) {...} 
          24 } 
          25 } 
          26 // Default encoding: fast conversion 
          27 byte[] bbuf=bc.getBuffer(); 
          28 char[] cbuf=cc.getBuffer(); 
          29 int start=bc.getStart(); 
          30 for (int i=0; i < length; i++) { 
          31 cbuf[i]=(char) (bbuf[i + start] & 0xff); 
          32 } 
          33 uri.setChars(cbuf, 0, length); 
          34 } 
          

          從上面的代碼中可以知道對 URL 的 URI 部分進行解碼的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定義的,如果沒有定義,那么將以默認編碼 ISO-8859-1 解析。所以如果有中文 URL 時最好把 URIEncoding 設置成 UTF-8 編碼。

          QueryString 又如何解析? GET 方式 HTTP 請求的 QueryString 與 POST 方式 HTTP 請求的表單參數都是作為 Parameters 保存,都是通過 request.getParameter 獲取參數值。對它們的解碼是在 request.getParameter 方法第一次被調用時進行的。request.getParameter 方法被調用時將會調用 org.apache.catalina.connector.Request 的 parseParameters 方法。這個方法將會對 GET 和 POST 方式傳遞的參數進行解碼,但是它們的解碼字符集有可能不一樣。POST 表單的解碼將在后面介紹,QueryString 的解碼字符集是在哪定義的呢?它本身是通過 HTTP 的 Header 傳到服務端的,并且也在 URL 中,是否和 URI 的解碼字符集一樣呢?從前面瀏覽器對 PathInfo 和 QueryString 的編碼采取不同的編碼格式不同可以猜測到解碼字符集肯定也不會是一致的。的確是這樣 QueryString 的解碼字符集要么是 Header 中 ContentType 中定義的 Charset 要么就是默認的 ISO-8859-1,要使用 ContentType 中定義的編碼就要設置 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 設置為 true。這個配置項的名字有點讓人產生混淆,它并不是對整個 URI 都采用 BodyEncoding 進行解碼而僅僅是對 QueryString 使用 BodyEncoding 解碼,這一點還要特別注意。

          從上面的 URL 編碼和解碼過程來看,比較復雜,而且編碼和解碼并不是我們在應用程序中能完全控制的,所以在我們的應用程序中應該盡量避免在 URL 中使用非 ASCII 字符,不然很可能會碰到亂碼問題,當然在我們的服務器端最好設置 <Connector/> 中的 URIEncoding 和 useBodyEncodingForURI 兩個參數。

          HTTP Header 的編解碼

          當客戶端發起一個 HTTP 請求除了上面的 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 不同,它是通過 HTTP 的 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 來解碼。如果也沒有定義的話,那么瀏覽器將使用默認的編碼來解碼。

          其它需要編碼的地方

          除了 URL 和參數編碼問題外,在服務端還有很多地方可能存在編碼,如可能需要讀取 xml、velocity 模版引擎、JSP 或者從數據庫讀取數據等。

          xml 文件可以通過設置頭來制定編碼格式

           <?xml version="1.0" encoding="UTF-8"?> 
          

          Velocity 模版設置編碼格式:

          services.VelocityService.input.encoding=UTF-8 
          

          JSP 設置編碼格式:

           <%@page contentType="text/html; charset=UTF-8"%> 
          

          訪問數據庫都是通過客戶端 JDBC 驅動來完成,用 JDBC 來存取數據要和數據的內置編碼保持一致,可以通過設置 JDBC URL 來制定如

          因為中文的博大精深,以及早期文件編碼的不統一,造成了現在可能碰到的文件編碼有GB2312、GBk、GB18030、UTF-8、BIG5等。因為編解碼的知識比較底層和冷門,一直以來我對這幾個編碼的認知也很膚淺,很多時候也會疑惑編碼名到底是大寫還是小寫,英文和數字之間是不是需要加“-”,規則到底是誰定的等等。

          我膚淺的認知如下:

          編碼說明GB2312最早的簡體中文編碼,還有海外版的HZ-GB-2312BIG5繁體中文編碼,主要用于臺灣地區。些繁體中文游戲亂碼,其實都是因為BIG5編碼和GB2312編碼的錯誤使用導致GBK簡體+繁體,我就當它是GB2312+BIG5,非國家標準,只是中文環境內基本都遵守。后來了解到,K居然是“擴展”的拼音首字母,這很中國。。。GB18030GB家族的新版,向下兼容,最新國家標準,現在中文軟件都理應支持的編碼格式,文件解碼的新選擇UTF-8不解釋了,國際化編碼標準,html現在最標準的編碼格式。

          概念梳理

          經過長時間的踩坑,我終于對這類知識有了一定的認知,現在把一些重要概念重新整理如下:

          首先要消化整個字符編解碼知識,先要明確兩個概念——字符集和字符編碼。

          字符集

          顧名思義就是字符的集合,不同的字符集最直觀的區別就是字符數量不相同,常見的字符集有ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。

          字符編碼

          字符編碼決定了字符集到實際二進制字節的映射方式,每一種字符編碼都有自己的設計規則,例如是固定字節數還是可變長度,此處不一一展開。

          常提到的GB2312、BIG5、UTF-8等,如果未特殊說明,一般語義上指的是字符編碼而不是字符集。

          字符集和字符編碼是一對多的關系,同一字符集可以存在多個字符編碼,典型代表是Unicode字符集下有UTF-8、UTF-16等等。

          BOM(Byte Order Mark)

          當使用windows記事本保存文件的時候,編碼方式可以選擇ANSI(通過locale判斷,簡體中文系統下是GB家族)、Unicode、Utf-8等。

          為了清晰概念,需要指出此處的Unicode,編碼方式其實是UTF-16LE。

          有這么多編碼方式,那文件打開的時候,windows系統是如何判斷該使用哪種編碼方式呢?

          答案是:windows(例如:簡體中文系統)在文件頭部增加了幾個字節以表示編碼方式,三個字節(0xef, 0xbb, 0xbf)表示UTF-8;兩個字節(0xff, 0xfe或者0xfe, 0xff)表示UTF-16(Unicode);無表示GB**。

          值得注意的是,由于BOM不表意,在解析文件內容的時候應該舍棄,不然會造成解析出來的內容頭部有多余的內容。

          LE(little-endian)和BE(big-endian)

          這個涉及到字節相關的知識了,不是本文重點,不過提到了就順帶解釋下。LE和BE代表字節序,分別表示字節從低位/高位開始。

          我們常接觸到的CPU都是LE,所以windows里Unicode未指明字節序時默認指的是LE。

          node的Buffer API中基本都有相應的2種函數來處理LE、BE,貼個文檔如下:

          const buf=Buffer.from([0, 5]);
          // Prints: 5
          console.log(buf.readInt16BE());
          // Prints: 1280
          console.log(buf.readInt16LE());
          

          Node解碼

          我第一次接觸到該類問題,使用的是node處理,當時給我的選擇有:

          • node-iconv(系統iconv的封裝)
          • iconv-lite(純js)

          由于node-iconv涉及node-gyp的build,而開發機是windows,node-gyp的環境準備以及后續的一系列安裝和構建,讓我這樣的web開發人員痛(瘋)不(狂)欲(吐)生(嘈),最后自然而然的選擇了iconv-lite。

          解碼的處理大致示意如下:

          const fs=require('fs')
          const iconv=require('iconv-lite')
          const buf=fs.readFileSync('/path/to/file')
          // 可以先截取前幾個字節來判斷是否存在BOM
          buf.slice(0, 3).equals(Buffer.from([0xef, 0xbb, 0xbf])) // UTF-8
          buf.slice(0, 2).equals(Buffer.from([0xff, 0xfe])) // UTF-16LE
          const str=iconv.decode(buf, 'gbk')
          // 解碼正確的判斷需要根據業務場景調整
          // 此處截取前幾個字符判斷是否有中文存在來確定是否解碼正確
          // 也可以反向判斷是否有亂碼存在來確定是否解碼正確
          // 正則表達式內常見的\u**就是unicode碼點
          // 該區間是常見字符,如果有特定場景可以根據實際情況擴大碼點區間
          /[\u4e00-\u9fa5]/.test(str.slice(0, 3))
          

          前端解碼

          隨著ES20151的瀏覽器實現越來越普及,前端編解碼也成為了可能。以前通過form表單上傳文件至后端解析內容的流程現在基本可以完全由前端處理,既少了與后端的網絡交互,而且因為有界面反饋,用戶體驗上更直觀。

          一般場景如下:

          const file=document.querySelector('.input-file').files[0]
          const reader=new FileReader()
          reader.onload=()=> {
          	const content=reader.result
          }
          reader.onprogerss=evt=> {
          	// 讀取進度
          }
          reader.readAsText(file, 'utf-8') // encoding可修改
          

          fileReader支持的encoding列表,可查閱此處。

          這里有一個比較有趣的現象,如果文件包含BOM,比如聲明是UTF-8編碼,那指定的encoding會無效,而且在輸出的內容中會去掉BOM部分,使用起來更方便。

          如果對編碼有更高要求的控制需求,可以轉為輸出TypedArray:

          reader.onload=()=> {
          	const buf=new Uint8Array(reader.result)
          	// 進行更細粒度的操作
          }
          reader.readAsArrayBuffer(file)
          

          獲取文本內容的數據緩沖以后,可以調用TextDecoder繼續解碼,不過需要注意的是獲得的TypedArray是包含BOM的:

          const decoder=new TextDecoder('gbk')
          const content=decoder.decode(buf)
          

          如果文件比較大,可以使用Blob的slice來進行切割:

          const file=document.querySelector('.input-file').files[0]
          const blob=file.slice(0, 1024)
          

          文件的換行不同操作系統不一致,如果需要逐行解析,需要視場景而定:

          • Linux: \n
          • Windows: \r\n
          • Mac OS: \r

          **注意:**這個是各系統默認文本編輯器的規則,如果是使用其他軟件,比如常用的sublime、vscode、excel等等,都是可以自行設置換行符的,一般是\n或者\r\n。

          前端編碼

          可以使用TextEncoder將字符串內容轉換成TypedBuffer:

          const encoder=new TextEncoder()
          encoder.encode(String)
          

          值得注意的是,從Chrome 53開始,encoder只支持utf-8編碼2,官方理由是其他編碼用的太少了。這里有個polyfill庫,補充了移除的編碼格式。

          前端生成文件

          前端編碼完成后,一般都會順勢實現文件生成,示例代碼如下:

          const a=document.createElement('a')
          const buf=new TextEncoder()
          const blob=new Blob([buf.encode('我是文本')], {
          	type: 'text/plain'
          })
          a.download='file'
          a.href=URL.createObjectURL(blob)
          a.click()
          // 主動調用釋放內存
          URL.revokeObjectURL(blob)
          

          這樣就會生成一個文件名為file的文件,后綴由type決定。如果需要導出csv,那只需要修改對應的MIME type:

          const blob=new Blob([buf.encode('第一行,1\r\n第二行,2')], {
          	type: 'text/csv'
          })
          

          一般csv都默認是由excel打開的,這時候會發現第一列的內容都是亂碼,因為excel沿用了windows判斷編碼的邏輯(上文提到),當發現無BOM時,采用GB18030編碼進行解碼而導致內容亂碼。

          這時候只需要加上BOM指明編碼格式即可:

          const blob=new Blob([new Uint8Array([0xef, 0xbb, 0xbf]), buf.encode('第一行,1\r\n第二行,2')], {
          	type: 'text/csv'
          })
          // or
          const blob=new Blob([buf.encode('\ufeff第一行,1\r\n第二行,2')], {
          	type: 'text/csv'
          })
          

          這里稍微說明下,因為UTF-8和UTF-16LE都屬于Unicode字符集,只是實現不同。所以通過一定的規則,兩種編碼可以相互轉換,而表明UTF-16LE的BOM轉成UTF-8編碼其實就是表明UTF-8的BOM。

          希望本文能幫助到您!

          點贊+轉發,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓-_-)

          關注 {我},享受文章首發體驗!

          每周重點攻克一個前端技術難點。更多精彩前端內容私信 我 回復“教程”

          原文鏈接:https://github.com/ProtoTeam/blog/blob/master/201712/3.md

          作者:螞蟻金服—數據體驗技術團隊

          們來看看在Java 7/8中字符集編碼和解碼的性能。先看看下面兩個String方法在不同字符集下的性能:

          /* String to byte[] */

          public byte[] getBytes(Charset charset);

          /* byte[] to String */

          public String(byte bytes[], Charset charset);

          我把“Develop with pleasure”通過谷歌翻譯為德語、俄語、日語和繁體中文。我們將根據這些短語構建指定大小的塊,通過使用“\n”作為分隔符來連接它們直到到達指定的長度(在大多數情況下,結果會稍長一些)。在那之后我們將100M字符的byte[]數據轉化為String數據(100M是Java中char字符的總長度)。我們將轉換10遍以確保結果更加可靠(因此,在下表中是轉換10億字符的時間)。

          我們將使用2個塊的大?。?00個字符用于測試短字符串轉換的性能,100M字符用來測試最初的轉換性能,你可以在本文末尾找到文章的源代碼。我們會用UTF-8的方式與“本地化的”字符集進行比較(英語US-ASCII、德語ISO-8859-1、俄語windows-1251、日語Shift_JIS、繁體中文GB18030),將UTF-8作為通用編碼時這些信息會非常很有(通常意味著更大的二進制轉換開銷)。我們也會對比Java 7u51和Java 8(的版本特性)。為了避免GC帶來的影響,所有測試都是在我搭載Xmx32G的Xeon-2650(2.8Ghz)工作站上運行。

          以下是測試結果。每個實例有兩個時間結果:Java7的時間(和Java8的時間)。”UTF-8″這一行遵循了每個“本地化的”字符集,它包含從前一行數據的轉換時間(例如,最后一行包括了string從繁體中文轉為UTF-8的編碼、解碼的時間)。

          CharsetgetBytes, ~100 chars (chunk size)new String, ~100 chars (chunk size)getBytes, ~100M charsnew String, ~100M chars
          US-ASCII2.451 sec(2.686 sec)0.981 sec(0.971 sec)2.402 sec(2.421 sec)0.889 sec(0.903 sec)
          UTF-81.193 sec(1.259 sec)0.974 sec(1.181 sec)1.226 sec(1.245 sec)0.887 sec(1.09 sec)
          ISO-8859-12.42 sec(0.334 sec)0.816 sec(0.84 sec)2.441 sec(0.355 sec)0.761 sec(0.801 sec)
          UTF-83.14 sec(3.534 sec)3.373 sec(4.134 sec)3.288 sec(3.498 sec)3.314 sec(4.185 sec)
          windows-12515.85 sec(5.826 sec)2.004 sec(1.909 sec)5.881 sec(5.747 sec)1.902 sec(1.87 sec)
          UTF-85.425 sec(5.256 sec)11.561 sec(12.326 sec)5.544 sec(4.921 sec)11.29 sec(12.314 sec)
          Shift_JIS17.343 sec(9.355 sec)24.85 sec(8.464 sec)16.95 sec(9.24 sec)24.6 sec(8.503 sec)
          UTF-89.398 sec(13.201 sec)12.007 sec(16.661 sec)9.681 sec(11.801 sec)12.035 sec(16.602 sec)
          GB1803018.754 sec(16.641 sec)15.877 sec(16.267 sec)18.494 sec(16.342 sec)16.034 sec(16.406 sec)
          UTF-89.374 sec(11.829 sec)12.092 sec(16.672 sec)9.678 sec(12.991 sec)12.25 sec(16.745 sec)

          測試結果

          我們可以注意到以下事實:

          • 這里幾乎沒有CPU開銷的分塊輸出——如果你為這個測試分配更少的內存,那么分塊結果將變得更糟。

          • 如果是單字節字符集,那么將byte[]轉換為String將非常快(US-ASCII、ISO-8859-1和windows-1251):一旦知道輸入數據的大小,那么就可以分配結果中char[]的合適大小。同時,如果是在java.lang包中,可以使用一個受保護的String構造函數,這并不需要char[]的拷貝。

          • 同時,String.getBytes(UTF-8)對于non-ASCII編碼不能高效地工作——包括更復雜的映射,它分配了最大可能的char[]輸出,然后復制實際使用的部分給String的返回結果。UTF-8轉換中文/日文的速度確實非常慢。

          • 如果是“本地化的”字符集,String -> byte[]的轉換效率通常是低于byte[] -> String的。出人意料的是,在使用UTF-8時會觀察到相反的結果:String -> byte[]普遍快于byte[] -> String。

          • Shift_JIS和ISO-8859-1的轉換(可能也包括一些其它字符集)在Java 8中進行了極大的優化(綠色高亮):相比Java 7,Java8對日語轉換的速度要快2-3倍。在ISO-8859-1的情況下,只有String -> byte[]進行了優化——它的運行速度比現在要快七倍!這個結果聽起來確實令我吃驚(請接著往下看)。

          • 一個更加明顯的區別是:byte[] -> String對于windows-1251與UTF-8編碼轉換時間的比較(紅色高亮)。它們大約相差六倍(windows-1251比UTF-8快六倍)。我不確定是否有可能證明它只是由不同的二進制表示:如果使用windows-1251,每個字符你需要1個字節的消耗;而如果使用UTF-8,對于俄語字符集則是每個字符兩個字節。ISO-8859-1和UTF-8之間是有大同小異的地方的(藍色高亮): 在德語字符串中只有一個字符不需要用2個UTF-8字符表示。而在俄語字符串中,(除空格外)幾乎每個字符都需要2個UTF-8字符。

          直接由 String->byte[]->String 轉換為 ASCII / ISO-8859-1 數據

          我嘗試過研究Java 8中的ISO-8859-1編碼器的表現。其算法本身非常簡單,ISO-8859-1字符集完全匹配Unicode表中前255個字符的位置,所以看起來像下面這樣:

          if ( char <=255 )

          write it as byte to output

          else

          skip input char, write Charset.replacement byte

          Java 7 和 8中ISO_8859_1.java的不同之處,Java 7在單一方法中包含了各種優先權編碼邏輯,但是Java 8提供了幫助方法(Helper Method)。當沒有字符大于255時,將輸入的char[]進行轉換。我認為這種方法使得JIT產生更多高效的代碼。

          眾所周知,US-ASCII或者ISO-8859-1的編碼器優于JDK編碼器。只需要假設字符串僅包含有效的字符編碼并且避免所有的“管道(plumbing)”:

          private static byte[] toAsciiBytes( final String str )

          {

          final byte[] res=new byte[ str.length() ];

          for (int i=0; i < str.length(); i++)

          res[ i ]=(byte) str.charAt( i );

          return res;

          }

          這種方式取代了Java 8中20-25%的ISO-8859-1編碼器,同時效率是Java 7的3到3.5倍。然而,它依賴JIT來進行數據訪問和String.charAt的邊界檢查。

          對于這兩個數據集,取代byte[] -> String轉換幾乎是不可能的。因為沒有公共的String構造函數或工廠方法,這將使用你提供的char[]類型。它們都進行了保護性的備份(否則將無法保證String的不變性)。性能方面最接近的是一個被棄用的String(byte ascii[], int hibyte, int offset, int count)構造函數。如果你的字符集匹配的是一個255字節的Unicode(US-ASCII, ISO-8859-1),那么對于byte[]->String編碼器而言是非常有用的。不幸的是,這個構造函數從字符串結尾開始復制數據,并不像CPU緩存那么友好。

          private static String asciiBytesToString( final byte[] ascii )

          {

          //deprecated constructor allowing data to be copied directly into String char[]. So convenient...

          return new String( ascii, 0 );

          }

          另一方面,String(byte bytes[],int offset, int length, Charset charset)減少了所有可能的邊界類型(edge):對于US-ASCII和ISO-8859-1,它分配了char[]所需的大小,進行一次低成本轉換(使byte變為 char)同時提供char[]轉為String構造函數的結果,在這種情況下就要信任編碼器了。

          總結

          • 首選windows-1252或者Shift_JIS這樣的本地字符集,其次才是UTF-8:(一般來說)它們生產更緊湊的二進制數據,并且速度比編、解碼更快(在Java 7中有一些例外,但在Java 8中成為了一條規則)。

          • ISO-8859-1在Java 7和8中總是快于US-ASCII:如果你沒有充足的理由使用US-ASCII,請選擇ISO-8859-1。

          • 你可以寫一個非常快速的String->byte[]進行US-ASCII/ISO-8859-1的轉換,但是你并不能取代Java解碼器——它們直接訪問并創建String輸出。

          架構師視頻資料分享鏈接:

          data:text/html;charset=UTF-8;base64,

          5p625p6E5biI5a2m5Lmg5Lqk5rWB576k5Y+35pivNTc1NzUxODU0Cg==

          復制粘貼在網站即可!


          主站蜘蛛池模板: 国产精品无码一区二区三区免费| 国产精品成人一区二区三区| 国产精品久久无码一区二区三区网 | 香蕉在线精品一区二区| 一区五十路在线中出| AV无码精品一区二区三区宅噜噜| 精品视频一区二区三区在线播放| 国产成人无码一区二区三区在线| 国产日韩一区二区三区| 中文字幕日本一区| 午夜精品一区二区三区在线视| 亚洲电影唐人社一区二区| 丝袜人妻一区二区三区网站| 国产产一区二区三区久久毛片国语 | 一区 二区 三区 中文字幕| 日韩一区二区在线播放| 久久精品国产一区二区电影| 精品国产一区二区三区色欲| 糖心vlog精品一区二区三区| 偷拍精品视频一区二区三区 | 蜜臀AV无码一区二区三区| 国产一区二区三区手机在线观看| 色天使亚洲综合一区二区| 无码人妻精品一区二区在线视频 | 亲子乱av一区区三区40岁| 国产美女精品一区二区三区| 久久久国产精品无码一区二区三区| 亚洲成av人片一区二区三区| 伊人色综合视频一区二区三区| 乱色熟女综合一区二区三区| 日韩精品无码一区二区三区| 国产一区中文字幕在线观看| 亚洲欧洲日韩国产一区二区三区| 日韩伦理一区二区| 中文字幕日韩精品一区二区三区| 熟女大屁股白浆一区二区| 国产91精品一区二区麻豆亚洲| 日本韩国一区二区三区| 精品国产乱子伦一区二区三区| 三上悠亚精品一区二区久久| 精品国产亚洲一区二区在线观看 |