ホームページ  >  記事  >  ウェブフロントエンド  >  Node.js の Buffer の簡単な分析とイベント ループについての話

Node.js の Buffer の簡単な分析とイベント ループについての話

青灯夜游
青灯夜游転載
2022-05-18 20:30:462480ブラウズ

この記事では、Node のバッファについて理解し、ブラウザのイベント ループについて説明します。皆様のお役に立てれば幸いです。

Node.js の Buffer の簡単な分析とイベント ループについての話

#バッファの使用


# #データのバイナリ

    コンピュータ内のすべてのコンテンツ (テキスト、数値、画像、オーディオ、ビデオ) は、最終的にはバイナリで表現されます
  • JS

    は、通常ユーザーに表示される文字列などの非常に直感的なデータを直接処理できます。

  • しかし、JS は画像も処理できます
  • 実際、Web ページでは、画像は常にブラウザによって処理されてきました
    • JS
    • または HTML 、ブラウザに画像のアドレスを伝えることだけを担当します ブラウザは画像を取得するリクエストを送信し、最終的に画像をレンダリングする責任があります
    # #ただし、サーバー側では異なります
  • ##サーバー側では、比較的多くのローカル ファイル タイプを処理する必要があります

    #たとえば、テキストを保存するファイルは使用されません

    utf -8
    • GBK
    • でエンコードされているため、そのバイナリ データを読み取り、GKB
    • を通じて対応するテキストに変換する必要があります。たとえば、読み取られたものは画像である必要があります。画像データ(バイナリ)を何らかの手段(トリミング、形式変換、回転、フィルターの追加)で2回処理したNode内にsharp##というファイルがあり、#のライブラリが読み込みを担当しますNode
    • TCP## などを介して、画像または受信画像の
    • Buffer を処理し、# を処理します。 #長い接続を確立します。TCP はバイトを送信します。データを渡す前にデータをバイトに変換する必要があり、送信されたバイトのサイズを知る必要があります (クライアントはサイズに基づいて読み取るコンテンツの量を判断する必要があります)
    • バッファとバイナリ

フロントエンド開発では、通常、バイナリを扱うことはほとんどありません。しかし、サーバー側の場合、多くの機能を実装するには、そのバイナリ データを直接操作する必要があります。関数を使用するには、Buffer という名前のクラスが提供されます。これはグローバル

    前に述べたように、バッファー ストアはバイナリ データです。では、どのように保存されるのでしょうか。
  • バッファは、バイナリ数を格納する配列と考えることができます。
  • この配列内の各項目は、バイナリの

    8 ビットを保存できます: 00000000、これはちょうど 1 バイトです

    • なぜ 8 ビットなのでしょうか?
    • コンピュータでは、2 進数に保存されるデータは非常に限られているため、2 進数を直接操作するケースはほとんどありません。したがって、通常は 8 ビットが次のように結合されます。この単位はバイトと呼ばれます (byte)
    つまり、
  • 1 バイト = 8 ビット
    1kb = 1024 バイト
  • ,

    1M = 1024kb

    ,
      1 G = 1024 M
    • たとえば、多くのプログラミング言語の int 型は ## です。 #4
    • バイト、
    • long 型は 8 バイト たとえば、TCP は、書き込み時、読み取り時、および読み取り時にバイト ストリームを送信します。読み取りの場合、バイト数
    • を指定する必要があります。たとえば、
    • RGB の値は 255 であるため、基本的に 1 つの単語が で使用されます。セクション ストア
    • バッファと文字列
    • バッファ バイト配列と同等で、配列内の各項目のサイズは 1 バイトです
文字列をバッファに入れたい場合は、どうすればよいでしょうか?種類は何ですか?プロセスの?

文字列を Buffer クラスに直接渡し、buffer インスタンスを作成します。

    英語の文字列には特徴があり、各文字はバイトのバイナリ エンコーディングに対応します。
  • const message = 'Hello'
    // 使用new关键字创建buffer实例,但这种创建方法已经过期了
    const buffer = new Buffer(message)
    console.log(buffer); // <Buffer 48 65 6c 6c 6f>
    console.log(buffer.toString()); // Hello

  • 中国語文字列のエンコーディングとデコーディング

    • buffer デフォルトのエンコーディング
    • utf-8
    であるため、次のコードでは、
  • Buffer
