一文讀懂base64編碼

1、爲何要使用 base64

咱們知道一個字節可表示的範圍是 0 ~ 255(十六進制:0x00 ~ 0xFF), 其中 ASCII 值的範圍爲 0 ~ 127(十六進制:0x00 ~ 0x7F);而超過 ASCII 範圍的 128~255(十六進制:0x80 ~ 0xFF)之間的值是不可見字符。javascript

ASCII(American Standard Code for Information Interchange,美國信息交換標準代碼)是基於拉丁字母的一套電腦編碼系統。它主要用於顯示現代英語,而其擴展版本延伸美國標準信息交換碼則能夠部分支持其餘西歐語言,並等同於國際標準 ISO/IEC 646。html

在 ASCII 碼中 0 - 31和 127 是控制字符,共 33 個。如下是其中一部分控制字符:前端

ascii-controller-table

其他 95 個,即 32 - 126 是可打印字符,包括數字、大小寫字母、經常使用符號等。java

ascii-printable-table

當不可見字符在網絡上傳輸時,好比說從 A 計算機傳到 B 計算機,每每要通過多個路由設備,因爲不一樣的設備對字符的處理方式有一些不一樣,這樣那些不可見字符就有可能被處理錯誤,這是不利於傳輸的。 爲了解決這個問題,咱們能夠先對數據進行編碼,好比 base64 編碼,變成可見字符,也就是 ASCII 碼可表示的可見字符,從而確保數據可靠傳輸。Base64 的內容是有 0 ~ 9,a ~ z,A ~ Z,+,/ 組成,正好 64 個字符,這些字符是在 ASCII 可表示的範圍內,屬於 95 個可見字符的一部分。web

2、什麼是 base64

Base64 是一種基於 64 個可打印字符來表示二進制數據的表示方法。因爲 2⁶ = 64 ,因此每 6 個比特爲一個單元,對應某個可打印字符。3 個字節有 24 個比特,對應於 4 個 base64 單元,即 3 個字節可由 4 個可打印字符來表示。相應的轉換過程以下圖所示:算法

base64-convert

**Base64 經常使用於在處理文本數據的場合,表示、傳輸、存儲一些二進制數據,包括 MIME 的電子郵件及 XML 的一些複雜數據。**在 MIME 格式的電子郵件中,base64 能夠用來將二進制的字節序列數據編碼成 ASCII 字符序列構成的文本。使用時,在傳輸編碼方式中指定 base64。使用的字符包括大小寫拉丁字母各 26 個、數字 10 個、加號 + 和斜槓 /,共 64 個字符,等號 = 用來做爲後綴用途。Base64 相應的索引表以下:express

base64-encode-table

瞭解完上述的知識,咱們以編碼 Man 字符串爲例,來直觀的感覺一下編碼過程。Man 由 M、a 和 n 3 個字符組成,它們對應的 ASCII 碼爲 7七、97 和 110。canvas

char-m-ascii

接着咱們以每 6 個比特爲一個單元,進行 base64 編碼操做,具體以下圖所示:瀏覽器

base64-encode-demo

由圖可知,Man (3字節)編碼的結果爲 TWFu(4字節),很明顯通過 base64 編碼後體積會增長 1/3。Man 這個字符串的長度恰好是 3,咱們能夠用 4 個 base64 單元來表示。但若是待編碼的字符串長度不是 3 的整數倍時,應該如何處理呢?安全

若是要編碼的字節數不能被 3 整除,最後會多出 1 個或 2 個字節,那麼可使用下面的方法進行處理:先使用 0 字節值在末尾補足,使其可以被 3 整除,而後再進行 base64 的編碼。

以編碼字符 A 爲例,其所佔的字節數爲 1,不能被 3 整除,須要補 2 個字節,具體以下圖所示:

base64-encode-a

由上圖可知,字符 A 通過 base64 編碼後的結果是 QQ==,該結果後面的兩個 = 表明補足的字節數。而最後個 1 個 base64 字節塊有 4 位是 0 值。

接着咱們來看另外一個示例,假設需編碼的字符串爲 BC,其所佔字節數爲 2,不能被 3 整除,須要補 1 個字節,具體以下圖所示:

