一.背景
在文件相關的數據加工等場景下,經常面臨生成的物理文件應該如何處理的問題,比如:
生成的文件放到哪里,路徑存在不存在?
臨時文件何時清理,如何解決命名沖突,防止覆蓋?
并發場景下的讀寫順序如何保證?
……
對于讀寫物理文件帶來的這些問題,最好的解決辦法就是 不寫文件 。然而,一些場景下想要不寫文件可不那么容易,比如文件上傳
二.問題
文件上傳一般通過表單提交來實現,例如:
var FormData = require('form-data');var fs = require('fs');var form = new FormData();form.append('my_file', fs.createReadStream('/foo/bar.jpg'));form.submit('example.org/upload', function(err, res) { console.log(res.statusCode);});(摘自 Form-Data )
不想寫物理文件的話,可以這樣做:
const FormData = require('form-data');const filename = 'my-file.txt';const content = 'balalalalala...變身';const formData = new FormData();// 1.先將字符串轉換成Bufferconst fileContent = Buffer.from(content);// 2.補上文件meta信息formData.append('file', fileContent, { filename, contentType: 'text/plain', knownLength: fileContent.byteLength});也就是說,文件流除了能夠提供數據外,還具有一些 meta 信息,如文件名、文件路徑等 ,而這些信息是普通 Stream 所不具備的。那么,有沒有辦法憑空創建一個“真正的”文件流?
三.思路
要想創建出“真正的”文件流,至少有正反 2 種思路:
給普通流添上文件相關的 meta 信息
先拿到一個真正的文件流,再改掉其數據和 meta 信息
顯然,前者更靈活一些,并且實現上能夠做到完全不依賴文件
文件流的生產過程
沿著憑空創造的思路,探究 fs.createReadStream API 的 內部實現 之后發現,生產文件流的關鍵過程如下:
function ReadStream(path, options) { // 1.打開path指定的文件 if (typeof this.fd !== 'number') this.open();}ReadStream.prototype.open = function() { fs.open(this.path, this.flags, this.mode, (er, fd) => { // 2.拿到文件描述符并持有 this.fd = fd; this.emit('open', fd); this.emit('ready'); // 3.開始流式讀取數據 // read來自父類Readable,主要調用內部方法_read // ref: https://github.com/nodejs/node/blob/v10.16.3/lib/_stream_readable.js#L390 this.read(); });};ReadStream.prototype._read = function(n) { // 4.從文件中讀取一個chunk fs.read(this.fd, pool, pool.used, toRead, this.pos, (er, bytesRead) => { let b = null; if (bytesRead > 0) { this.bytesRead += bytesRead; b = thisPool.slice(start, start + bytesRead); } // 5.(通過觸發data事件)吐出一個chunk,如果還有數據,process.nextTick再次this.read,直至this.push(null)觸發'end'事件 // ref: https://github.com/nodejs/node/blob/v10.16.3/lib/_stream_readable.js#L207 this.push(b); });};
|
新聞熱點
疑難解答
圖片精選