Maison  >  Article  >  interface Web  >  Cet article vous donnera une compréhension approfondie de la classe Buffer dans Node

Cet article vous donnera une compréhension approfondie de la classe Buffer dans Node

青灯夜游
青灯夜游avant
2022-12-12 19:36:131823parcourir

Cet article vous donnera une compréhension approfondie de la classe Buffer dans Node J'espère qu'il vous sera utile !

Cet article vous donnera une compréhension approfondie de la classe Buffer dans Node

Avant la sortie du TypedArray, le langage JavaScript ne pouvait pas bien gérer les données binaires brutes(données binaires brutes) En effet, au début, JavaScript était principalement utilisé dans les navigateurs comme langage de script. , il existe donc très peu de scénarios dans lesquels des données binaires natives doivent être traitées. Après la sortie de Node, étant donné que les applications côté serveur doivent traiter un grand nombre de flux binaires tels que la lecture et écriture de fichiers, les connexions TCP, etc., Node a défini un nouveau type de données Buffer en plus de JavaScript. (V8) . Étant donné que Buffer est largement utilisé dans les applications Node, ce n'est qu'en maîtrisant véritablement son utilisation que vous pourrez écrire de meilleures applications Node. [Tutoriels associés recommandés : Tutoriel vidéo Nodejs, Enseignement de la programmation]

Bases du binaire


Avant d'introduire formellement l'utilisation spécifique de Buffer, passons brièvement en revue les connaissances sur le binaire.

En tant que programmeurs, nous devrions tous être familiers avec le binaire, car toutes les données sous-jacentes de l'ordinateur sont stockées au format binaire. En d’autres termes, les fichiers de votre ordinateur, qu’il s’agisse de texte brut, d’images ou de vidéos, sont composés des deux chiffres 01 présents sur le disque dur de l’ordinateur. En informatique, on appelle un seul nombre 0 ou 1 un bit(bit), et 8 bits peuvent former un byte(byte). Si le nombre décimal 16 est représenté par 1 octet, la structure de stockage sous-jacente est : 截屏2022-10-15 下午2.23.13.pngOn peut voir que si 16 est représenté en binaire, il y a 6 chiffres de plus par rapport à la représentation décimale Si le nombre est plus grand, Si le nombre de. les chiffres binaires sont plus grands, il nous sera très gênant de lire et d'écrire. Pour cette raison, les programmeurs aiment généralement utiliser hexadécimal (hexadécimal) pour représenter les données au lieu d'utiliser directement le binaire. Par exemple, lorsque nous écrivons du CSS, la valeur de color utilise un caractère hexadécimal (comme #FFFFFF) au lieu d'un groupe. de 0 et de 1.

Encodage des caractèresPuisque la couche inférieure de toutes les données est binaire et que les données transmises sur le réseau sont également binaires, pourquoi les articles que nous lisons maintenant sont-ils

chinois

au lieu d'un tas de 0 et 1 ? Nous présenterons ici le concept de encodage de caractères. Le soi-disant Encodage de caractères est simplement une table de relations de mappage, qui représente la façon dont les caractères (caractères chinois, caractères anglais ou autres caractères) correspondent à des nombres binaires (comprenant plusieurs octets). Par exemple, si nous utilisons le familier ascii pour encoder, la représentation binaire du caractère anglais a est 0b01100001 (0b est le préfixe d'un nombre binaire). Par conséquent, lorsque notre ordinateur lit la chaîne 0b01100001 de données binaires à partir d'un fichier codé en ascii, le caractère a sera affiché à l'écran et le caractère a sera également enregistré sur l'ordinateur. la donnée binaire transmise sur le réseau est 0b01100001. En plus des codes ascii, les encodages de caractères courants incluent utf-8 et utf-16, etc.

Buffer

Après avoir maîtrisé les
connaissances binaires

de base et les concepts de encodage de caractères, nous pouvons enfin apprendre Buffer de manière formelle. Jetons un coup d'œil à la définition officielle de Buffer :

L'instance
, vous obtiendrez une chaîne de valeurs en valeurs hexadécimales.

En termes simples, le soi-disant Buffer est un espace mémoire de taille fixe alloué par Node en dehors de la mémoire tas V8. Lorsque Buffer est imprimé à l'aide de console.log, une chaîne de valeurs exprimées en hexadécimal sera imprimée en unités de octets.

