先建一個文件,按UTF-16大端 BOM 格式保存一個字符串:hi aleck,
使用 file API 把他按二進制方式讀取到瀏覽器。
文件讀取方法在這里:
http://hi.baidu.com/ecalf830/item/e3b2d2c9b1003222a0b50a39
簡單介紹一下 file api 的相關用法
1、在瀏覽器中打開文件
<input type="file" id="f" multiple="multiple"/>
當 file 控件 有 mutiple 屬性時,可以上傳多個文件,文件打開后保存在 file 控件的 files 屬性,這個例子中只打開一個文件,獲取的文件的方式是 :
var file = document.getElementById("f").files[0];
2、Blob 與文件讀取類 FileReader
二進制大對象 Blob (binary large object) 用於存儲二進制字節數據,
創建一個 blob 對象: var blob = new Blob(); 該對象的size屬性表示文件的字節數,類似於數組的length,slice 可以從文件中復制出一段二進制數據,用法類似於數組的 silce,復制得到的數據也是 Blob 對象。
FileReader 類用於讀取 Blob 數據對象。
創建一個文件讀取 對象 :var reader = new FileReader();
瀏覽器打開的文件 file 繼承於 Blob (thefile instanceof Blob == true),因此可以使用 FileReader 打開 file 對象。
FileReader 對象的屬性、方法及在打開Blob數據過程中可以使用的回調函數(試在 firebug 下查看 FileReader 的屬性和方法 console.log(new FileReader())):
error: null onabort: null onerror: null onload: null //讀取成功調用 onloadend: function(){...} //讀取完畢調用,即使讀取失敗也會調用 onloadstart: null //開始打開文件時調用 onprogress: null //文件打開過程中,反復調用直至文件讀取完畢 readyState: 2 result: "þÿhi aleck" //文件讀取結果得到的字符串 __proto__: FileReader DONE: 2 EMPTY: 0 LOADING: 1 abort: function abort() { [native code] } addEventListener: function addEventListener() { [native code] } constructor: function FileReader() { [native code] } dispatchEvent: function dispatchEvent() { [native code] } readAsArrayBuffer: function readAsArrayBuffer() { [native code] } //讀取為二進制字節緩沖 readAsBinaryString: function readAsBinaryString() { [native code] } //讀取為二進制數據 readAsDataURL: function readAsDataURL() { [native code] } //讀取為base64數據 readAsText: function readAsText() { [native code] } //讀取為普通字符串 removeEventListener: function removeEventListener() { [native code] } __proto__: Object
可以在 onloadend 事件內綁定處理打開的文件數據的回調函數,然后打開文件,
這里將文件以二進制方式打開:reader.readAsBinaryString();
查 看 reader.result 可以看到讀取結果。由於javascript 沒有二進制數據類型,因此,二進制數據按每個字節的8位二進制數對應 的 unicode 編碼的字符顯示出來,因此二進制數據實際被顯示為一串 ascii 字符,如果要讀取的文件是英文格式的, 那么這個二進制數據看起 來跟實際的字符串很相似。
本例中 UTF-16 BOM 大端格式的 字符串 "hi aleck" 打開為二進制后得到:
reader.result = "þÿhi aleck" //注意 reader.result.length 為 18, 這其中包含了空字符 \0 和標記字節流存儲順序的“零寬空格”
每個字節的8位二進制轉為二位十六進制為:
reader.result.split('').map(function(v){ return ('0'+v.charCodeAt(0).toString(16)).slice(-2); })
得到:
["fe", "ff", "00", "68", "00", "69", "00", "20", "00", "61", "00", "6c", "00", "65", "00", "63", "00", "6b"]
由於是UTF-16編碼,每個字符占二字節,feff 是用於字節序標記的字符"零寬空格",其余字符編碼由於只需要一個字節,高位字節用 0 補足,unicode 編碼為0 的字符為空字符 \0 。
例如 0068 是字母 h 的 2 字節(16位二進制轉4位16進制)編碼格式:
String.fromCharCode(parseInt('0068',16)) //h
3、字節數組緩沖 ArrayBuffer 和 類型數組
ArrayBuffer 類的對象指向一段定長的內存空間,其屬性 byteLength 表示對象的字節長度,slice 對象類似於 Blob 和數組的 slice 用於拷貝出一定字節長度的緩沖數據並創建成新的 ArrayBuffer 對象。
ArrayBuffer 緩沖字節數據對象只是一段內存空間,不能直接訪問,需要通過 DataView 對象(DataView 有點類似一個混合類型數組的元素組成的 list,其元素可以是幾種類型數組中的任一種,這里不細述)或類型數組訪問內存的數據,他們的關系有點像 Blob 與 FileReader .
其中 javascript 類型數組有下面幾種:
Float32Array
Float64Array
Int8Array
Int16Array
Int32Array
Uint8Array
Uint16Array
Uint32Array
Uint8ClampedArray
http://www.javascripture.com/DataView
本例中使用存儲無符號整數的 Uint8Array、 Uint16Array、 Uint32Array
Uint8Array 每個元素為一字節,表達范圍為 0-255,Uint16Array 每元素 2字節,Uint32Array 每個元素4字節。
如果想要將 緩沖字節 ArrayBuffer 對象讀入到類型數組中,比如可以這樣
var buff = new ArrayBuffer(16); //創建一段16字節大小的內存緩沖空間 var u8 = new Uint8Array(buff,8); //創建一個8位無符號整形類型數組,並指向 buff 對象的前 8 個字節。 var u16 = new Uint16Array(buff,8); //16 位類型數組 var u32 = new Uint32Array(buff,8); //32為類型數組
組要注意的是,類型數組並沒有拷貝出 ArrayBuffer 對象的內容,而是指向對應的內存空間,因此 u8、u16、u32 這幾個數組訪問的是同一塊內存,這意味着通過 u8 修改了內容后,u16 和 u32 將訪問到改變后的內容。
現在 u8、u16 、u32 指向同一塊 8 字節長度的內存,因此 u8 有8 個元素,u16 有4個元素,u32 則只有2個元素。
將上面打開文件得到的字節的前8個通過u8數組逐個存入 buff 內
["fe","ff","00","68","00","69","00","20"].forEach(function(v,i){ u8[i] = parseInt(v,16);});
有:
u8 = [254, 255, 0, 104, 0, 105, 0, 32] u16 = [65534, 26624, 26880, 8192] u32 = [1744895998, 536897792]
('00'+u8[0].toString(16)).slice(-2) // "fe" ('00'+u8[1].toString(16)).slice(-2) // "ff" ('00'+u8[2].toString(16)).slice(-2) // "00" ('00'+u8[3].toString(16)).slice(-2) // "68" ('00'+u8[4].toString(16)).slice(-2) // "00" ('00'+u8[5].toString(16)).slice(-2) // "69" ('00'+u8[6].toString(16)).slice(-2) // "00" ('00'+u8[7].toString(16)).slice(-2) // "20" ('0000'+u16[0].toString(16)).slice(-4) // "fffe" ('0000'+u16[1].toString(16)).slice(-4) // "6800" ('0000'+u16[2].toString(16)).slice(-4) // "6900" ('0000'+u16[3].toString(16)).slice(-4) // "2000" ('00000000'+u32[0].toString(16)).slice(-8) // "6800fffe" ('00000000'+u32[1].toString(16)).slice(-8) // "20006900"
可以看到,讀取 Blob 對象時,在多字節類型數組中( u16 2字節的元素 和 u32 4字節的元素), 元素的字節是按低位在前的順序存儲的,即小端方式。
我們也可以直接把通過 FileReader 的 readAsArrayBuffer() 方法將 Blob 對象讀取到 ArrayBuffer 對象中,然后在 類型數組中訪問字節:
reader.readAsArrayBuffer(blob); var u16 = new Uint16Array(reader.result); u16 為 [65534, 26624, 26880, 8192, 24832, 27648, 25856, 25344, 27392]
類型數組的數據溢出問題:
因 為數組元素類型決定其在內存中占據的字節長度,其數值表達范圍是按不同類型是不同的,如果存儲的數值超出其表達范圍便發生溢出,例 如, Uint8Array 類型的數組元素只占據一個字節,元素值的表達范圍是 8位2進制, 即 0~255 ,如果試圖存儲 257 將發生溢出, 實際會得到取模后的值
var uint8 = new Uint8Array(10); uint8[0] //0 uint8[0]=257; uint8[0]; //1 257%256 = 1 uint8[0] = -1; uint8[0]; //255 -1+256=255 uint8[0] = x; 則 uint8[0] == (x%256+256)%256 ; //true Uint8ClampedArray 跟 Uint8Array 不同,如果給 Uint8ClampedArray 類型的數組元素賦值超出范圍,則取最靠近所賦值的合法的值,
這個數組可以用於處理圖像的顏色數據,例如 canvas 的 image.data
var context = document.createElement("canvas").getContext("2d"); var imageData = context.createImageData(16, 16); console.log(imageData); //ImageData {height: 16, width: 16, data: Uint8ClampedArray[1024]} console.log(imageData.data instanceof Uint8ClampedArray)//true
var cint8 = new Uint8ClampedArray(10); // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] cint8[0]=257; // [255, 0, 0, 0, 0, 0, 0, 0, 0, 0] cint8[0]= -2; //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0] cint8[0] = x;則 cint8[0] == Math.min(Math.max(0,x),255)