前端使用FileReader 讀取本地文件和校驗文件惟一

故事背景

昨天下午被問到一個問題:oss 對象存儲裏邊因爲有些圖片被共享,致使上傳了不少的重複的圖片或者文件,有沒有辦法在上傳以前判斷一下這個文件是否被上傳過,若是上傳過直接去後端拿存儲的地址行不行。前端

當時被問到的時候,第一反應是根據file的文件類型名稱和大小生成一個MD5,後來被否決了,加入文件改了名字的話,這個文件仍是會被上傳上去git

而後經過一天的調研,學習了這個以前沒有用過的FileReader對象,順便被他的其餘方法給吸引住了,今天這裏分享一下es6

是什麼FileReader

FileReader 對象容許Web應用程序異步讀取存儲在用戶計算機上的文件(或原始數據緩衝區)的內容,使用File或Blob

Blob 對象表示一個不可變、原始數據的類文件對象。Blob 表示的不必定是JavaScript原生格式的數據。npm

File 接口基於Blob,繼承了 blob 的功能並將其擴展使其支持用戶系統上的文件。") 對象指定要讀取的文件或數據。後端

其中File對象能夠是來自用戶在一個 input 元素用於爲基於Web的表單建立交互式控件,以便接受來自用戶的數據; 可使用各類類型的輸入數據和控件小部件,具體取決於設備和user agent元素上選擇文件後返回的FileList對象,也能夠來自拖放操做生成的DataTransfer對象,還能夠是來自在一個HTMLCanvasElement上執行mozGetAsFile()方法後返回結果。(MDN)數組

說白了就是FileReader對象能夠對內存中的數據進行操做promise

而後須要知道一個重點就是瀏覽器

FileReader僅用於以安全的方式從用戶(遠程)系統讀取文件內容 它不能用於從文件系統中按路徑名簡單地讀取文件。

也就是說他是不能夠直接用本地的路徑去讀取文件的,能夠請求後端的資源,來讀取對應的文件,或者前端以一個比較安全的方式讀取文件,常見的好比說input的文件上傳安全

FileReader 的屬性

打印一下 如圖異步

1.jpg

EMPTY: 0
LOADING: 1
DONE: 2

這三個是對象實例的狀態,分別是未讀取文件,正在讀取和讀取完畢

readyState: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
    result: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
    error: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]

而後這三個屬性

  • readyState,這個是獲取當前對象實例的狀態
  • result,這個是讀取文件成功以後的返回值,具體的返回值是根據,你調用的對象實例的方法而返回的,下邊我會講一下FileReader的方法的具體使用
  • error,顯而易見,報錯的時候的信息
onloadstart: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
    onprogress: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
    onload: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
    onabort: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
    onerror: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
    onloadend: [Exception: TypeError: Illegal invocation at FileReader.invokeGetter (<anonymous>:1:142)]
  • onloadstart 讀取開始時觸發,這裏能夠作一些經常使用的處理 好比加載loading什麼的
  • onprogress 這個事件就比較讓人喜歡了,這個方法會在文件被讀取的過程當中被觸發,每大概11927552字節左右會被觸發一次,這個裏邊會反給一個ProgressEvent 對象,這個對象裏邊有本次讀取文件的最大字節數和已經讀取完畢的字節數,能夠用來作進度條什麼的
  • onload 這個事件是文件讀取成功的時候觸發 ,在這裏裏邊可使用上邊說道的實例上邊的result屬性,查看你操做的函數的對應的內容
  • onabort 讀取文件被終端的時候觸發,與之對應的有一箇中斷讀取的方法
  • onerror 讀取文件失敗的時候觸發
  • onloadend 讀取文件 無論失敗仍是成功都會觸發這個方法,這個方法的執行時機在onload方法以後

FileReader 的方法

  • readAsDataURL , 這個方法會返回一個你獲得的這個對象的一個base64的地址,可是這個地址,你會發現你的文件越大,這個地址就越長,其實這個地址是一個Base64編碼的文件數據字符串

而後以前說了FileReader全部的操做都是異步的,因此你並不能像下邊這樣獲取返回值

let fileReader = new FileReader()
        let url = fileReader.readAsDataURL(file.file)
        console.log(url)

這樣是打印不出來的,你須要在他自身的處理事件上邊回調獲取

let fileReader = new FileReader()
        fileReader.readAsDataURL(file.file)
        fileReader.onload=()=>{
            console.log(fileReader.result)
        }

回調結果在對象實例的result屬性上邊,上邊有說過

  • readAsBinaryString 開始讀取指定的Blob中的內容。一旦完成,result屬性中將包含所讀取文件的原始二進制數據。