Créer un Buffer

Après avoir compris les concepts de base de Buffer, créons un objet Buffer. Il existe de nombreuses façons de créer Buffer, les plus courantes sont Buffer.alloc, Buffer.allocUnsafe et Buffer.from.

Buffer.alloc(size[, fill[, encoding]])

C'est la façon la plus courante de créer un Buffer. Il vous suffit de transmettre la taille du Buffer

const buff = Buffer.alloc(5)

console.log(buff)
// Prints: <Buffer 00 00 00 00 00>

Dans le code ci-dessus, j'ai créé. un tampon d'une taille de 5 octetsZone tampon, la fonction console.log imprimera cinq nombres hexadécimaux consécutifs, indiquant le contenu actuellement stocké dans le tampon. Nous pouvons voir que le Buffer actuel est rempli de 0, ce qui est le comportement par défaut de Node. Nous pouvons définir les deux paramètres suivants fill et encoding pour spécifier du contenu supplémentaire à remplir lors de l'initialisation.

Il convient de mentionner ici que j'ai utilisé l'objet global Buffer de Node dans le code ci-dessus sans l'importer explicitement à partir du package node:buffer Ceci est entièrement dû à la commodité de l'écriture et du développement réel. Le plus tard devrait. être écrit comme :

import { Buffer } from &#39;node:buffer&#39;

Buffer.allocUnsafe(size)

Buffer.allocUnsafeLa plus grande différence entre Buffer.alloc est que l'espace mémoire appliqué pour l'utilisation de la fonction allocUnsafe est none. initialisé, ce qui signifie que les données utilisées la dernière fois peuvent encore rester, il y aura donc des problèmes de sécurité des données. La fonction allocUnsafe reçoit un paramètre size comme taille de la zone tampon :

const buff = Buffer.allocUnsafe(5)

console.log(buff)
// Prints (实际内容可能有出入): <Buffer 8b 3f 01 00 00>

À en juger par les résultats de sortie ci-dessus, nous ne pouvons pas contrôler le contenu du tampon alloué à l'aide de Buffer.allocUnsafe. C'est précisément parce que la mémoire allouée n'est pas initialisée que cette fonction alloue Buffer plus rapidement que Buffer.alloc Dans le développement réel, nous devrions faire un choix en fonction de nos besoins réels.

Buffer.from

Cette fonction est notre fonction la plus couramment utilisée pour créer un tampon. Elle a de nombreuses surcharges différentes, ce qui signifie que la transmission de différents paramètres aura des comportements différents. Jetons un coup d'œil à quelques surcharges courantes :

Buffer.from(string[, encoding])

Lorsque le premier paramètre que nous transmettons est de type string, Buffer.from sera basé sur la chaîne The encoding (paramètre encoding, la valeur par défaut est utf8) génère la représentation binaire correspondant à la chaîne. Prenons un exemple :