base64-encode-bc

由上圖可知,字符串 BC 通過 base64 編碼後的結果是 QkM=,該結果後面的 1 個 = 表明補足的字節數。而最後個 1 個 base64 字節塊有 2 位是 0 值。

3、base64 編碼的應用

3.1 顯示 base64 編碼的圖片

在編寫 HTML 網頁時,對於一些簡單圖片,一般會選擇將圖片內容直接內嵌在網頁中,從而減小沒必要要的網絡請求,可是圖片數據是二進制數據,該怎麼嵌入呢?絕大多數現代瀏覽器都支持一種名爲 Data URLs 的特性,容許使用 base64 對圖片或其餘文件的二進制數據進行編碼,將其做爲文本字符串嵌入網頁中。

Data URLs 由四個部分組成:前綴(data:)、指示數據類型的 MIME 類型、若是非文本則爲可選的 base64 標記、數據自己:

data:[<mediatype>][;base64],<data>
複製代碼

mediatype 是個 MIME 類型的字符串,例如 "image/jpeg" 表示 JPEG 圖像文件。若是被省略,則默認值爲 text/plain;charset=US-ASCII。若是數據是文本類型,你能夠直接將文本嵌入(根據文檔類型,使用合適的實體字符或轉義字符)。若是是二進制數據,你能夠將數據進行 base64 編碼以後再進行嵌入。好比嵌入一張圖片:

<img alt="logo" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...">
複製代碼

MIME(Multipurpose Internet Mail Extensions)多用途互聯網郵件擴展類型,是設定某種擴展名的文件用一種應用程序來打開的方式類型,當該擴展名文件被訪問的時候,瀏覽器會自動使用指定應用程序來打開。多用於指定一些客戶端自定義的文件名,以及一些媒體文件打開方式。

常見的 MIME 類型有:超文本標記語言文本 .html text/html、PNG圖像 .png image/png、普通文本 .txt text/plain 等。

但須要注意的是:若是圖片較大,圖片的色彩層次比較豐富,則不適合使用這種方式,由於該圖片通過 base64 編碼後的字符串很是大,會明顯增大 HTML 頁面的大小,從而影響加載速度。 除此以外,利用 HTML FileReader API,咱們也能夠方便的實現圖片本地預覽功能,具體代碼以下:

<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script> const loadFile = function(event) { const reader = new FileReader(); reader.onload = function(){ const output = document.querySelector('output'); output.src = reader.result; }; reader.readAsDataURL(event.target.files[0]); }; </script>
複製代碼

在完成本地圖片預覽以後,能夠直接把圖片對應的 Data URLs 數據提交到服務器。針對這種情形,服務端須要作一些相關處理,才能正常保存上傳的圖片,這裏以 Express 爲例,具體處理代碼以下:

const app = require('express')();

app.post('/upload', function(req, res){
    let imgData = req.body.imgData; // 獲取POST請求中的base64圖片數據
    let base64Data = imgData.replace(/^data:image\/\w+;base64,/, "");
    let dataBuffer = Buffer.from(base64Data, 'base64');
    fs.writeFile("image.png", dataBuffer, function(err) {
        if(err){
          res.send(err);
        }else{
          res.send("圖片上傳成功!");
        }
    });
});
複製代碼

3.2 瀏覽器端圖片壓縮

在一些場合中,咱們但願在上傳本地圖片時,先對圖片進行必定的壓縮,而後再提交到服務器,從而減小傳輸的數據量。在前端要實現圖片壓縮,咱們能夠利用 Canvas 對象提供的 toDataURL() 方法,該方法接收 typeencoderOptions 兩個可選參數。

其中 type 表示圖片格式,默認爲 image/png。而 encoderOptions 用於表示圖片的質量,在指定圖片格式爲 image/jpegimage/webp 的狀況下,能夠從 0 到 1 的區間內選擇圖片的質量。若是超出取值範圍,將會使用默認值 0.92,其餘參數會被忽略。

下面咱們來看一下具體如何實現圖片壓縮:

// compress.js
const MAX_WIDTH = 800; // 圖片最大寬度

