2012
Feb
22

Javascript 的中文編碼是使用兩個 bytes 來儲存的,而英文、符號等是使用一個 byte 來儲存。

Using length

使用 string.length 來計算長度時,中文字串會被當成是一個字來計算,因為 Javascript 是使用 multiple byte 計算,就像是 php 的 mb_strlen。

Example
  1. var tx="測試&eng";
  2. alert(tx.length);
  3. //output: 6

看到吧不管是中文,英文,都當成一個字來算,所以總共是 6 個字,不過有時候我們並不想把中文字當成一個字來計算,因為中文字明明佔的 byte 數就是比較多,所以來看一下中文編碼吧。

中文編碼

一般使用 big5 為繁體中文編碼,每一個中文字是使用兩個 bytes 來儲存。

UTF-8 編碼則是每一個中文字使用三個 bytes 來儲存。

不過這個算法對 javascript 的中文長度計算是有問題的,首先我寫了一小段 Javascript 程式碼,以及兩個不同編碼的網頁,我先抓取一段 UTF-8編碼的網頁,然後計算他的長度。

utf8_data.txt 的內容 = [測試中文eng] ,這個檔案在 linux 系統中看到的檔案大小為 15 bytes,代表一個中文字使用三個 bytes 儲存,不過在 Javascript中抓到的字串長度 = 7。
big5_data.txt 的內容 = [測試中文eng],這個檔案在 linux 系統中看到的檔案大小為 11 bytes,代表一個中文字使用二個 bytes 儲存,不過在 Javascript中抓到的字串長度 = 9

View Demo
Example
  1. for (var i = 0; i < tx.length; i++) {
  2. c+=""+tx.charCodeAt(i).toString(2)+"<br />";
  3. k+=""+tx.charCodeAt(i).toString()+"<br />";
  4. }
  5. document.body.innerHTML+=c;
  6. document.body.innerHTML+=k;
  7. //output
  8. /*
  9. 110111000101100
  10. 1000101001100110
  11. 100111000101101
  12. 110010110000111
  13.  
  14. 28204
  15. 35430
  16. 20013
  17. 25991
  18. */
從上面就可以看出來,雖然我是使用 UTF-8 編碼,不過轉成二進位後,每個中文字都是兩個 bytes 的長度而已(從上面的二進位數字中來看,最多只有 16 個 「0和1」,而一個 byte 有 8 個 bits ,所以是 兩個 bytes)。

計算文字 bytes 數

我將每一個文字轉成數字,再去計算他的二進位的 bit 數,決定文字是使用幾個 byte 來儲存。


兩個不同的編碼頁面,使用 length抓到的結果是不相同的,原因是我寫的 Javascript 是使用 UTF-8 編碼,所以可以正常的計算 UTF-8編碼的內容長度,不過在計算 big5 編碼的內容時,就會因為編碼表的不同,而出現怪怪的結果。

接著我試著在 Javascript 中,把中文字轉成二進位的編碼來看看。

Example
  1. var tx="測試中文";
  2. var c=""
  3. function stringBytes(c){
  4. var n=c.length,s;
  5. var len=0;
  6. for(var i=0; i <n;i++){
  7. s=c.charCodeAt(i);
  8. while( s > 0 ){
  9. len++;
  10. s = s >> 8;
  11. }
  12. }
  13. return len;
  14. }
  15.  
  16. var tx="測試中文12313";
  17. $(document.body).append(stringBytes(tx));
  18. //output: 13

上個範例中,中文字有四個,數字有五個,每個中文字為 2 bytes ,所以總合長度是 13 bytes 。

不存在三個 bytes 的文字

測試了一下 Javascript 是否有三個 bytes 的文字,結果發現,當編碼的數字大於 65535 時(16 bits),數字又會回到 0 開始計算,所以 Javascript 的編碼表中最多只有 65536 個文字 (包含 0 , 0 ~ 65535)。

Example
  1. var t1=String.fromCharCode(65586);
  2. var t2=String.fromCharCode(50);
  3. if(t1==t2) alert("yes")
  4. //兩個文字都是 2 , (65586 -65536 =50)

Using encodeURIComponent

另一種計算方式是使用 encodeURIComponent ,先使用 encodeURIComponent 將字串進行編碼,之後再用 Regular Expression 計算 bytes 數。

Example
  1. tx = "測式中文xxxx";
  2. var str = encodeURIComponent(tx);
  3. console.log("utf8:"+str);
  4. len = str.replace(/%[A-F\d]{2}/g, 'U').length;
  5. console.log("len = "+len);
這個方式可以成功的計算 Big5 & UTF-8 的編碼長度。

get string length from web

如果我們使用 ajax 去抓網頁的資料時, Big5 編碼的頁面會自動被轉換成 UTF-8 的亂碼,這時 encodeURIComponent 就沒辦法計算正確的長度。

用 ajax 從 header 中抓頁面的長度

如果是要抓頁面的size ,可以直接從 header 的資訊中取得,方式如下。

