在開發前端「後臺管理系統」時,免不了有操做文件的需求,好比說要運營人員要上傳一份 txt 形式的號碼包文件,一行就是一個號碼,而後展示在前端界面上給運營人員 review,沒問題以後再將號碼包文件上傳。javascript
在咱們理清 Blob 和 FileReader 這些你有可能接觸過的概念以前,咱們先來打牢一下基礎,看看咱們 js 是如何操做內存中的二進制數據?瞭解完這塊部分,可以幫助咱們更好地去操做文件。html
這篇文章,我帶你們理清以下概念:前端
ArrayBuffer
TypedArray
DataView
Uint8Array
/ Uint16Array
等等ArrayBuffer 對象、TypedArray 對象、DataView 對象是 JavaScript 操做二進制數據的一個接口。這些對象早就存在,屬於獨立的規格,ES6將它們歸入了 ECMAScript 規格,而且增長了新的方法。java
這些對象原始的設計目的,與 WebGL 項目有關。所謂 WebGL,就是指瀏覽器與顯卡之間的通訊接口,爲了知足 JavaScript 與顯卡之間大量的、實時的數據交換,它們之間的數據通訊必須是二進制的,而不能是傳統的文本格式。文本格式傳遞一個 32 位整數,兩端的 JavaScript 腳本與顯卡都要進行格式轉化,將很是耗時。這時要是存在一種機制,能夠像 C 語言那樣,直接操做字節,將 4 個字節的 32 位整數,以二進制形式原封不動地送入顯卡,腳本的性能就會大幅提高。數組
二進制數組就是在這種背景下誕生的。它很像 C 語言的數組,容許開發者以數組下標的形式,直接操做內存,大大加強了 JavaScript 處理二進制數據的能力,使得開發者有可能經過 JavaScript 與操做系統的原生接口進行二進制通訊。瀏覽器
二進制數組的讀寫通常是經過如下三類對象來操做:app
ArrayBuffer
對象,表明內存之中的一段二進制數據,能夠經過「視圖」進行操做。「視圖」部署了數組接口,這意味着,能夠用數組的方法操做內存。函數
建立一個指定長度的 ArrayBuffer
對象,並讀取該對象的字節大小:post
// 8 表明所須要分配的內存大小(單位:字節)
const buffer = new ArrayBuffer(8);
console.log(buffer.byteLength);
// 8
複製代碼
可是 ArrayBuffer
對象只讀,是不能被直接操做的,只能經過「視圖實例對象」來操做,它們會將緩衝區中的數據表示爲特定的格式,並經過這些格式來讀寫緩衝區的內容。性能
而且,ArrayBuffer
類提供了一個方法判斷某變量是否爲「視圖實例對象」:
console.log( ArrayBuffer.isView(arg) );
複製代碼
而所謂的「視圖實例對象」,也就是「類型數組對象」(TypedArray
對象) 或 DataView
對象。
二者的區別在於,後者能夠自定義格式和字節序。例如,前者只能實例化一樣格式(好比都是 Unit8Array
類型)的對象,然後者能夠第一個字節爲 Unit8
類型,第二個字節爲 Int16
類型,等等。
DataView
類型對象筆者平常工做狀況下比較少用,可是應用場景很是豐富,筆者打算另起一篇文章來單獨闡述其使用方法和場景。
TypedArray
視圖類型很特殊,它不是一個構造函數,而是一組構造函數的統稱,也就是說不能在程序中直接使用 TypedArray
來實例化對象,由於它只屬於一個「概念」。
而真正能實例化對象、且隸屬於 TypedArray
視圖的構造函數有以下:
Int8Array
: 8位有符號整數,長度1個字節Uint8Array
:8位無符號整數,長度1個字節Uint8ClampedArray
:8位無符號整數,長度1個字節,溢出處理不一樣Int16Array
:16位有符號整數,長度2個字節Uint16Array
:16位無符號整數,長度2個字節Int32Array
:32位有符號整數,長度4個字節Uint32Array
:32位無符號整數,長度4個字節Float32Array
:32位浮點數,長度4個字節Float64Array
:64位浮點數,長度8個字節咱們在此僅拿 Uint8Array
類型舉例說明,其餘同理的。
首先看看 Uint8Array
每一個元素的所佔字節數:
console.log( Uint8Array.BYTES_PER_ELEMENT );
// 1
// 表示 1 個字節
複製代碼
實例化對象能夠有以下寫法:
// 寫法 1:實例化一個空的視圖對象
new Uint8Array();
// 寫法 2:建立初始化爲 0 的,包含 2 個元素的無符號整型數組
new Uint8Array(2);
// 寫法 3:從 ArrayBuffer 中建立
// uint8Buff 和 uint16Buff 視圖對象底層對應的是同一段內存對象
// 一方修改,會影響到另外一方
const buff = new ArrayBuffer(8);
const uint8Buff = new Uint8Array(buff);
const uint16Buff = new Uint16Array(buff);
console.log(uint8Buff.length);
// 8
複製代碼
另外,很厲害的一點是:普通數組的操做方法和屬性,對 TypedArray
類型數組徹底適用。
在肯定好編碼方式的前提下,ArrayBuffer 與字符串是能夠互相轉換的。咱們就經過 String.prototype.charCodeAt
與 String.fromCharCode
來實現一下。咱們知道這兩個函數都是基於 UTF-16 編碼格式將字符串和整數值進行轉換的,因此能夠肯定的編碼方式就是 「UTF-16」。
來看看代碼的實現:
function ab2str (arraybuffer) {
return String.fromCharCode.apply(
null,
new Uint16Array(arraybuffer)
);
}
function str2ab (str) {
// UTF-16 編碼中,一個字符在內存中須要佔用兩個字節
var arraybuffer = new ArrayBuffer(str.length * 2);
var u16Arr = new Uint16Array(arraybuffer);
var len = u16Arr.length;
for (var i=0; i<len; i++) {
u16Arr[i] = str.charCodeAt(i);
}
return u16Arr;
}
console.log(
ab2str( str2ab('Hello World') )
);
複製代碼
回到一開始的命題,「二進制數組」的知識可以幫助咱們更好地去操做文件。
那咱們來看看,在讀取文件的時候,驗證下使用 ArrayBuffer 方式讀取出來的文件內容是否正確。
建立一份 test.txt 文件,準備讀取
Hello World
複製代碼
前端 html 代碼
<input id="fileInputer" type="file" />
複製代碼
前端 js 代碼
const fileInputer = document.getElementById('fileInputer');
function ab2str (arraybuffer) {
return String.fromCharCode.apply(
null,
// 注意換成了 Uint8Array
// 由於 txt 文檔在電腦上通常都是 UTF-8 編碼
new Uint8Array(arraybuffer)
);
}
fileInputer.addEventListener('change', function (event) {
const target = event.target;
if (!target.files[0]) {
return ;
}
const file = evt.target.files[0];
const reader1 = new FileReader();
const reader2 = new FileReader();
// 以 ArrayBuffer 方式讀取,獲取結果後轉成字符串
reader1.onloadend = function (evt) {
console.log(
reader1.result
);
console.log(
ab2str(new Uint8Array(reader1.result))
);
};
reader1.readAsArrayBuffer(file);
// 以 text 方式讀取,獲取結果後直接展現
reader2.onloadend = function (evt) {
console.log(
reader2.result
);
};
reader2.readAsText(file);
}, false);
複製代碼
看到這裏,你們可能會有個疑問,爲何 FileReader 的實例已經提供了 readAsText
方法了,你還要使用 readAsArrayBuffer
方法費盡地去讀取,再轉換成字符串呢?
是這個道理沒錯,可是實際會有這兩種狀況:
我來給你們解答下:
readAsText
方法將文件讀到內存,是很是浪費以及損耗性能的,好比,1 G的日誌文件,700 MB 的號碼包文件,分分鐘會把瀏覽器卡死,嚴重影響用戶體驗。那麼,使用 readAsArrayBuffer
就能夠解決問題了嗎?是的,能夠的,這時候,就須要使用到 Blob 以及 Blob 與文件之間關係的知識了,咱們下一篇文章繼續闡述。