深入淺出JavaScript中base64編碼原理
前言
平靜之下,驀然回首,base64 卻在燈火闌珊處。
今天翻開舊項目發(fā)現挺多圖片相關的插件都是用 base64 來顯示圖片的。談到 base64,腦海遐想翩翩,思緒回蕩之下 base64 瑕瑜互見。本文旨在記錄工作中遇見的問題并加以總結,如有不妥請指正~
base64 由來
base64 是網絡傳輸 8Bit 字節(jié)代碼的編碼方式之一,是一種基于 64 個可打印字符來表示二進制數據的方法。在做支付系統(tǒng)時,報文交互都需要使用 base64 對明文進行轉碼,然后再進行簽名或加密,之后再進行(或再次 base64 轉碼)傳輸。那么,base64 到底起到什么作用呢?
在參數傳輸的過程中經常遇到的一種情況:使用全英文的字符串沒問題,但一旦涉及到中文就會出現亂碼的情況。與此類似,網絡上傳輸的字符并不全是可打印的字符,比如二進制文件、圖片等。base64 的出現就是為了解決此問題,它是基于 64 個可打印的字符來表示二進制的數據的一種方法。
電子郵件剛問世的時候,只能傳輸英文,但后來隨著用戶的增加,中文、日韓俄文等文字的用戶也有需求,但這些字符并不能被服務器或網關有效處理,因此 base64 就登場了。隨后,base64 在 URL、Cookie、網頁傳輸少量二進制文件中也有相應的使用。
base64 的編碼原理
基于a-z、A-Z、0-9、+/這 64 個字符來標識二進制數據,另外=符號用于當字節(jié)缺位時補用。
base64 編碼對照表
base64 編碼對照表
| 索引 | 對應字符 | 索引 | 對應字符 | 索引 | 對應字符 | 索引 | 對應字符 |
| 0 | A | 17 | R | 34 | i | 51 | z |
| 1 | B | 18 | S | 35 | j | 52 | 0 |
| 2 | C | 19 | T | 36 | k | 53 | 1 |
| 3 | D | 20 | U | 37 | l | 54 | 2 |
| 4 | E | 21 | V | 38 | m | 55 | 3 |
| 5 | F | 22 | W | 39 | n | 56 | 4 |
| 6 | G | 23 | X | 40 | o | 57 | 5 |
| 7 | H | 24 | Y | 41 | p | 58 | 6 |
| 8 | I | 25 | Z | 42 | q | 59 | 7 |
| 9 | J | 26 | a | 43 | r | 60 | 8 |
| 10 | K | 27 | b | 44 | s | 61 | 9 |
| 11 | L | 28 | c | 45 | t | 62 | + |
| 12 | M | 29 | d | 46 | u | 63 | / |
| 13 | N | 30 | e | 47 | v | ||
| 14 | O | 31 | f | 48 | w | ||
| 15 | P | 32 | g | 49 | x | ||
| 16 | Q | 33 | h | 50 | y |
base64 的編碼轉換規(guī)則
base64 要求把每三個 8Bit 的字節(jié)轉換為四個 6Bit 的字節(jié)(3*8 = 4*6 = 24),然后把 6Bit再添兩位高位 0,組成四個 8Bit 的字節(jié)(4*8=32)。
為什么使用 3 個字節(jié)一組呢?因為 6 和 8 的最小公倍數為 24,三個字節(jié)正好 24 個二進制位,每 6 個 bit 位一組,恰好能夠分為 4 組。
同時用于分組后每組添加兩個高位 0,轉換后的字符串理論上將要比原來的字符長 1/3(24/32=1/3)。
步驟分解:
- 將待轉換的字符串每三個字符分為一組,每個字符字節(jié)占 8bit,那么共有 24 個二進制位。
- 將 24 個二進制位每 6 個字節(jié)為一組,共分為 4 組。
- 在每組 6 字節(jié)前面添加兩個 0,每組由 6 字節(jié)變?yōu)?8 字節(jié)二進制位,組成總共 32 個二進制位,即四個字節(jié)。
- 根據 base64 編碼對照表獲得對應的值。
舉個栗子
1.以標準 3 個字符LJY為例。
LJY對應的 ASCII 碼值分別為 76、74、89,對應的二進制值是 01001100、01001010、01011001。由此組成一個 24 位的二進制位字符串。- 將 24 位的二進制位字符串,按照每 6 位二進制位一組分成 4 組。
- 對 4 組 每組 6 位二進制位字符串前面補兩個 0,每組擴展為 8 位二進制位,4 組共擴展成 32 個二進制位,此時 4 組二進制位分別為:00010011、00000100、00101001、00011001。其對應的 base64 編碼索引為:19、4、41、25。
2.用 base64 編碼索引值在 base64 編碼表中進行查找,分別對應:T、E、p、Z。
因此LJYbase64 編碼之后就變?yōu)椋篢EpZ。
| 文本 | L | J | Y |
| ASCII | 76 | 74 | 89 |
| 二進制位 | 01001100 | 01001010 | 01011001 |
| 分組二進制 | 010011 | 000100 | 101001 | 011001 |
| 分組二進制補2個0 | 00010011 | 00000100 | 00101001 | 00011001 |
| 分組索引 | 19 | 4 | 41 | 25 |
| base64編碼 | T | E | p | Z |
主要展示:
轉換前二進制位: 01001100 01001010 01011001
轉換后二進制位: 00010011 00000100 00101001 00011001
字符位數不足情況
上述栗子是面向剛好三個字符為一組的情況。當然不是所有時候都這么巧字符位數足夠,除此以外有位數不足的情況。那么,面對字符位數不足的情況下該如何處理呢?
base64 給出的方案是,當每組字符不足三位時,不足位數位置需要使用=符號補上。
位數不足情況處理情景:
- 位數缺一個字節(jié):一個字節(jié)共 8 個二進制位,依舊按照規(guī)則進行分組。此時共 8 個二進制位,每 6 個一組,則第二組缺少 4 位,用 0 補齊,得到兩個 base64 編碼,而后面兩組沒有對應數據,都用
=補上。 - 位數缺兩個字節(jié):兩個字節(jié)共 16 個二進制位,依舊按照規(guī)則進行分組。此時總共 16 個二進制位,每 6 個一組,則第三組缺少 2 位,用 0 補齊,得到三個 base64 編碼,第四組完全沒有數據則用
=補上。
位數不足圖解如下:
<!-- 缺2位字符,字符串以A為例轉換base64后位QQ== --> | 文本(1Byte) | A | | | | 二進制位 | 01000001 | | | | 分組二進制 | 010000 | 010000 | | | | 分組二進制補0 | 00010000 | 00010000 | | | | 分組索引 | 16 | 16 | | | | base64編碼 | Q | Q | = | = | <!-- 缺1位字符,字符串以AB為例轉換base64后位QUI= --> | 文本(1Byte) | A | B | | | 二進制位 | 01000001 | 01000010 | | | 分組二進制 | 010000 | 010100 | 001000 | | | 分組二進制補0 | 00010000 | 00010100 | 00001000 | | | 分組索引 | 16 | 20 | 8 | | | base64編碼 | Q | U | I | = |
列舉了一個字符到三個字符轉換為 base64 ,可以發(fā)現將 base64 就是按照 base64 編碼對照表來將二進制轉換為字符串,使得數據不能直接明文展示出來,但也算不上是加密,而這巧好可用在傳輸、存儲、表示二進制領域的情景。
- 另外值得注意的是,不用語言如中文有多種編碼(比如:utf-8、gb2312、gbk 等),不同編碼對應 base64 編碼結果都不一樣。
- 其次在推演過程中可發(fā)現 base64 即用 6 位字節(jié)(2 的 6 次冪就是 64)表示字符同理,Base32 就是用 5 位字節(jié),Base16 就是用 4 位字節(jié)。大家可以按照上面的步驟進行演化測試。
base64 優(yōu)缺點
知道 base64 是什么后,也該到為什么出現了。為什么要是使用 base64 呢,這要從其優(yōu)缺點入手來選擇適合場景了。
優(yōu)勢:
- base64 適合不同平臺、不同語言的傳輸;
- 頁面中內嵌使用 base64 格式的小圖片,可減少了服務器訪問次數;
- 二進制位轉換 base64 算法簡單,對性能影響不大;
缺點
1.二進制文件轉換為 base64 后,體積大概增加 1/3;
- 在基于 Android6.0 及以下默認瀏覽器實測場景中發(fā)現,某些機型如中興上傳 base64 圖片會因為字符大小過大導致上傳奔潰的情況。
- 字符長度過大的 base64 不適應使用在 URL 情景,因為 IOS 端瀏覽器會限制 URL 長度,當長度超過時會自動切除多余部分,導致數據丟失。
- base64 字符過大會導致頁面加載速度變慢,因此建議 10kb 以下的圖片使用。
2.base64 無法緩存,要緩存只能緩存包含 base64 的文件,比如 js 或者 css;
3.面對大文件時,會消耗一定的 CPU 進行編解碼
JavaScript 的 base64 轉碼方法
Web API 二進制與 base64 轉換
atob(encodedData) : 解碼一個 base64 編碼的字符串。
enCodedData,是一個通過 btoa() 方法編碼的字符串, 為二進制字符串包含 base64 編碼的數據。并返回包含來自 encodedData 的解碼數據的 ASCII 字符串。
btoa(stringToEncode) : 創(chuàng)建一個 bas64 編碼的字符串。
stringToEncode 為要編碼的二進制字符串。并返回包含 stringToEncode 的 base64 表示形式的 ASCII 字符串。
另外在 JavaScript 中,字符串使用 UTF-16 字符編碼表示:在這種編碼中,字符串表示為 16 位(2 字節(jié))單元的序列。每個 ASCII 字符都可以放入其中一個單元的第一個字節(jié),但許多其他字符不能。
base64 在設計上需要二進制數據作為其輸入。就 JavaScript 字符串而言,這意味著每個字符只占用一個字節(jié)的字符串。因此,如果將一個字符串傳遞到 btoa()中,其中包含占用多個字節(jié)的字符,則會出現錯誤,因為這不被視為二進制數據,因此超 16 位字符在使用 btoa()時需要先對字符轉碼為二進制位。
// 簡單數據
const encodedData = btoa('Hello, world'); // encode a string
const decodedData = atob(encodedData); // decode the string
/* 復雜數據 */
// convert a Unicode string to a string in which
// each 16-bit unit occupies only one byte
function toBinary(string) {
const codeUnits = new Uint16Array(string.length);
for (let i = 0; i < codeUnits.length; i++) {
codeUnits[i] = string.charCodeAt(i);
}
const charCodes = new Uint8Array(codeUnits.buffer);
let result = '';
for (let i = 0; i < charCodes.byteLength; i++) {
result += String.fromCharCode(charCodes[i]);
}
return result;
}
function fromBinary(binary) {
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
const charCodes = new Uint16Array(bytes.buffer);
let result = '';
for (let i = 0; i < charCodes.length; i++) {
result += String.fromCharCode(charCodes[i]);
}
return result;
}
// a string that contains characters occupying > 1 byte
const myString = '???????';
const converted = toBinary(myString);
const encoded = btoa(converted);
console.log(encoded); // OCY5JjomOyY8Jj4mPyY=
const decoded = atob(encoded);
const original = fromBinary(decoded);
console.log(original); // ???????
兼容性:atob() 方法不支持 IE9 及更早的 IE 版本。
base64 轉二進制
// base64編碼表
const map = {
0: 52,
1: 53,
2: 54,
3: 55,
4: 56,
5: 57,
6: 58,
7: 59,
8: 60,
9: 61,
A: 0,
B: 1,
C: 2,
D: 3,
E: 4,
F: 5,
G: 6,
H: 7,
I: 8,
J: 9,
K: 10,
L: 11,
M: 12,
N: 13,
O: 14,
P: 15,
Q: 16,
R: 17,
S: 18,
T: 19,
U: 20,
V: 21,
W: 22,
X: 23,
Y: 24,
Z: 25,
a: 26,
b: 27,
c: 28,
d: 29,
e: 30,
f: 31,
g: 32,
h: 33,
i: 34,
j: 35,
k: 36,
l: 37,
m: 38,
n: 39,
o: 40,
p: 41,
q: 42,
r: 43,
s: 44,
t: 45,
u: 46,
v: 47,
w: 48,
x: 49,
y: 50,
z: 51,
'+': 62,
'/': 63,
};
function base64to2(base64) {
let len = base64.length * 0.75; // 轉換為int8array所需長度
base64 = base64.replace(/=*$/, ''); // 去掉=號(占位的)
const int8 = new Int8Array(len); //設置int8array視圖
let arr1,
arr2,
arr3,
arr4,
p = 0;
for (let i = 0; i < base64.length; i += 4) {
arr1 = map[base64[i]]; // 每次循環(huán) 都將base644個字節(jié)轉換為3個int8array直接
arr2 = map[base64[i + 1]];
arr3 = map[base64[i + 2]];
arr4 = map[base64[i + 3]];
// 假設數據arr 數據 00101011 00101111 00110011 00110001
int8[p++] = (arr1 << 2) | (arr2 >> 4);
// 上面的操作 arr1向左邊移動2位 變?yōu)?0101100
// arr2 向右移動4位:00000010
// | 為'與'操作: 10101110
int8[p++] = (arr2 << 4) | (arr3 >> 2);
int8[p++] = (arr3 << 6) | arr4;
}
return int8;
}base64 轉成 Blob
// base64圖片轉blob
function base64toBlob(base64) {
var arr = base64.split(','),
mime = arr[0].match(/:(.*?);/)[1] || 'image/png',
bstr = atob(arr[1]), // 將base64轉為Unicode規(guī)則編碼
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n); // 轉換編碼后才可以使用charCodeAt 找到Unicode編碼
}
return new Blob([u8arr], { type: mime });
}
/* 優(yōu)化版 */
function base64ToBlob(base64) {
var arr = base64.split(',');
var mime = arr[0].match(/:(.*?);/)[1] || 'image/png';
// 去掉url的頭,并轉化為byte
var bytes = window.atob(arr[1]);
// 處理異常,將ascii碼小于0的轉換為大于0
var ab = new ArrayBuffer(bytes.length);
// 生成視圖(直接針對內存):8位無符號整數,長度1個字節(jié)
var u8arr = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) {
u8arr[i] = bytes.charCodeAt(i);
}
return new Blob([u8arr], { type: mime });
}以上就是深入淺出JavaScript中base64編碼原理的詳細內容,更多關于JavaScript base64編碼的資料請關注腳本之家其它相關文章!
相關文章
Windows下支持自動更新的Electron應用腳手架的方法
這篇文章主要介紹了Windows下支持自動更新的Electron應用腳手架的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
IE11下使用canvas.toDataURL報SecurityError錯誤的解決方法
這篇文章主要給大家介紹了關于在IE11下使用canvas.toDataURL報SecurityError錯誤的解決方法,文中通過示例代碼介紹的非常詳細,對同樣遇到這個問題的朋友們具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2017-11-11
javascript中節(jié)點的最近的相關節(jié)點訪問方法
parentNode——父節(jié)點;firstChild——第一個子節(jié)點;lastChild——最后一個子節(jié)點;previousSibling——緊挨著的前面的兄弟節(jié)點;這樣就可以作短途旅行,訪問當前節(jié)點的某些相關節(jié)點,感興趣的你可以參考下哈2013-03-03
js中AppendChild與insertBefore的用法詳細解析
這篇文章主要是對js中AppendChild與insertBefore的用法進行了詳細的分析介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12