function compress(base64, quality, mimeType) {
  let canvas = document.createElement("canvas");
  let img = document.createElement("img");
  img.crossOrigin = "anonymous";
  return new Promise((resolve, reject) => {
    img.src = base64;
    img.onload = () => {
      let targetWidth, targetHeight;
      if (img.width > MAX_WIDTH) {
        targetWidth = MAX_WIDTH;
        targetHeight = (img.height * MAX_WIDTH) / img.width;
      } else {
        targetWidth = img.width;
        targetHeight = img.height;
      }
      canvas.width = targetWidth;
      canvas.height = targetHeight;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除畫布
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      let imageData = canvas.toDataURL(mimeType, quality / 100);
      resolve(imageData);
    };
  });
}
複製代碼

對於返回的 Data URL 格式的圖片數據,爲了進一步減小傳輸的數據量,咱們能夠把它轉換爲 Blob 對象:

function dataUrlToBlob(base64, mimeType) {
  let bytes = window.atob(base64.split(",")[1]);
  let ab = new ArrayBuffer(bytes.length);
  let ia = new Uint8Array(ab);
  for (let i = 0; i < bytes.length; i++) {
    ia[i] = bytes.charCodeAt(i);
  }
  return new Blob([ab], { type: mimeType });
}
複製代碼

在轉換完成後,咱們就能夠壓縮後的圖片對應的 Blob 對象封裝在 FormData 對象中,而後再經過 AJAX 提交到服務器上:

function uploadFile(url, blob) {
  let formData = new FormData();
  let request = new XMLHttpRequest();
  formData.append("image", blob);
  request.open("POST", url, true);
  request.send(formData);
}
複製代碼

其實 Canvas 對象除了提供 toDataURL() 方法以外,它還提供了一個 toBlob() 方法,該方法的語法以下:

canvas.toBlob(callback, mimeType, qualityArgument)
複製代碼

toDataURL() 方法相比,toBlob() 方法是異步的,所以多了個 callback 參數,這個 callback 回調方法默認的第一個參數就是轉換好的 blob文件信息。

介紹完上述的內容,咱們來看一下本地圖片壓縮完整的示例:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>本地圖片壓縮</title>
  </head>
  <body>
    <input type="file" accept="image/*" onchange="loadFile(event)" />
    <script src="./compress.js"></script>
    <script> const loadFile = function (event) { const reader = new FileReader(); reader.onload = async function () { let compressedDataURL = await compress( reader.result, 90, "image/jpeg" ); let compressedImageBlob = dataUrlToBlob(compressedDataURL); uploadFile("https://httpbin.org/post", compressedImageBlob); }; reader.readAsDataURL(event.target.files[0]); }; </script>
  </body>
</html>
複製代碼

4、如何進行 base64 編碼和解碼

在 JavaScript 中,有兩個函數被分別用來處理解碼和編碼 base64 字符串:

  • btoa():該函數可以基於二進制數據 「字符串」 建立一個 base64 編碼的 ASCII 字符串。
  • atob(): 該函數可以解碼經過 base64 編碼的字符串數據。

4.1 btoa 使用示例

const name = 'Semlinker';
const encodedName = btoa(name);
console.log(encodedName); // U2VtbGlua2Vy
複製代碼

4.2 atob 使用示例

const encodedName = 'U2VtbGlua2Vy';
const name = atob(encodedName);
console.log(name); // Semlinker
複製代碼

對於 atob 和 btoa 這兩個方法來講,其中的 a 表明 ASCII,而 b 表明 Blob,即二進制。所以 atob 表示 ASCII 到二進制,對應的是解碼操做。而 btoa 表示二進制到 ASCII,對應的是編碼操做。在瞭解方法中 a 和 b 分別表明的意義以後,在之後的工做中,咱們就不會用錯了。

5、總結

Base64 是一種數據編碼方式,目的是爲了保障數據的安全傳輸。但標準的 base64 編碼無需額外的信息,便可以進行解碼,是徹底可逆的。所以在涉及傳輸私密數據時,並不能直接使用 base64 編碼,而是要使用專門的對稱或非對稱加密算法。

6、參考資源

相關文章
相關標籤/搜索