極簡 Node.js 入門 - 3.2 檔案讀取
阿新 • • 發佈:2020-08-20
> 極簡 Node.js 入門系列教程:[https://www.yuque.com/sunluyong/node](https://www.yuque.com/sunluyong/node)
>
> 本文更佳閱讀體驗:[https://www.yuque.com/sunluyong/node/fs-read](https://www.yuque.com/sunluyong/node/fs-read)
Node.js 提供了多種讀取檔案的 API
## fs.readFile
`fs.readFile(path[, options], callback)` 是最常用的讀取檔案方法,用於非同步讀取檔案的**全部**內容
```javascript
const fs = require('fs');
fs.readFile('./test.txt', (err, data) => {
if (err) throw err;
console.log(data);
});
```
回撥會傳入兩個引數 (err, data),其中 data 是檔案的內容,如果 `options` 是字串,則它指定字元編碼:
```javascript
fs.readFile('./test.txt', 'utf8', callback);
```
options 可以設定為物件
```javascript
fs.readFile('./test.txt', { encoding: 'utf8', flag: 'r' }, callback);
```
## fs.open、fs.read、fs.close
fs.readFile 使用相當簡單,在大部分讀取小檔案的時候我們都應該使用這個方法,但 fs.readFile() 會把檔案全部內容讀取,如果想精確讀取部分檔案內容,Node.js 也提供了類似 C 語言 fopen、fgetc、fclose 的操作
在 Node.js 中讀取一個檔案同樣有三步
1. fs.open():分配(開啟)檔案描述符
1. fs.read():讀取檔案內容
1. fs.close():關閉檔案描述符
> 在 POSIX 每個開啟的檔案系統都分配了一個稱為檔案描述符的數字。 檔案系統操作使用檔案描述符來標識和跟蹤每個特定的檔案。一旦被分配,則檔案描述符可用於從檔案讀取資料、向檔案寫入資料、或請求關於檔案的資訊
> buffer 相關知識需要先行檢視 > [Buffer](https://www.yuque.com/sunluyong/node/buffer) 章節
### fs.open(path[, flags[, mode]], callback)
通過 fs.open 方法可以開啟一個檔案,獲取分配到的檔案描述符,方法引數含義:
- **path** 檔案路徑(實際還能是 Buffer、URL)
- **flags** [檔案標誌位](http://nodejs.cn/api/fs.html#fs_file_system_flags),預設值 r(read 縮寫),標識檔案用於讀取
- **mode** 檔案模式,預設值 0o666 (rw-)可讀寫
- **callback**
- err
- fd 分配到的檔案描述符 (file description)
### fs.read(fd, [options,] callback)
>
fs.read 用於從檔案描述符中讀取資料,方法引數含義:
- **fd** 檔案描述符
- **options** 可選項,不設定使用下述預設值
- buffer:資料(從 fd 讀取)將被寫入的緩衝區,預設會申請一個新的緩衝區 `Buffer.alloc(16384)`
- offset:buffer 開始寫入的偏移量,預設值為 0
- length:需要讀取的位元組數,預設使用 `buffer.length`
- position:從檔案中開始讀取資料的位置;如果 position 為 null,則從當前檔案位置讀取,並重置檔案位置到上次位置;如果 position 是整數,則檔案位置會被保持。預設值為 null
- **callback**
- err
- bytesRead
- buffer
> fs.read 還有一個需要把引數寫全的過載 fs.read(fd, buffer, offset, length, position, callback)
### fs.close(fs, callback)
fs.close 用於關閉檔案描述符,大多數作業系統都會限制同時開啟的檔案描述符數量,因此當操作完成時關閉描述符非常重要。 如果不這樣做將導致記憶體洩漏,最終導致應用程式崩潰
### demo
_test.txt_
```
0123456789
abcdefghigklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
```
```javascript
const fs = require('fs');
const promisify = require('util').promisify;
const open = promisify(fs.open);
const read = promisify(fs.read);
const close = promisify(fs.close);
async function test() {
const fd = await open('./test.txt');
const readOptions = {
// buffer: Buffer.alloc(26), 非同步呼叫預設可以不設定,如果希望讀取的位元組寫入指定的緩衝區可以指定
position: 11, // 從第 11 個位元組開始讀取,讀取後文件位置被重置
length: 26, // 讀取 26 個位元組
};
const { bytesRead: bytesRead1, buffer: buf1 } = await read(fd, readOptions);
console.log(`第一次讀取位元組數: ${bytesRead1}`);
console.log(`第一次讀取資料內容: ${buf1.toString()}`);
// 不指定 position,檔案位置每次讀取後會保持
const { bytesRead: bytesRead2, buffer: buf2 } = await read(fd, { length: 1 });
console.log(`第二次從檔案重置後位置讀取 ${bytesRead2} 位元組內容: ${buf2.toString()}`);
const { bytesRead: bytesRead3, buffer: buf3 } = await read(fd, { length: 1 });
console.log(`第三次從檔案當前位置讀取 ${bytesRead3} 位元組內容: ${buf3.toString()}`);
await close(fd);
console.log(`檔案描述符 ${fd} 已關閉`);
}
test();
```
```
第一次讀取位元組數: 26
第一次讀取資料內容: abcdefghigklmnopqrstuvwxyz
第二次從檔案重置後位置讀取 1 位元組內容: 0
第三次從檔案當前位置讀取 1 位元組內容: 1
檔案描述符 20 已關閉
```
- 程式第一次從第 11 個位元組開始讀取 `test.txt` 內容,一共讀取 26 個位元組
- 第一次讀取設定了 position 屬性,讀取完成後檔案指標位置被重置為 0
- 第二次讀取沒有設定 position 讀取一個位元組後,檔案位置停留在 1
- 第三次讀取直接從檔案位置 1 開始讀取
除非希望精確控制,否則不要使用這種方式讀取檔案,手工控制緩衝區、檔案位置指標很容易出現各種意外狀況 ## fs.createReadStream 對於大檔案讀取一般使用流的方式,關於流的簡單原理在後面章節有專門介紹,本章介紹一下使用 fs 建立可讀檔案流
`fs.createReadStream(path[, options])` 1. path 1. options(比較常用的有) - fd: 如果指定了 fd,則 ReadStream 會忽略 path 引數,使用指定的檔案描述符(不會再次觸發 open 事件) - autoClose: 預設值: true,檔案讀取完成或者出現異常時是否自動關閉檔案描述符 - start: 開始讀取位置 - end: 結束讀取位置 - highWaterMark: 預設值: 64 * 1024,普通可讀流一般是 16k
流的各個狀態會有對應的事件丟擲,還是讀取上文用過的 `test.txt` 檔案 ```javascript const fs = require('fs'); const rs = fs.createReadStream('./test.txt', { start: 11, end: 36 }); rs.on('open', fd => { console.log(`檔案描述符 ${fd} 已分配`); }); rs.on('ready', () => { console.log('檔案已準備好'); }); rs.on('data', chunk => { console.log('讀取檔案資料:', chunk.toString()); }); rs.on('end', () => { console.log('檔案讀取完成'); }); rs.on('close', () => { console.log('檔案已關閉'); }); rs.on('error', (err) => { console.log('檔案讀取發生發生異常:', err.stack); }); ``` ``` 檔案描述符 20 已分配 檔案已準備好 讀取檔案資料: abcdefghigklmnopqrstuvwxyz 檔案讀取完成 檔案已關閉 ``` 可讀流詳細操作參考:可讀流 [https://www.yuque.com/sunluyong/node/readable](https://www.yuque.com/sunluyong/node/re
除非希望精確控制,否則不要使用這種方式讀取檔案,手工控制緩衝區、檔案位置指標很容易出現各種意外狀況 ## fs.createReadStream 對於大檔案讀取一般使用流的方式,關於流的簡單原理在後面章節有專門介紹,本章介紹一下使用 fs 建立可讀檔案流
`fs.createReadStream(path[, options])` 1. path 1. options(比較常用的有) - fd: 如果指定了 fd,則 ReadStream 會忽略 path 引數,使用指定的檔案描述符(不會再次觸發 open 事件) - autoClose: 預設值: true,檔案讀取完成或者出現異常時是否自動關閉檔案描述符 - start: 開始讀取位置 - end: 結束讀取位置 - highWaterMark: 預設值: 64 * 1024,普通可讀流一般是 16k
流的各個狀態會有對應的事件丟擲,還是讀取上文用過的 `test.txt` 檔案 ```javascript const fs = require('fs'); const rs = fs.createReadStream('./test.txt', { start: 11, end: 36 }); rs.on('open', fd => { console.log(`檔案描述符 ${fd} 已分配`); }); rs.on('ready', () => { console.log('檔案已準備好'); }); rs.on('data', chunk => { console.log('讀取檔案資料:', chunk.toString()); }); rs.on('end', () => { console.log('檔案讀取完成'); }); rs.on('close', () => { console.log('檔案已關閉'); }); rs.on('error', (err) => { console.log('檔案讀取發生發生異常:', err.stack); }); ``` ``` 檔案描述符 20 已分配 檔案已準備好 讀取檔案資料: abcdefghigklmnopqrstuvwxyz 檔案讀取完成 檔案已關閉 ``` 可讀流詳細操作參考:可讀流 [https://www.yuque.com/sunluyong/node/readable](https://www.yuque.com/sunluyong/node/re