const buff = Buffer.from(&#39;你好世界&#39;)

console.log(buff)
// Prints: <Buffer e4 bd a0 e5 a5 bd e4 b8 96 e7 95 8c>
console.log(buff.toString())
// Prints: &#39;你好世界&#39;
console.log(buff.toString(&#39;ascii&#39;))
// Prints: &#39;&#39;d= e%=d8\x16g\x15\f&#39;&#39;

Dans l'exemple ci-dessus, j'ai utilisé la chaîne "Hello World" pour terminer l'initialisation du Buffer Comme je n'ai pas passé le deuxième paramètre encoding, la valeur par défaut est utf8 coding. Plus tard, en examinant la sortie du premier console.log, nous pouvons constater que même si la chaîne que nous avons transmise ne contient que quatre caractères, le Buffer initialisé a 12 octets, car utf8 est en cours d'encodage. Un caractère chinois sera représenté en utilisant 3 octets. Ensuite, nous utilisons la méthode buff.toString() pour afficher le contenu du buff. Puisque le format de sortie d'encodage par défaut de la méthode toString est utf8, nous pouvons voir que le deuxième console.log peut correctement générer le résultat. contenu stocké dans le buff. Cependant, dans le troisième console.log, nous précisons que le type d'encodage des caractères est ascii. À ce stade, nous verrons un tas de caractères tronqués. En voyant cela, je pense que vous devez avoir une compréhension plus profonde du Encodage des caractères que j'ai mentionné précédemment.

Buffer.from(buffer)

Lorsque le paramètre reçu par Buffer.from est un objet tampon, Node créera une nouvelle instance Buffer, puis copiera le contenu du tampon transmis dans le nouvel objet Buffer.

const buf1 = Buffer.from(&#39;buffer&#39;)
const buf2 = Buffer.from(buf1)

console.log(buf1)
// Prints: <Buffer 62 75 66 66 65 72>
console.log(buf2)
// Prints: <Buffer 62 75 66 66 65 72>

buf1[0] = 0x61

console.log(buf1.toString())
// Prints: auffer
console.log(buf2.toString())
// Prints: buffer

Dans l'exemple ci-dessus, nous avons d'abord créé un objet Buffer

buf1, le contenu qui y est stocké est la chaîne "buffer", puis initialisé un nouvel objet Buffer buf2 via cet objet Buffer. À ce moment-là, nous avons changé le premier octet de buf1 en (l'encodage de a), et nous avons constaté que la sortie de buf1 devenait 0x61auffer, tandis que le contenu de buf2 n'a pas changé, ce qui confirme Buffer. from(buffer) est la vue de la copie des données.

?注意:当Buffer的数据很大的时候,Buffer.from拷贝数据的性能是很差的,会造成CPU占用飙升,主线程卡死的情况,所以在使用这个函数的时候一定要清楚地知道Buffer.from(buffer)背后都做了什么。笔者就在实际项目开发中踩过这个坑,导致线上服务响应缓慢!

Buffer.from(arrayBuffer[, byteOffset[, length]])

说完了buffer参数,我们再来说一下arrayBuffer参数,它的表现和buffer是有很大的区别的。ArrayBuffer是ECMAScript定义的一种数据类型,它简单来说就是一片你不可以直接(或者不方便)使用的内存,你必须通过一些诸如Uint16ArrayTypedArray对象作为View来使用这片内存,例如一个Uint16Array对象的.buffer属性就是一个ArrayBuffer对象。当Buffer.from函数接收一个ArrayBuffer作为参数时,Node会创建一个新的Buffer对象,不过这个Buffer对象指向的内容还是原来ArrayBuffer的内容,没有任何的数据拷贝行为。我们来看个例子:

const arr = new Uint16Array(2)

arr[0] = 5000
arr[1] = 4000

const buf = Buffer.from(arr.buffer)

console.log(buf)
// Prints: <Buffer 88 13 a0 0f>

// 改变原来数组的数字
arr[1] = 6000

console.log(buf)
// Prints: <Buffer 88 13 70 17>

从上面例子的输出我们可以知道,arrbuf对象会共用同一片内存空间,所以当我们改变原数组的数据时,buf的数据也会发生相应的变化。

其它Buffer操作

看完了创建Buffer的几种做法,我们接着来看一下Buffer其它的一些常用API或者属性

buf.length

这个函数会返回当前buffer占用了多少字节

// 创建一个大小为1234字节的Buffer对象
const buf1 = Buffer.alloc(1234)
console.log(buf1.length)
// Prints: 1234

const buf2 = Buffer.from(&#39;Hello&#39;)
console.log(buf2.length)
// Prints: 5

Buffer.poolSize

这个字段表示Node会为我们预创建的Buffer池子有多大,它的默认值是8192,也就是8KB。Node在启动的时候,它会为我们预创建一个8KB大小的内存池,当用户用某些API(例如Buffer.alloc)创建Buffer实例的时候可能会用到这个预创建的内存池以提高效率,下面是一个具体的例子:

const buf1 = Buffer.from(&#39;Hello&#39;)
console.log(buf1.length)
// Prints: 5

// buf1的buffer属性会指向其底层的ArrayBuffer对象对应的内存
console.log(buf1.buffer.byteLength)
// Prints: 8192

const buf2 = Buffer.from(&#39;World&#39;)
console.log(buf2.length)
// Prints: 5

// buf2的buffer属性会指向其底层的ArrayBuffer对象对应的内存
console.log(buf2.buffer.byteLength)
// Prints: 8192

在上面的例子中,buf1buf2对象由于长度都比较小所以会直接使用预创建的8KB内存池。其在内存的大概表示如图:截屏2022-12-11 下午1.51.54.png这里值得一提的是只有当需要分配的内存区域小于4KB(8KB的一半)并且现有的Buffer池子还够用的时候,新建的Buffer才会直接使用当前的池子,否则Node会新建一个新的8KB的池子或者直接在内存里面分配一个区域(FastBuffer)。

buf.write(string[, offset,[, length]][, encoding])

这个函数可以按照一定的偏移量(offset)往一个Buffer实例里面写入一定长度(length)的数据。我们来看一下具体的例子:

const buf = Buffer.from(&#39;Hello&#39;)

console.log(buf.toString())
// Prints: "Hello"

// 从第3个位置开始写入&#39;LLO&#39;字符
buf.write(&#39;LLO&#39;, 2)
console.log("HeLLO")
// Prints: "HeLLO"

这里需要注意的是当我们需要写入的字符串的长度超过buffer所能容纳的最长字符长度(buf.length)时,超过长度的字符会被丢弃:

const buf = Buffer.from(&#39;Hello&#39;)

buf.write(&#39;LLO!&#39;, 2)
console.log(buf.toString())
// Print:s "HeLLO"

另外,当我们写入的字符长度超过buffer的最长长度,并且最后一个可以写入的字符不能全部填满时,最后一个字符整个不写入:

const buf = Buffer.from(&#39;Hello&#39;)

buf.write(&#39;LL你&#39;, 2)
console.log(buf.toString())
// Prints "HeLLo"

在上面的例子中,由于"你"是中文字符,需要占用三个字节,所以不能全部塞进buf里面,因此整个字符的三个字节都被丢弃了,buf对象的最后一个字节还是保持"o"不变。

Buffer.concat(list[, totalLength])

这个函数可以用来拼接多个Buffer对象生成一个新的buffer。函数的第一个参数是待拼接的Buffer数组,第二个参数表示拼接完的buffer的长度是多少(totalLength)。下面是一个简单的例子:

const buf1 = Buffer.from(&#39;Hello&#39;)
const buf2 = Buffer.from(&#39;World&#39;)

const buf = Buffer.concat([buf1, buf2])
console.log(buf.toString())
// Prints "HelloWorld"

上面的例子中,因为我们没有指定最终生成Buffer对象的长度,所以Node会计算出一个默认值,那就是buf.totalLength = buf1.length + buf2.length。而如果我们指定了totalLength的值的话,当这个值比buf1.lengh + buf2.length小时,Node会截断最后生成的buffer;如果指定的值比buf1.length + buf2.length大时,生成buf对象的长度还是totalLength,多出来的位数填充的内容是0。

这里还有一点值得指出的是,Buffer.concat最后拼接出来的Buffer对象是通过拷贝原来Buffer对象得出来,所以改变原来的Buffer对象的内容不会影响到生成的Buffer对象,不过这里我们还是需要考虑拷贝的性能问题就是了。

Garbage collection des objets Buffer

Au début de l'article, j'ai dit que les zones mémoire allouées par tous les objets Buffer dans Node sont indépendantes de l'espace tas V8 et appartiennent à la mémoire hors tas. Cela signifie-t-il que l'objet Buffer n'est pas affecté par le mécanisme de récupération de place V8 et que nous devons gérer manuellement la mémoire ? En fait non, chaque fois que nous utilisons l'API de Node pour créer un nouvel objet Buffer, chaque objet Buffer correspond à un objet (une référence à la mémoire Buffer) dans l'espace JavaScript. Cet objet est contrôlé par le garbage collection V8. accrochez quelques hooks pour libérer la mémoire hors tas pointée par le Buffer lorsque cette référence est récupérée. En termes simples, nous n'avons pas à nous soucier de l'espace alloué par le mécanisme de récupération de place de Buffer V8 nous aidera à récupérer la mémoire inutile.

Résumé

Dans cet article, j'ai présenté quelques connaissances de base de Buffer, y compris les API et propriétés courantes de Buffer. J'espère que ces connaissances pourront être utiles à votre travail.

Pour plus de connaissances sur les nœuds, veuillez visiter :

tutoriel Nodejs !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer