文章用JS簡單的實現UTF-8編碼和Base64編碼,閱讀本文能夠了解Unicode 與 UTF-8 之間的轉換,瞭解Base64編碼爲何會使數據量變長。express
概要:bash
Unicode,ASCII,GB2312編碼集合等,相似於字典。字符的編碼,相似於字典中的字在哪一頁哪一行。當不一樣系統用同一本字典查同一個編碼獲得的字符會一致。學習
以下圖:ui
wikipedia:
編碼
Unicode is a computing industry standard for the consistent encoding, representation, and handling of text expressed in most of the world's writing systems.spa
在創造Unicode以前各類語言有不一樣的編碼集合,ASCII,GB2312等也是發展過程當中編碼集合,並且這些編碼集合相互衝突,給不一樣語言系統進行交流帶來了麻煩。由於兩種相同的字符在不一樣的編碼系統中可能有徹底不一樣的意思。因而Unicode出現了,Unicode編碼集合給每一個字符提供了一個惟一的數字,不論平臺,程序,語言,Unicode 字符集所以被普遍應用。3d
Javascript程序是用Unicode字符集編寫的, 字符串(string)中每一個字符一般來自於Unicode 字符集。rest
Unicode 字符集相似於字典,字符就相似於字。字符的Unicode碼值,就相似於字在字典的第頁第幾行。code
由於Unicode 編碼轉換成二進制,是一串0,和1,傳輸個另外一方的時候,須要一個規則來分割這一串0、1。orm
因而就出現了UTF-n 編碼們。
8bit = 1byte
存儲UTF(Universal Transformation Format,通用傳輸格式),其實就是不改變字符集中各個字符的代碼,創建一套新的編碼方式,把字符的代碼經過這個編碼方式映射成傳輸時的編碼,最主要的任務就是在使用Unicode字符集保持通用性的同時節約流量和硬盤空間。
字符用四個字節表示
UTF-16字符用兩個字節或四個字節表示
UTF-8
一種變長的編碼方式,根據須要用1~4個字節來表示字符,(按需傳遞節約流量和硬盤空間,所以UTF-8用的比較廣)
eg: 字符羅的UTF-8編碼
用codePointAt獲得了字符的Unicode 編碼,確認用幾個字節表示,而後按照規則填充。
編碼流程:
資料補充:
ES6 提供了codePointAt()方法,可以正確處理字節儲存的字符,返回一個字符的碼點(Unicode 編碼)。
ES6 提供了String.fromCodePoint()方法能正確處理一個碼點(Unicode 編碼),返回碼點(Unicode 編碼)對應的字符
// 編碼
function encodeUtf8(str) {
var bytes = []
for (ch of str) {
// for...of循環,能正確識別 32 位的 UTF-16 字符, 能夠查閱資料瞭解。
let code = ch.codePointAt(0)
if (code >= 65536 && code <= 1114111) {// 位運算, 補齊8位
bytes.push((code >> 18) | 0xf0)
bytes.push(((code >> 12) & 0x3f) | 0x80)
bytes.push(((code >> 6) & 0x3f) | 0x80)
bytes.push((code & 0x3f) | 0x80)
} else if (code >= 2048 && code <= 65535) {
bytes.push((code >> 12) | 0xe0)
bytes.push(((code >> 6) & 0x3f) | 0x80)
bytes.push((code & 0x3f) | 0x80)
} else if (code >= 128 && code <= 2047) {
bytes.push((code >> 6) | 0xc0)
bytes.push((code & 0x3f) | 0x80)
} else {
bytes.push(code)
}
}
return bytes
}
// 補位
function padStart(str, len, prefix) {
return ((new Array(len + 1).join(prefix)) + str).slice(-len) // 也可用 new Array(len+1).fill(0)
}
// 解碼
function decodeUtf8(str) {
let strValue = ''
let obStr = [...str].map((ch)=> {
// 一位16進制數 轉二進制 須要四位來表示 補全位數
return padStart(parseInt(ch,16).toString(2), 4, 0)
}).join('').match(/\d{8}/g).map((item)=> parseInt(item,2))
for (var i = 0; i < obStr.length; ) {
let code = obStr[i]
let code1, code2, code3, code4, hex
// 比較 前4 位 parseInt(11110000,2) = 240 4個字節
if ((code & 240) == 240) {
code1 = (code & 0x03).toString(2)
code2 = padStart((obStr[i + 1] & 0x3f).toString(2),6, '0')
code3 = padStart((obStr[i + 2] & 0x3f).toString(2),6, '0')
code4 = padStart((obStr[i + 3] & 0x3f).toString(2),6, '0')
hex = parseInt((code1 + code2 + code3 + code4),2)
strValue = strValue + String.fromCodePoint(hex)
i = i + 4
} else if ((code & 224) == 224) { // 3個字節表
code1 = (code & 0x07).toString(2)
code2 = padStart((obStr[i + 1] & 0x3f).toString(2),6, '0')
code3 = padStart((obStr[i + 2]& 0x3f).toString(2),6, '0')
hex = parseInt((code1 + code2 + code3),2)
strValue = strValue + String.fromCodePoint(hex)
i = i + 3
} else if ((code & 192) == 192) {// 2個字節表
code1 = (code & 0x0f).toString(2)
code2 = padStart((obStr[i + 1] & 0x3f).toString(2),6, '0')
hex = parseInt((obStr + code2),2)
strValue = strValue + String.fromCodePoint(hex)
i = i + 2
} else {
hex = code
strValue = strValue + String.fromCodePoint(code)
i = i + 1
}
}
return strValue
}
// byte to hex
function transferHex(bytes) {
let s = ''
bytes &&
bytes.forEach(ch => {
s = s + ch.toString(16)
})
return s
}
let text = "羅小步 啊哈哈 𠮷 ssdf 34534 ASD"
let strHax = transferHex(encodeUtf8(text))
console.log(strHax)
let str = decodeUtf8(strHax)
console.log(str)
console.log("test ok?", text === str)複製代碼
規則:Base64的編碼方法要求把每三個8bit的字節轉換成四個6bit的字節,而後把6Bit再添兩位高位0,組成四個8Bit的字節。
若是要編碼的二進制數據不是3的倍數,最後剩下一個或者兩個字節Base64會在末尾補零,再在編碼的末尾加上一個或者兩個‘=’。
每一個8bit 編碼成:CHARST[paresInt(8bit ,2)]
CHARTS = ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/複製代碼
編碼流程:
const CHARTS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const prefix = '='
const prefixTwo = 2
const prefixfour = 4
function padEnd(str, len, prefix) {
return (str + (new Array(len + 1)).join(prefix)).slice(0, len)
}
function padStart(str, len, prefix) {
return ((new Array(len + 1).join(prefix)) + str).slice(-len)
}
// 編碼
function encodeBase64(str){
let byteStr = ''
for(let ch of encodeUtf8(str)){
byteStr = byteStr + padStart(ch.toString(2),8,0)
}
let rest = byteStr.length % 6 // 餘2 就是剩下了一個字節,餘 4 就是剩下兩個字節
let restStr = rest === prefixTwo ? '==' :'='
let prefixzero = rest === prefixTwo ? prefixfour: prefixTwo
byteStr = padEnd(byteStr , byteStr.length + prefixzero,'0')
return byteStr.match(/(\d{6})/g).map(val=>parseInt(val,2)).map(val=>CHARTS[val]).join('') + restStr;
}
// 解碼
function decodeBase64(str) {
let matchTime = str.match(/(ha)/g)
let [...restStr] = str.replace(/=/g,'')
restStr = restStr.map((item)=> {
let value = CHARTS.indexOf(item)
return padStart(value.toString(2),6,0)
}).join('').match(/(\d{8})/g).map((item)=>parseInt(item,2).toString(16)).join()
console.log(restStr)
return decodeUtf8(restStr)
}
let text = "羅小步 啊哈哈 𠮷 ssdf 34534 ASD"
let strHax = encodeBase64(text)
console.log(strHax)
let str = decodeBase64(strHax)
console.log(str)
console.log("test ok?", text === str)複製代碼
Base64的編碼方法要求把每三個8bit的字節轉換成四個6bit的字節,編碼會使數據量變長原來的1/3.
編碼方式只是一種對字符集表現的形式。文章用js 簡單的實現utf8編碼和base64編碼。代碼實現比較粗糙,理解不許確之處,還請教正。歡迎一塊兒討論學習。