1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Node.js底層知識 - 理解Buffer

        共 9753字,需瀏覽 20分鐘

         ·

        2020-12-25 19:15

        一. 認(rèn)識Buffer

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

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

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

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

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

        但是對于服務(wù)器來說是不一樣的:

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

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

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

        1.2. Buffer和二進(jìn)制

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

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

        為什么是8位呢?

        • 在計(jì)算機(jī)中,很少的情況我們會直接操作一位二進(jìn)制,因?yàn)橐晃欢M(jìn)制存儲的數(shù)據(jù)是非常有限的;

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

        • 也就是說 1byte = 8bit1kb=1024byte,1M=1024kb;

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

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

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

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

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

        const buffer01 = new Buffer("why");

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

        當(dāng)然目前已經(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ù)默認(rèn)為00;
        const buffer01 = Buffer.alloc(8);

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

        我們也可以對其進(jìn)行操作:

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

        也可以使用相同的方式來獲取:

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

        2.2. Buffer和文件讀取

        文本文件的讀?。?/p>

        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
        })

        圖片文件的讀取:

        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)存分配

        事實(shí)上我們創(chuàng)建Buffer時,并不會頻繁的向操作系統(tǒng)申請內(nèi)存,它會默認(rèn)先申請一個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)建新的空間;
        • 如果夠就直接使用,但是之后要進(jìn)行 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. 認(rèn)識Stream

        什么是流呢?

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

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

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

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

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

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

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

        官方:另外所有的流都是EventEmitter的實(shí)例:

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

        Stream和EventEmitter關(guān)系

        流(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,另外兩個大家可以自行學(xué)習(xí)一下。

        4.2. Readable

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

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

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

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

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

        • start:文件讀取開始的位置;
        • end:文件讀取結(jié)束的位置;
        • highWaterMark:一次性讀取字節(jié)的長度,默認(rèn)是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("文件讀取結(jié)束");
        })

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

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

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

          read.pause();

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

        4.3. Writable

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

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

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

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

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

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

        我們進(jìn)行一次簡單的寫入

        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("文件寫入結(jié)束");
        })

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

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

        • 這是因?yàn)閷懭肓髟诖蜷_后是不會自動關(guān)閉的;
        • 我們必須手動關(guān)閉,來告訴Node已經(jīng)寫入結(jié)束了;
        • 并且會發(fā)出一個 finish 事件的;
        writer.close();

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

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

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

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

        4.4. pipe方法

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

        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("輸出流關(guān)閉");
        })

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

        公眾號


        瀏覽 55
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            成人免费版 欧美州 | 国产九色av刺激露脸对白 | 天天操成人网 | 秋霞一区二区 | 国产精品日韩在线 | 丝袜高跟国产成人精品一区 | 操逼操网站 | 武侠古典AV | 精品国产乱码久久久久久老虎 | 毛片基地在线 |