這個方法獲取的結果是原始二進制數據,不能直接使用,還須要作一些轉換或者使用標籤什麼得才能用,打印出來大概是這樣的

1.jpg

  • abort 這個是中斷文件的讀取,好比你以爲這個讀取的事件有點長,再或者在某個特定狀況下你但願他停下來,那麼這個時候可使用這個方法中斷他,而且使用這個方法以後fileReader對象的狀態是DONE 也就是說能夠在onload裏邊去獲取已經讀取的數據

readAsArrayBuffer

  • 最後這兩個是我今天用到的方法readAsArrayBuffer 開始讀取指定的Blob中的內容, 一旦完成, result 屬性中保存的將是被讀取文件的 ArrayBuffer數據對象.

    • 這個ArrayBuffer上邊有mdn的傳送門能夠看下,或者看一下阮一峯老師的es6最下邊的講解,他是一個字節數組,用來表示通用的、固定長度的原始二進制數據緩衝區
    • 他不能直接被操做,你能夠用對應的TypedArray接口或者DataView的接口來操做他,這是一個對二進制字節數據操做的底層接口,我這裏使用了TypeArray
  • TypeArray的經常使用構造函數

    • 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 個字節。

好,看到這裏,以前沒有接觸過的同窗是否是腦瓜子嗡嗡的。。不要緊 我昨天我也嗡嗡的。。。

簡單扼要的說一下就是說,上邊列出來的這九個構造函數,都會根據你傳進去的參數,生成一個對應的數組,而後這些數組統稱爲TypeArray視圖,這個數組包含了全部的數組的方法和屬性,你能夠像數組同樣去操做他們,一會我會在下邊打印一下他們的結果,看一下就知道了

  • 而後上邊說的DataView,簡單說一下這個DataView是和TypedArray 配套使用的,由於DataView的參數是接受一個TypedArray對象,具體方法以下
  • 讀取

    • getInt8:讀取 1 個字節,返回一個 8 位整數。
    • getUint8:讀取 1 個字節,返回一個無符號的 8 位整數。
    • getInt16:讀取 2 個字節,返回一個 16 位整數。
    • getUint16:讀取 2 個字節,返回一個無符號的 16 位整數。
    • getInt32:讀取 4 個字節,返回一個 32 位整數。
    • getUint32:讀取 4 個字節,返回一個無符號的 32 位整數。
    • getFloat32:讀取 4 個字節,返回一個 32 位浮點數。
    • getFloat64:讀取 8 個字節,返回一個 64 位浮點數。
  • 寫入

    • setInt8:寫入 1 個字節的 8 位整數。
    • setUint8:寫入 1 個字節的 8 位無符號整數。
    • setInt16:寫入 2 個字節的 16 位整數。
    • setUint16:寫入 2 個字節的 16 位無符號整數。
    • setInt32:寫入 4 個字節的 32 位整數。
    • setUint32:寫入 4 個字節的 32 位無符號整數。
    • setFloat32:寫入 4 個字節的 32 位浮點數。
    • setFloat64:寫入 8 個字節的 64 位浮點數。

readAsText

這個是以前的時候搞得一個讀取文件的方法,裏邊用到了FileReaderreadAsText方法,很少說廢話了,直接附上代碼和效果圖

export default function readFile(model) {
    return new Promise((resolve) => {
        // 谷歌
        if (window.FileReader) {
            // 獲取文件流
            let file = model.currentTarget ? model.currentTarget.files[0] : model;
            // 建立FileReader實例
            let reader = new FileReader();
            // 讀文件
            reader.readAsText(file);
            reader.onload = () => {
                resolve(reader.result)
            }
        }
        //支持IE 7 8 9 10
        else if (typeof window.ActiveXObject != 'undefined') {
            let xmlDoc;
            xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
            xmlDoc.async = false;
            resolve(xmlDoc.load(model))
        }
        //支持FF
        else if (document.implementation && document.implementation.createDocument) {
            let xmlDoc;
            xmlDoc = document.implementation.createDocument("", "", null);
            xmlDoc.async = false;
            resolve(xmlDoc.load(model))
        }
    })
}
   
//安裝依賴
npm i zjsmethods -S
~~~~
// 頁面引入並使用
 import { _readFile } from "zjsmethods"
_readFile(file).then(res=>{
    console.log(res)
})

1.png

readAsArrayBuffer校驗文件惟一

上邊說了那麼一大堆,終於要進入正題了哈,直接看初版代碼

const reader = new FileReader();
    reader.readAsArrayBuffer(file.file);
    reader.onload = () => {
           let u8Arr =  new Uint8Array(reader.result)
           console.log(u8Arr)
           console.log(md5(u8Arr))
    }

ok 沒得問題,結果以下
1.png

