首頁  >  文章  >  web前端  >  淺析Nodejs怎麼進行大檔案讀寫

淺析Nodejs怎麼進行大檔案讀寫

青灯夜游
青灯夜游原創
2022-09-28 20:09:282117瀏覽

淺析Nodejs怎麼進行大檔案讀寫

筆者最近在做一些node端的檔案讀寫和分片上傳工作,在這個過程中,發現node讀取的檔案如果超過2G,超過了讀取Blob最大值,會出現讀取異常,此外在node中讀寫檔案也受伺服器RAM的限制等,需要分片讀取,本人記錄一下遇到的問題以及解決問題的經過。 【相關教學推薦:nodejs影片教學

  • node中的檔案讀寫
  • node檔案讀寫RAM和Blob大小的限制
  • 其他

一、node中的檔案讀寫

1.1 常規檔案讀寫

    常規的,如果我們要讀取一個比較小的文件,可以直接通過:

const fs = require('fs')
let data = fs.readFileSync("./test.png")
console.log(data,123)
//输出data = <Buffer 89 50 4e ...>

    一般而言,同步的方法不是很推薦,因為js/nodejs是單線程的,同步的方法會阻塞主執行緒。最新版的node直接提供了fs.promise,可以結合async/await直接使用:

const fs = require('fs')
const readFileSync = async () => {
    let data = await fs.promises.readFile("./test.png")
    console.log(data,123)
}
readFileSync()
//输出data = <Buffer 89 50 4e ...>

這裡透過非同步的方法呼叫不會阻塞主線程,多個檔案讀取的IO也可以並行進行等。

1.2 Stream檔案讀寫

    常規的檔案讀寫,我們會把檔案一次的讀取到記憶體中,這個方法時間效率與記憶體效率都很低,時間效率低是指必須要一次讀取完畢後才能執行後續才做,內存效率低是指必須把這個文件都一次性讀取放入內存中,很佔用內存。因此這種情況下,我們一般會使用Stream來進行檔案的讀取:

const fs = require('fs')
const readFileTest = () => {
    var data = ''
    var rs = fs.createReadStream('./test.png');
    rs.on('data', function(chunk) {
        data += chunk;
        console.log(chunk)
     });
    rs.on('end',function(){
        console.log(data);
    });
    rs.on('error', function(err){
        console.log(err.stack);
     });
}
readFileTest()
// data = <Buffer 89 50 64 ...>

透過Steam來進行檔案讀寫,可以提高記憶體效率和時間效率。

  • 記憶體效率:在處理資料之前,不需要在記憶體中載入大量(或整個)資料
  • 時間效率:一旦有了數據,就可以開始處理,這大大減少開始處理資料的時間,而不必等到整個資料載入完畢再處理。

Stream的檔案也支援第二種寫法:

const fs = require('fs')
const readFileTest = () => {
    var data = ''
    var chunk;
    var rs = fs.createReadStream('./test.png');
    rs.on('readable', function() {
    while ((chunk=rs.read()) != null) {
        data += chunk;
    }});
    rs.on('end', function() {
        console.log(data)
    });
};
readFileTest()

#二、node檔案讀寫RAM和Blob大小的限制

##2.1 基礎問題

    在讀取大檔案時,會有讀取檔案大小的限制,例如我們現在正在讀取一個2.5G的視訊檔案:

const fs = require('fs')
const readFileTest = async () => {
    let data = await fs.promises.readFile("./video.mp4")
    console.log(data)
}
readFileTest()
執行上述的程式碼會錯誤:

RangeError [ERR_FS_FILE_TOO_LARGE]: File size (2246121911) is greater than 2 GB
  1.   -old-space-size=5000',此時5000M>2.5G,但是報錯還是沒有消失,也就是說透過Options無法改變node讀取檔案的大小限制。
    上述是常規的方式讀取大文件,如果透過Steam的方式讀取還會有文件大小的限制嘛?例如:
    const fs = require('fs')
    const readFileTest = () => {
        var data = ''
        var rs = fs.createReadStream('./video.mp4');
        rs.on('data', function(chunk) {
            data += chunk;
         });
        rs.on('end',function(){
            console.log(data);
        });
        rs.on('error', function(err){
            console.log(err.stack);
         });
    }
    readFileTest()
  1. 如上方式讀取一個2.5G的檔案不會有異常,不過要注意的是這邊有一個報錯:
  2. data += chunk;
                    ^
    
    RangeError: Invalid string length
