<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Node.js底層知識 - 理解Buffer

          共 9753字,需瀏覽 20分鐘

           ·

          2020-12-25 19:15

          一. 認識Buffer

          1.1. 數(shù)據(jù)的二進制

          計算機中所有的內(nèi)容:文字、數(shù)字、圖片、音頻、視頻最終都會使用二進制來表示。

          JavaScript可以直接去處理非常直觀的數(shù)據(jù):比如字符串,我們通常展示給用戶的也是這些內(nèi)容。

          不對啊,JavaScript不是也可以處理圖片嗎?

          • 事實上在網(wǎng)頁端,圖片我們一直是交給瀏覽器來處理的;
          • JavaScript或者HTML,只是負責告訴瀏覽器一個圖片的地址;
          • 瀏覽器負責獲取這個圖片,并且最終將這個圖片渲染出來;

          但是對于服務器來說是不一樣的:

          • 服務器要處理的本地文件類型相對較多;
          • 比如某一個保存文本的文件并不是使用 utf-8進行編碼的,而是用 GBK,那么我們必須讀取到他們的二進制數(shù)據(jù),再通過GKB轉(zhuǎn)換成對應的文字;
          • 比如我們需要讀取的是一張圖片數(shù)據(jù)(二進制),再通過某些手段對圖片數(shù)據(jù)進行二次的處理(裁剪、格式轉(zhuǎn)換、旋轉(zhuǎn)、添加濾鏡),Node中有一個Sharp的庫,就是讀取圖片或者傳入圖片的Buffer對其再進行處理;
          • 比如在Node中通過TCP建立長連接,TCP傳輸?shù)氖亲止?jié)流,我們需要將數(shù)據(jù)轉(zhuǎn)成字節(jié)再進行傳入,并且需要知道傳輸字節(jié)的大?。头诵枰鶕?jù)大小來判斷讀取多少內(nèi)容);

          我們會發(fā)現(xiàn),對于前端開發(fā)來說,通常很少會和二進制打交道,但是對于服務器端為了做很多的功能,我們必須直接去操作其二進制的數(shù)據(jù);

          所以Node為了可以方便開發(fā)者完成更多功能,提供給了我們一個類Buffer,并且它是全局的。

          1.2. Buffer和二進制

          我們前面說過,Buffer中存儲的是二進制數(shù)據(jù),那么到底是如何存儲呢?

          • 我們可以將Buffer看成是一個存儲二進制的數(shù)組;
          • 這個數(shù)組中的每一項,可以保存8位二進制:00000000

          為什么是8位呢?

          • 在計算機中,很少的情況我們會直接操作一位二進制,因為一位二進制存儲的數(shù)據(jù)是非常有限的;

          • 所以通常會將8位合在一起作為一個單元,這個單元稱之為一個字節(jié)(byte);

          • 也就是說 1byte = 8bit,1kb=1024byte1M=1024kb;

          • 比如很多編程語言中的int類型是4個字節(jié),long類型是8個字節(jié);

          • 比如TCP傳輸?shù)氖亲止?jié)流,在寫入和讀取時都需要說明字節(jié)的個數(shù);

          • 比如RGB的值分別都是255,所以本質(zhì)上在計算機中都是用一個字節(jié)存儲的;

          也就是說,Buffer相當于是一個字節(jié)的數(shù)組,數(shù)組中的每一項對于一個字節(jié)的大?。?/p>

          如果我們希望將一個字符串放入到Buffer中,是怎么樣的過程呢?

          const buffer01 = new Buffer("why");

          console.log(buffer01);
          字符串存儲buffer的過程

          當然目前已經(jīng)不希望我們這樣來做了:

          VSCode的警告

          那么我們可以通過另外一個創(chuàng)建方法:

          const buffer2 = Buffer.from("why");
          console.log(buffer2);

          如果是中文呢?

          const buffer3 = Buffer.from("王紅元");
          console.log(buffer3);
          // <Buffer e7 8e 8b e7 ba a2 e5 85 83>
          const str = buffer3.toString();
          console.log(str);
          // 王紅元

          如果編碼和解碼不同:

          const buffer3 = Buffer.from("王紅元"'utf16le');
          console.log(buffer3);

          const str = buffer3.toString('utf8');
          console.log(str); // ?s?~CQ

          二. Buffer其他用法

          2.1. Buffer的其他創(chuàng)建

          Buffer的創(chuàng)建方式有很多:

          buffer的創(chuàng)建

          來看一下Buffer.alloc:

          • 我們會發(fā)現(xiàn)創(chuàng)建了一個8位長度的Buffer,里面所有的數(shù)據(jù)默認為00;
          const buffer01 = Buffer.alloc(8);

          console.log(buffer01); // <Buffer 00 00 00 00 00 00 00 00>

          我們也可以對其進行操作:

          buffer01[0] = 'w'.charCodeAt();
          buffer01[1] = 100;
          buffer01[2] = 0x66;
          console.log(buffer01);

          也可以使用相同的方式來獲?。?/p>

          console.log(buffer01[0]);
          console.log(buffer01[0].toString(16));

          2.2. Buffer和文件讀取

          文本文件的讀取:

          const fs = require('fs');

          fs.readFile('./test.txt', (err, data) => {
            console.log(data); // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
            console.log(data.toString()); // Hello World
          })

          圖片文件的讀?。?/p>

          fs.readFile('./zznh.jpg', (err, data) => {
            console.log(data); // <Buffer ff d8 ff e0 ... 40418 more bytes>
          });

          圖片文件的讀取和轉(zhuǎn)換:

          • 將讀取的某一張圖片,轉(zhuǎn)換成一張200x200的圖片;
          • 這里我們可以借助于 sharp 庫來完成;
          const sharp = require('sharp');
          const fs = require('fs');

          sharp('./test.png')
            .resize(10001000)
            .toBuffer()
            .then(data => {
              fs.writeFileSync('./test_copy.png', data);
            })

          三. Buffer的內(nèi)存分配

          事實上我們創(chuàng)建Buffer時,并不會頻繁的向操作系統(tǒng)申請內(nèi)存,它會默認先申請一個8 * 1024個字節(jié)大小的內(nèi)存,也就是8kb

          • node/lib/buffer.js:135行
          Buffer.poolSize = 8 * 1024;
          let poolSize, poolOffset, allocPool;

          const encodingsMap = ObjectCreate(null);
          for (let i = 0; i < encodings.length; ++i)
            encodingsMap[encodings[i]] = i;

          function createPool({
            poolSize = Buffer.poolSize;
            allocPool = createUnsafeBuffer(poolSize).buffer;
            markAsUntransferable(allocPool);
            poolOffset = 0;
          }
          createPool();

          假如我們調(diào)用Buffer.from申請Buffer:

          • 這里我們以從字符串創(chuàng)建為例
          • node/lib/buffer.js:290行
          Buffer.from = function from(value, encodingOrOffset, length{
            if (typeof value === 'string')
              return fromString(value, encodingOrOffset);
           
           // 如果是對象,另外一種處理情況
            // ...
          };

          我們查看fromString的調(diào)用:

          • node/lib/buffer.js:428行
          function fromString(string, encoding{
            let ops;
            if (typeof encoding !== 'string' || encoding.length === 0) {
              if (string.length === 0)
                return new FastBuffer();
              ops = encodingOps.utf8;
              encoding = undefined;
            } else {
              ops = getEncodingOps(encoding);
              if (ops === undefined)
                throw new ERR_UNKNOWN_ENCODING(encoding);
              if (string.length === 0)
                return new FastBuffer();
            }
            return fromStringFast(string, ops);
          }

          接著我們查看fromStringFast:

          • 這里做的事情是判斷剩余的長度是否還足夠填充這個字符串;
          • 如果不足夠,那么就要通過 createPool 創(chuàng)建新的空間;
          • 如果夠就直接使用,但是之后要進行 poolOffset的偏移變化;
          • node/lib/buffer.js:428行
          function fromStringFast(string, ops{
            const length = ops.byteLength(string);

            if (length >= (Buffer.poolSize >>> 1))
              return createFromString(string, ops.encodingVal);

            if (length > (poolSize - poolOffset))
              createPool();
            let b = new FastBuffer(allocPool, poolOffset, length);
            const actual = ops.write(b, string, 0, length);
            if (actual !== length) {
              // byteLength() may overestimate. That's a rare case, though.
              b = new FastBuffer(allocPool, poolOffset, actual);
            }
            poolOffset += actual;
            alignPool();
            return b;
          }

          四. Stream

          4.1. 認識Stream

          什么是流呢?

          • 我們的第一反應應該是流水,源源不斷的流動;
          • 程序中的流也是類似的含義,我們可以想象當我們從一個文件中讀取數(shù)據(jù)時,文件的二進制(字節(jié))數(shù)據(jù)會源源不斷的被讀取到我們程序中;
          • 而這個一連串的字節(jié),就是我們程序中的流;

          所以,我們可以這樣理解流:

          • 是連續(xù)字節(jié)的一種表現(xiàn)形式和抽象概念;
          • 流應該是可讀的,也是可寫的;

          在之前學習文件的讀寫時,我們可以直接通過 readFile或者 writeFile方式讀寫文件,為什么還需要流呢?

          • 直接讀寫文件的方式,雖然簡單,但是無法控制一些細節(jié)的操作;
          • 比如從什么位置開始讀、讀到什么位置、一次性讀取多少個字節(jié);
          • 讀到某個位置后,暫停讀取,某個時刻恢復讀取等等;
          • 或者這個文件非常大,比如一個視頻文件,一次性全部讀取并不合適;

          事實上Node中很多對象是基于流實現(xiàn)的:

          • http模塊的Request和Response對象;
          • process.stdout對象;

          官方:另外所有的流都是EventEmitter的實例:

          我們可以看一下Node源碼中有這樣的操作:

          Stream和EventEmitter關系

          流(Stream)的分類:

          • Writable:可以向其寫入數(shù)據(jù)的流(例如 fs.createWriteStream())。
          • Readable:可以從中讀取數(shù)據(jù)的流(例如 fs.createReadStream())。
          • Duplex:同時為Readable和的流Writable(例如 net.Socket)。
          • TransformDuplex可以在寫入和讀取數(shù)據(jù)時修改或轉(zhuǎn)換數(shù)據(jù)的流(例如zlib.createDeflate())。

          這里我們通過fs的操作,講解一下Writable、Readable,另外兩個大家可以自行學習一下。

          4.2. Readable

          之前我們讀取一個文件的信息:

          fs.readFile('./foo.txt', (err, data) => {
            console.log(data);
          })

          這種方式是一次性將一個文件中所有的內(nèi)容都讀取到程序(內(nèi)存)中,但是這種讀取方式就會出現(xiàn)我們之前提到的很多問題:

          • 文件過大、讀取的位置、結束的位置、一次讀取的大?。?/section>

          這個時候,我們可以使用 createReadStream,我們來看幾個參數(shù),更多參數(shù)可以參考官網(wǎng):

          • start:文件讀取開始的位置;
          • end:文件讀取結束的位置;
          • highWaterMark:一次性讀取字節(jié)的長度,默認是64kb;
          const read = fs.createReadStream("./foo.txt", {
            start3,
            end8,
            highWaterMark4
          });

          我們?nèi)绾潍@取到數(shù)據(jù)呢?

          • 可以通過監(jiān)聽data事件,獲取讀取到的數(shù)據(jù);
          read.on("data", (data) => {
            console.log(data);
          });

          我們也可以監(jiān)聽其他的事件:

          read.on('open', (fd) => {
            console.log("文件被打開");
          })

          read.on('end', () => {
            console.log("文件讀取結束");
          })

          read.on('close', () => {
            console.log("文件被關閉");
          })

          甚至我們可以在某一個時刻暫停和恢復讀?。?/p>

          read.on("data", (data) => {
            console.log(data);

            read.pause();

            setTimeout(() => {
              read.resume();
            }, 2000);
          });

          4.3. Writable

          之前我們寫入一個文件的方式是這樣的:

          fs.writeFile('./foo.txt'"內(nèi)容", (err) => {
            
          });

          這種方式相當于一次性將所有的內(nèi)容寫入到文件中,但是這種方式也有很多問題:

          • 比如我們希望一點點寫入內(nèi)容,精確每次寫入的位置等;

          這個時候,我們可以使用 createWriteStream,我們來看幾個參數(shù),更多參數(shù)可以參考官網(wǎng):

          • flags:默認是w,如果我們希望是追加寫入,可以使用 a或者 a+;
          • start:寫入的位置;

          我們進行一次簡單的寫入

          const writer = fs.createWriteStream("./foo.txt", {
            flags"a+",
            start8
          });

          writer.write("你好啊", err => {
            console.log("寫入成功");
          });

          如果我們希望監(jiān)聽一些事件:

          writer.on("open", () => {
            console.log("文件打開");
          })

          writer.on("finish", () => {
            console.log("文件寫入結束");
          })

          writer.on("close", () => {
            console.log("文件關閉");
          })

          我們會發(fā)現(xiàn),我們并不能監(jiān)聽到 close 事件:

          • 這是因為寫入流在打開后是不會自動關閉的;
          • 我們必須手動關閉,來告訴Node已經(jīng)寫入結束了;
          • 并且會發(fā)出一個 finish 事件的;
          writer.close();

          writer.on("finish", () => {
            console.log("文件寫入結束");
          })

          writer.on("close", () => {
            console.log("文件關閉");
          })

          另外一個非常常用的方法是 end

          • end方法相當于做了兩步操作:write傳入的數(shù)據(jù)和調(diào)用close方法;
          writer.end("Hello World");

          4.4. pipe方法

          正常情況下,我們可以將讀取到的 輸入流,手動的放到 輸出流中進行寫入:

          const fs = require('fs');
          const { read } = require('fs/promises');

          const reader = fs.createReadStream('./foo.txt');
          const writer = fs.createWriteStream('./bar.txt');

          reader.on("data", (data) => {
            console.log(data);
            writer.write(data, (err) => {
              console.log(err);
            });
          });

          我們也可以通過pipe來完成這樣的操作:

          reader.pipe(writer);

          writer.on('close', () => {
            console.log("輸出流關閉");
          })

          備注:所有內(nèi)容首發(fā)于公眾號,之后會更新Flutter、TypeScript、React、Node、uniapp、mpvue、數(shù)據(jù)結構與算法等等一系列教程,也會更新一些自己的學習心得等,歡迎大家關注

          公眾號


          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美性爱在线中文字幕 | 五月天亚洲综合小说网 | 男女拍拍视频 j.zyme.xin | 狠狠操大香蕉 | 亚洲AV无码成人精品一区 |