正在我以爲如此簡單的時候,意外發生了,在我用比較小的文件的時候,只有1M 左右,可是當我上傳了一個視頻作測試的時候大概有兩個G,瀏覽器崩潰了。。崩潰了。。了

3.png

而後我展開了 以前比較小文件的字節數組,大概有這麼大

2.png

緣由是readAsArrayBuffer在讀取文件的時候會先把整個文件加載到內存中,那麼若是文件太大,內存就不夠用了,瀏覽器進程就會崩潰。

既然整個加載不行,那麼咱們選擇把一個文件分段加載,後來我以爲10M一段比較穩妥,因而改爲了當文件小於10M的時候平均分紅10段,若是大於10M ,那麼每10M 分紅一段 直到分完爲止,一樣爲了不加密的時候數據太多形成卡頓,在生成標識的時候放棄用整個數組生成標識,採起固定規則的最大10M 數據生成標識

async vaildArrayBuffer(){
        const reader = new FileReader();
        while(this.whileNumber--){
            this.start = this.end
            this.end = this.end+this.whileMax
            let { start,end,sliceEnd,file}=this
                reader.readAsArrayBuffer(file.slice(start,end));
                reader.onload = () => {
                        new Uint8Array(reader.result)
                            .slice(0, sliceEnd)
                            .join('')
                }
        }
    }

這個時候又出了一個小插曲,在調用的時候reader被提示,正在進行文件讀取,也是就一個reader在作讀取文件操做的時候不能同事讀取兩個,因而乎剛開始的時候我高估了讀取的速度放在了回調裏邊讀取文件,代價就是我在電腦前面眼巴巴的看了控制檯大概5分鐘,後來改爲了promise包裹,最後整理出的代碼以下

/*
 * @Date: 2020-03-22 16:36:37
 * @information: 最後更新時間
 */
import md5 from 'md5'
export default class vaileFile{
    constructor(file){
        this.file = file
        // 每次截取多少二進制
        this.whileMax = Math.floor(file.size / 10 > 10240 * 1024 ?  10240 * 1024 : file.size / 10);
        // 循環截取多少次
        this.whileNumber = file.size <= 10240 * 1024 ? 10 : Math.ceil(file.size/this.whileMax)
        // 二進制的截取長度,超出10M後 每10M 截取一部分,最多10M
        this.sliceEnd = Math.floor(1024 * 10240 / file.size * 100 / this.whileNumber * this.whileMax)
        this.sliceEnd = this.whileNumber>10?this.sliceEnd:10240 * 1024 
        // 轉換二進制的長度
        this.start = 0 
        this.end = 0;
    }
    /**
     * @Author: 周靖鬆
     * @Date: 2020-03-22 15:53:07
     * @information: 校驗文件惟一
     */
    async vaildArrayBuffer(){
            let promiseArr = []
            while(this.whileNumber--){
                this.start = this.end
                this.end = this.end+this.whileMax
                let { start,end,sliceEnd,file}=this
                let promiseArrayBuffer = new Promise((resolve,reject)=>{
                    const reader = new FileReader();
                    reader.readAsArrayBuffer(file.slice(start,end));
                    reader.onload = () => {
                        resolve(
                            new Uint8Array(reader.result)
                                .slice(0, sliceEnd)
                                .join('')
                        )
                    }
                })
                promiseArr.push(promiseArrayBuffer)
            }
            return md5((await Promise.all(promiseArr)).join(''))
        }
    }

大功告成,上傳的文件後會生成一個md5 ,複製文件,文件更名字,均可以識別是以前的文件

而後寫一個README.md 說明一下使用方法

### _vaileFile ,//使用文件二進制校驗文件惟一性

    當有業務須要上傳oss 對象存儲的時候,爲了不同一個文件(視頻,音頻,圖片,壓縮包等),有可能其餘人複製或者更名字等等,形成文件重複上傳,大量佔用空間,寫了一個校驗文件的方法

    //安裝依賴
    npm i zjsmethods -S
    
    //引入這個類

    import { _vaileFile } from 'zjsmethods'

    //  而後在你須要判斷oss 是否有該文件的時候

    new _vaileFile('file對象').vaildArrayBuffer().then(res=>{
        console.log(res)
        // 繼續上傳 或者 向後端請求已經存在的文件url
    })

    // new 這個類以後 有一個vaildArrayBuffer 方法 他返回一個promise ,裏邊返回值是一個md5的字符串,這個是這個文件的惟一標識

最後發佈npm包傳git 結束學習 ★,°:.☆( ̄▽ ̄)/$:.°★

喜歡的點個贊吧,有不足之處歡迎斧正

相關文章
相關標籤/搜索