クラスは utf-8 エンコーディングを使用して文字列をエンコードし、さらに utf -8 を使用して文字列をデコードします

中国語の文字列には特徴があり、utf-8 エンコードでは、1 つのテキストがバイナリ エンコードの 3 バイトに対応します

const message = &#39;你好啊&#39;
// 使用Buffer.from对我们的字符串进行解码
const buffer = Buffer.from(message)
console.log(buffer); // <Buffer e4 bd a0 e5 a5 bd e5 95 8a>
// buffer实例中有个toString方法可以对编码进行解码
console.log(buffer.toString()); // &#39;你好啊&#39;
  • #何が起こるかエンコードとデコードで異なる形式のエンコードを使用する場合はどうすればよいですか? デコードされたものは、最初にエンコードした文字列ではないことは疑いの余地がありません。
  • const message = &#39;你好啊&#39;
    const buffer = Buffer.from(message, &#39;utf16le&#39;)
    console.log(buffer); // <Buffer 60 4f 7d 59 4a 55>
    console.log(buffer.toString()); // `O}YJU
  • 残りの部分は、バッファの作成方法
  • buffer

    を作成するにはさまざまな方法があります。ここでは、
      alloc
    • を通じて
    • Buffer
    ## を作成できます。 .#
    • 我们可以直接对buffer实例以数组的形式对每一位进行修改

      • 如果修改的是一个十进制数字,那它会自动帮助我们转化成16进制的数字
      • 如果修改的是一个十六进制数字,那么就会直接写入
    // 其可以指定我们buffer的位数,比如这里传递进去的是8,那么创建出来的buffer就有8个元素,且每个元素对应的二进制数都是0
    const buffer = Buffer.alloc(8)
    console.log(buffer); // <Buffer 00 00 00 00 00 00 00 00>
    // 赋值为十进制数字的话,buffer会帮我们转化为16进制数字再写入到对应的位置
    buffer[0] = 88 
    // 在js中,以0x开头的就表示为16进制的数字
    buffer[1] = 0x88
    console.log(buffer); // <Buffer 58 88 00 00 00 00 00 00>

    Buffer和文件操作

    1、文本文件

    • 如果未指定字符编码,则不进行解码,直接返回原始的 buffer,也就是文件内容结果utf-8编码后的二进制数
    const fs = require(&#39;fs&#39;)
    fs.readFile(&#39;./a.txt&#39;, (err, data) => {
      console.log(data); // <Buffer e5 93 88 e5 93 88>
    })
    • 编码和解码用的都是utf-8,则可以得到文件中正确的内容
    const fs = require(&#39;fs&#39;)
    // encoding表示解码所用的字符编码,编码默认为utf-8
    fs.readFile(&#39;./a.txt&#39;, { encoding: &#39;utf-8&#39; }, (err, data) => {
      console.log(data); // 哈哈
    })
    • 如果编码和解码所用的字符编码不同,则最终读取出来的内容会乱码
    const fs = require(&#39;fs&#39;)
    // 编码用的是utf16le字符编码,解码使用的是utf-8格式,肯定是解不是正确的内容的
    fs.readFile(&#39;./a.txt&#39;, { encoding: &#39;utf16le&#39; }, (err, data) => {
      console.log(data); // 鏥袓
    })
    // 以上代码和下面代码类似
    const msg = &#39;哈哈&#39;
    const buffer = Buffer.from(msg, &#39;utf-8&#39;)
    console.log(buffer.toString(&#39;utf16le&#39;)); // 鏥袓

    2、图片文件

    • 对图片编码进行拷贝,达到复制图片的目的

      • 读取图片的时候不要指定encoding属性,因为字符编码只有在读取文本文件的时候才有用
    const fs = require(&#39;fs&#39;)
    
    fs.readFile(&#39;./logo.png&#39;, (err, data) => {
      console.log(data); // 打印出来的是图片文件对应的二进制编码
      
      // 我们还可以将图片编码写入到另一个文件当中,相当于我们将该图片拷贝了一份
      fs.writeFile(&#39;./bar.png&#39;, data, err => {
        console.log(err); 
      })
    })
    • 对图片进行翻转、裁剪等操作,可以使用sharp这个库
    const sharp = require(&#39;sharp&#39;)
    
    // 将logo.png这张图片裁剪成200x300后拷贝到文件bax.png中
    sharp(&#39;./logo.png&#39;)
      .resize(200, 300)
      .toFile(&#39;./bax.png&#39;, (err, info) => {
        console.log(err);
      })
    
    // 还可以将图片文件先转为buffer,然后在写入到文件中,也可以实现拷贝图片的目的
    sharp(&#39;./logo.png&#39;)
      .resize(300, 300)
      .toBuffer()
      .then(data => {
        fs.writeFile(&#39;./baa.png&#39;, data, err => {
          console.log(err);
        })
      })

    Buffer的创建过程

    • 事实上我们创建Buffer时,并不会频繁的向操作系统申请内存,它会默认先申请一个8 * 1024 个字节大小的内存,也就是8kb
    • 等到内存不够或者快用完的时候才会去申请新的内存

    事件循环和异步IO


    什么是事件循环?

    • 事件循环是什么?

      • 事实上我把事件循环理解成我们编写的JS和浏览器或者Node之间的一个桥梁
    • 浏览器的事件循环是一个我们编写的JS代码和浏览器API调用(setTimeoutAJAX监听事件等)的一个桥梁,桥梁之间通过回调函数进行沟通
    • Node的事件循环是一个我们编写的JS代码和系统调用(file systemnetwor等)之间的一个桥梁,,桥梁之间也是通过回调函数进行沟通的

    Node.js の Buffer の簡単な分析とイベント ループについての話

    进程和线程

    进程和线程是操作系统中的两个概念:

    • 进程(process):计算机已经运行的程序
    • 线程(thread):操作系统能够运行运算调度的最小单位,所以CPU能够直接操作线程

    听起来很抽象,我们直观一点解释:

    • 进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程)
    • 线程:每一个进程中,都会启动一个线程用来执行程序中的代码,这个线程被称之为主线程
    • 所以我们也可以说进程是线程的容器

    再用一个形象的例子解释

    • 操作系统类似于一个工厂
    • 工厂中有很多车间,这个车间就是进程
    • 每个车间可能有一个以上的工人在工厂,这个工人就是线程

    多进程多线程开发

    操作系统是如何做到同时让多个进程(边听歌、边写代码、边查阅资料)同时工作呢?

    Node.js の Buffer の簡単な分析とイベント ループについての話

    • 这是因为CPU的运算速度非常快,他可以快速的在多个进程之间迅速的切换
    • 当我们的进程中的线程获取到时间片时,就可以快速执行我们编写的代码
    • 对于用户来说是感受不到这种快速的切换的

    Node.js の Buffer の簡単な分析とイベント ループについての話

    浏览器和JavaScript

    • 我们经常会说JavaScript是单线程的,但是JS的线程应该有自己的容器进程:浏览器或者Node

    • 浏览器是一个进程吗,它里面只有一个线程吗?

      • 目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出
      • 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程
    • 但是JavaScript的代码执行是在一个单独的线程中执行的

      • 这就意味着JS的代码,在同一时刻只能做一件事
      • 如果这件事是非常耗时的,就以为这当前的线程就会被阻塞

    JavaScript的执行过程

    函数要被压入函数调用栈中后才会被执行,下面我们来分析下代码的执行过程

    const message = &#39;Hello World&#39;
    
    console.log(message);
    
    function sum(num1, num2) {
      return num1 + num2
    }
    
    function foo() {
      const result = sum(20, 30)
      console.log(result);
    }
    
    foo()
    • 我们JS的代码其实也可以像其它编程语言一样可以看成是在main函数中执行的
    • 那么首先我们要将main函数压入函数调用栈中
    • 定义变量message
    • 执行log函数,log函数会被放入到函数调用栈中,执行完后出栈
    • 调用foo函数,foo函数被压入函数调用栈中,但是执行过程中又需要调用sum函数
    • 所以sum函数会被压入到函数调用栈中,sum函数执行完毕后出栈
    • 此时foo函数也得到了sum函数返回的值,并执行了赋值操作,但又遇到了log函数
    • 所以又要将log函数压入到调用栈,log函数被执行完毕,出栈后foo函数也执行完毕,foo函数出栈
    • foo函数执行完后,整个js代码执行完毕,main函数出栈

    浏览器的事件循环

    如果在执行JS代码的过程中,有异步操作呢?

    • 比如中间我们插入了一个setTimeout的函数调用
    • 那么setTimeout这个函数被放入到调用栈中,执行会立即结束,并不会阻塞后续代码的执行

    那么,往setTimeout函数里面传入的函数(我们称之为timer函数),会在什么时候被执行呢?

    • 事实上,setTimeout是调用了web api,浏览器会提前会将回调函数存储起来,在合适的时机,会将timer函数加入到一个事件队列中
    • 事件队列中的函数,会被放入到函数调用栈中,在调用栈中被执行

    为什么setTimeout不会阻塞代码的执行呢?就是因为浏览器里面维护了一个非常非常重要的东西——事件循环

    • 浏览器中会通过某种方式帮助我们保存setTimeout中的回调函数的,比较常用的方法就是保存到一个红黑树里面

    • 等到setTimeout定时器时间到达的时候,它就会将我们的timer回调函数从保存的地方取出来并放入到事件队列里面

    • 事件循环一旦发现我们的队列中有东西了,并且当前函数调用栈是空的,其它同步代码也执行完之后,就会将我们队列中的回调函数依次出列放入到函数调用栈中执行(队列中前一个函数出栈后,下一个函数才会入栈)

    当然事件队列中不一定只有一个事件,比如说在某个过程中用户点击了浏览器当中的某个按钮,我们可能对这个按钮的点击做了一个监听,对应了一个回调函数,那个回调函数也会被加入到我们的队列里面的,执行顺序按照它们在事件队列中的顺序执行。还有我们发送ajax请求的回调,也是加入到事件队列里面的

    总结:其实事件循环是一个很简单的东西,它就是在某一个特殊的情况下,需要去执行某一个回调的时候,它就把提前保存好的回调塞入事件队列里面,事件循环再给它取出来放入到函数调用栈中

    Node.js の Buffer の簡単な分析とイベント ループについての話

    宏任务与微任务

    但是事件循环中并非只维护一个队列,事实上是有两个队列,而且队列中的任务执行一定会等到所有的script都执行完毕后

    • 宏任务队列(macrotask queue):ajaxsetTimeoutsetIntervalDOM监听、UI Rendering
    • 微任务队列(microtask queue):Promisethen回调、Mutation Observer APIqueueMicrotask()

    那么事件循环对于两个队列的优先级是怎么样的呢?

    • main script中的代码优先执行(编写的顶层script代码)
    • 在执行任何一个宏任务之前(不是队列,是一个宏任务),都会先查看微任务队列中是否有任务需要执行
      • 也就是宏任务执行之前,必须保证微任务队列是空的
      • 如果不为空,那么就优先执行微任务队列中的任务(回调)

    面试题

    考点:main stciptsetTimeoutPromisethenqueueMicrotask

    setTimeout(() => {
      console.log(&#39;set1&#39;);4
      new Promise(resolve => {
        resolve()
      }).then(resolve => {
        new Promise(resolve => {
          resolve()
        }).then(() => {
          console.log(&#39;then4&#39;);
        })
        console.log(&#39;then2&#39;);
      })
    })
    
    new Promise(resolve => {
      console.log(&#39;pr1&#39;);
      resolve()
    }).then(() => {
      console.log(&#39;then1&#39;);
    })
    
    setTimeout(() => {
      console.log(&#39;set2&#39;);
    })
    
    console.log(2);
    
    queueMicrotask(() => {
      console.log(&#39;queueMicrotask&#39;);
    })
    
    new Promise(resolve => {
      resolve()
    }).then(() => {
      console.log(&#39;then3&#39;);
    })
    
    // pr1
    // 2
    // then1
    // queueMicrotask
    // then3
    // set1
    // then2
    // then4
    // set2
    • setTimeout会立即压入函数调用栈,执行完毕后立即出栈,其timer函数被放入到宏任务队列中

    • 传入Promise类的函数会被立即执行,其并不是回调函数,所以会打印出pr1,并且由于执行了resolve方法,所以该Promise的状态会立即变为fulfilled,这样then函数执行的时候其对应的回调函数就会被放入到微任务队列中

    • 又遇到了一个setTimeout函数,压栈出栈,其timer函数会被放入到宏任务队列中

    • 遇到console.log语句,函数压栈后执行打印出了2,然后出栈

    • 这里通过queueMicrotask绑定了个函数,该函数会被放入到微任务队列中

    • 又遇到了new Promise语句,但是其立即就将promise的状态改为了fulfilled,所以then函数对应的回调也被放入到了微任务队列中

    • 由于同步脚本代码已经执行完毕,现在事件循环开始要去把微任务队列和宏任务对垒的任务按照优先级顺序放入到函数调用栈中执行了,注意:微任务的优先级比宏任务高,每次想要执行宏任务之前都要看看微任务队列里面是否为空,不为空则需要先执行微任务队列的任务

    • 第一个微任务是打印then1,第二个微任务是打印queueMicrotask,第三个微任务是打印then3,执行完毕后,就开始去执行宏任务

    • 第一个宏任务比较复杂,首先会打印set1,然后执行了一个立即变换状态的new promise语句,其then回调会被放入到微任务队列中,注意现在微任务队列可不是空的,所以需要执行优先级较高的微任务队列,相当于该then回调被立即执行了,又是相同的new Promise语句,其对应的then对调被放入到微任务队列中,注意new Promise语句后面还有一个console函数,该函数会在执行完new Promise语句后立即执行,也就是打印then2,现在微任务对垒还是有一项任务,所以接下来就是打印then4。目前为止,微任务队列已经为空了,可以继续执行宏任务队列了

    • 所以接下里的宏任务set2会被打印,宏任务执行完毕

    • 整个代码的打印结果是:pr1 -> 2 -> then1 -> queueMicrotask -> then3 -> set1 -> then2 -> then4 -> set2

    面试题

    考点:main scriptsetTimeoutPromisethenqueueMicrotaskawaitasync

    知识补充:async、await是Promise的一个语法糖,在处理事件循环问题时

    • 我们可以将await关键字后面执行的代码,看做是包裹在new Promise((resolve,rejcet) => { 函数执行 })中的代码
    • await语句后面的代码,可以看做是上一个Promise中的then(res => {函数执行})中的代码
    async function async1() {
      console.log(&#39;async1 start&#39;);
      await async2()
      console.log(&#39;async1 end&#39;);
    }
    
    async function async2() {
      console.log(&#39;async2&#39;);
    }
    
    console.log(&#39;script start&#39;);
    
    setTimeout(() => {
      console.log(&#39;setTimeout&#39;);
    }, 0)
    
    async1()
    
    new Promise(resolve => {
      console.log(&#39;promise1&#39;);
      resolve()
    }).then(() => {
      console.log(&#39;promise2&#39;);
    })
    
    console.log(&#39;script end&#39;);
    
    // script start
    // async1 start
    // async2
    // promise1
    // script end
    // async1 end
    // promise2
    // setTimeout
    • 一开始都是函数的定义,不需要压入函数调用栈中执行,直到遇到第一个console语句,压栈后执行打印script start后出栈

    • 遇到第一个setTimeout函数,其对应的timer会被放入到宏任务队列中

    • async1函数被执行,首先打印出async1 start,然后又去执行await语句后面的async2函数,因为前面也说了,将await关键字后面的函数看成是new Promise里面的语句,这个函数是会被立即执行的,所以async2会被打印出来,但该await语句后面的代码相当于是放入到then回调中的,也就是说console.log('async1 end')这行代码被放入到了微任务队列里

    • コードは実行を続け、新しい Promise ステートメントに遭遇するため、promise1 がすぐに出力され、then コールバックの関数がマイクロタスク キューに入れられます

    • 最後のコンソール関数はprintingscript endを実行し、同期コードが実行され、イベントループはマクロタスクキューとマイクロタスクキュー内のタスクを実行する必要があります

    • 最初にマイクロタスク キューに移動します。最初のマイクロタスクに対応する print ステートメントが実行されます。これは、async1 end が出力され、次に promise2 が出力されることを意味します。このとき、マイクロタスク キューは空であり、マクロタスク キュー内のタスクが開始されます。

    • タイマー関数に対応する setTimeout が出力されます。マクロタスクも実行され、最終的な印刷シーケンスは次のとおりです: script start ->async1 start ->async2 ->promise1 ->script end ->async1 end ->promise2 ->setTimeout

    ノード関連の知識の詳細については、nodejs チュートリアル を参照してください。

以上がNode.js の Buffer の簡単な分析とイベント ループについての話の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。