Example
  1. //using jquery
  2. var geturl;
  3. geturl =$.ajax({
  4. type:"get",
  5. url:"b.txt",
  6. beforeSend:function(){
  7. },
  8. success:function(tx){
  9. var header = geturl.getAllResponseHeaders();
  10. var re=/Content-Length:[\s]([0-9]+)/;
  11. var k=header.match(re);
  12. document.body.innerHTML="length = "+k[1];
  13. }
  14. });

使用 ajax 取得 header 的內容

在 Ajax 回傳的 物件中,使用 method : getAllResponseHeaders,就可以抓到 response header 的內容,範例如下。

Example
  1. var request = null;
  2. try {
  3. request = new XMLHttpRequest();
  4. } catch (trymicrosoft) {
  5. try {
  6. request = new ActiveXObject("Msxml2.XMLHTTP");
  7. } catch (othermicrosoft) {
  8. try {
  9. request = new ActiveXObject("Microsoft.XMLHTTP");
  10. } catch (failed) {
  11. request = null;
  12. }
  13. }
  14. }
  15. function ajaxResultHeader() {
  16.  
  17. if (request.readyState == 4 ) {
  18. if (request.status == 200 ) {
  19. var tx = request.responseText;
  20. var header =request.getAllResponseHeaders();
  21. console.log("header = "+header);
  22. } else
  23. alert("Error! Request status is " + request.status);
  24. }
  25. }
  26. function ajaxGetHeader() {
  27. var url = "b.txt";
  28. request.open("GET" , url, true);
  29. request.onreadystatechange = ajaxResultHeader;
  30. request.setRequestHeader("Content-Type","application/octet-stream");
  31. request.send();
  32. }
  33. ajaxGetHeader();

取得的 header 內容

Example
  1. Date: Wed, 29 Feb 2012 03:01:23 GMT
  2. Server: Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1
  3. Last-Modified: Wed, 29 Feb 2012 02:44:38 GMT
  4. Etag: "b00000002859d-827-4ba115319353b"
  5. Accept-Ranges: bytes
  6. Content-Length: 2087
  7. Content-Type: text/plain

使用 ajax 從 content 中取得前 2KB 的Bytes 數

HTTP Request 中本身有個屬性 Range , 可以指定我要取得多少 bytes ,從 x1 ~ x2 bytes ,因為從 Server 端取得的資料就是 2 KB ,這樣就不需要經過 Ajax 的胡亂編碼過程,就能正確的取得bytes數,用法就是在 Request Header 中加入 Range:bytes=0-2000,下面是範例。

! 使用 Range 抓取片段資料,Http Response 的 status 會等於 206
Example
  1. var request = null;
  2. try {
  3. request = new XMLHttpRequest();
  4. } catch (trymicrosoft) {
  5. try {
  6. request = new ActiveXObject("Msxml2.XMLHTTP");
  7. } catch (othermicrosoft) {
  8. try {
  9. request = new ActiveXObject("Microsoft.XMLHTTP");
  10. } catch (failed) {
  11. request = null;
  12. }
  13. }
  14. }
  15. function ajaxResult() {
  16.  
  17. if (request.readyState == 4 ) {
  18. if (request.status == 200 || request.status == 206) {
  19. var tx = request.responseText;
  20. console.log("content= "+tx);
  21. } else
  22. alert("Error! Request status is " + request.status);
  23. }
  24. }
  25. function ajaxGet() {
  26. var url = "b.txt";
  27. request.open("GET" , url, true);
  28. request.onreadystatechange = ajaxResult;
  29. request.setRequestHeader("Content-Type","application/octet-stream");
  30. request.setRequestHeader("Range","bytes=0-2000");
  31. request.send();
  32. }
  33. ajaxGet();

Request Header Example

線上測試,使用 Ajax 抓 page header And content

線上測試
Bytes: from ~

備註

bit 運算

在電腦的世界中,數字都是使用二進位來計算的,下面是基本的十進位與二進位對照表。

十進位二進位
210
4100
81000
6110

二進位運算符號 「>>」,「 >> 1」這個是指二進位的數字向右邊移動一格,所以最右邊的數字會被刪除,範例如下。

十進位運算二進位的原始值二進位結果
2 >> 1 = 1101
11 >> 2 = 3 111011
8 >> 1 = 41000100
6 >> 1 = 311011

目前回應 Comments(1 comments)

  • 數星星 2014/11/11

    文中計算字串長度函式中的這行,目前使用Chrome版本 38.0.2125.111 m 是失效的:
    len = str.replace(/%[A-Fd]{2}/g, 'U').length;
    改為
    len = str.replace(/%[A-F0-9]{2}/g, 'U').length;
    可正常使用

    Reply

    Admin

    因為編輯器的關系 ,程式瀘掉了一個斜線 「 \d」,正確的程式如下
     
    len = str.replace(/%[A-F\d]{2}/g, 'U').length;

回應 (Leave a comment)