此時是因為data的長度超過了最大限制,如2048M等。因此在用Steam處理的時候,在讀取結果的保存時,要注意檔案的大小,千萬不能超過預設的Buffer的最大值。上述這種情況,我們不用data = chunk將資料全部保存在一個大的data中,我們可以邊讀取邊處理。

2.2 分片讀取

    createReadStream在讀取檔案的過程中,其實也可以分段讀取,這種分段讀取的方法也可以做為大檔案讀取的備選項。特別是並發讀取的時候有一定的優點,可以提升檔案讀取和處理的速度。

    createReadStream接受第二個參數{start,end}。我們可以透過fs.promises.stat來取得檔案的大小,然後確定分片,最後分片一次讀取,例如:

取得檔案大小

const info = await fs.promises.stat(filepath)
   const size = info.size
###依照指定的SIZE分片(例如128M一個分片)######
  const SIZE = 128 * 1024 * 1024
  let sizeLen = Math.floor(size/SIZE)
    let total = sizeLen +1 ;
    for(let i=0;i<=sizeLen;i++){
      if(sizeLen ===i){
        console.log(i*SIZE,size,total,123)
        readStremfunc(i*SIZE,size,total)
      }else{
        console.log(i*SIZE,(i+1)*SIZE,total,456)
        readStremfunc(i*SIZE,(i+1)*SIZE-1,total)
      }
    }
  //分片后【0,128M】,【128M, 256M】...
###3.實作讀取函數###
const readStremfunc = () => {
    const readStream =  fs.createReadStream(filepath,{start:start,end:end})
    readStream.setEncoding('binary')
    let data = ''
    readStream.on('data', chunk => {
        data = data + chunk
    })
    readStream.end('data', () => {
      ...
    })
}
###    值得注意的是fs.createReadStream(filepath,{ start,end}),start和end是前閉後閉的,例如fs.createReadSteam(filepath,{start:0,end:1023})讀取的是[0,1023]總共1024個bit。 #########三、其他############3.1 擴充瀏覽器端的大檔案讀取與寫入#########    前面將了大檔案在nodejs中的讀取,那麼在瀏覽器端會讀取大檔案會有什麼問題嗎? ###

    浏览器在本地读取大文件时,之前有类似FileSaver、StreamSaver等方案,不过在浏览器本身添加了File的规范,使得浏览器本身就默认和优化了Stream的读取。我们不需要做额外的工作,相关的工作:github.com/whatwg/fs。不过不同的版本会有兼容性的问题,我们还是可以通过FileSaver等进行兼容。

3.2 请求静态资源大文件

    如果是在浏览器中获取静态资源大文件,一般情况下只需要通过range分配请求即可,一般的CDN加速域名,不管是阿里云还是腾讯云,对于分片请求都支持的很好,我们可以将资源通过cdn加速,然后在浏览器端直接请求cdn加速有的资源。

    分片获取cdn静态资源大文件的步骤为,首先通过head请求获取文件大小:

const getHeaderInfo = async (url: string) => {
  const res: any = await axios.head(url + `?${Math.random()}`);
  return res?.headers;
};
const header = getHeaderInfo(source_url)
const size = header['content-length']

我们可以从header中的content-length属性中,获取文件的大小。然后进行分片和分段,最后发起range请求:

const getRangeInfo = async (url: string, start: number, end: number) => {
    const data = await axios({
      method: 'get',
      url,
      headers: {
        range: `bytes=${start}-${end}`,
      },
      responseType: 'blob',
    });
    return data?.data;
  };

在headers中指定 range: bytes=${start}-${end},就可以发起分片请求去获取分段资源,这里的start和end也是前闭后闭的。

更多node相关知识,请访问:nodejs 教程

以上是淺析Nodejs怎麼進行大檔案讀寫的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn