Node.js Buffer 完全指南:二进制数据处理从入门到精通

更新日期: 2025-11-15 阅读: 39 标签: 进制

在Node.js开发中,我们经常需要处理图片、文件、网络数据包等二进制数据。JavaScript传统上擅长处理文本,但对二进制数据的支持有限。这就是Buffer出现的原因 - 它让Node.js能够高效地处理二进制数据。


什么是Buffer?

Buffer是Node.js中用于处理二进制数据的类。它类似于整数数组,但对应的是V8堆外的一块原始内存分配。每个Buffer元素都是0到255之间的整数值,代表一个字节。

简单来说,Buffer就像是一个专门存放二进制数据的特殊数组。


Buffer的内存管理

Node.js使用slab分配机制来管理Buffer内存,这种机制减少了频繁申请内存的性能开销。

内存分配规则:

  • 需要小于8KB的Buffer:Node.js会尝试复用现有的slab单元(8KB内存块)

  • 需要大于8KB的Buffer:Node.js会分配一个独立的内存块

需要注意的是,如果一个slab被多个Buffer共享,必须等所有Buffer都被释放,这个slab的内存才能被回收。


创建Buffer实例

安全的创建方法

1. Buffer.alloc(size) - 最安全的方式

// 创建长度为10字节且用0填充的Buffer
const buf1 = Buffer.alloc(10);
console.log(buf1); // <Buffer 00 00 00 00 00 00 00 00 00 00>

2. Buffer.from(data) - 从现有数据创建

// 从字符串创建(默认UTF-8编码)
const buf2 = Buffer.from('Hello, Node.js!');
console.log(buf2.toString()); // Hello, Node.js!

// 从数组创建
const buf3 = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
console.log(buf3.toString()); // Hello

// 从另一个Buffer创建
const buf4 = Buffer.from(buf3);

需要谨慎使用的方法

Buffer.allocUnsafe(size) - 快速但可能包含旧数据

// 创建未初始化的Buffer,性能好但可能包含敏感数据
const buf = Buffer.allocUnsafe(10);

// 使用前必须填充
buf.fill(0);
// 或者完全写入数据
buf.write('HelloWorld');

创建方法对比

方法安全性性能使用场景
Buffer.alloc(size)高(预填充0)较慢通用场景,避免数据泄漏
Buffer.from(data)中等从现有数据转换
Buffer.allocUnsafe(size)低(可能含旧数据)性能敏感且会立即覆盖数据的场景

Buffer的常用操作

写入数据

const buf = Buffer.alloc(20);
const bytesWritten = buf.write('Hello, Node.js!', 0, 'utf8');
console.log(`写入了 ${bytesWritten} 个字节`); // 写入了 14 个字节

读取数据

const buf = Buffer.from('Hello, World!');

// 转换为字符串
console.log(buf.toString('utf8')); // Hello, World!
console.log(buf.toString('hex'));  // 48656c6c6f2c20576f726c6421
console.log(buf.toString('base64')); // SGVsbG8sIFdvcmxkIQ==

// 读取特定位置
console.log(buf[0]); // 72 (H的ASCII码)
console.log(buf.readUInt8(0)); // 72

切片操作

const buf = Buffer.from('Node.js Buffer');
const slice = buf.slice(0, 7);
console.log(slice.toString()); // Node.js

// 注意:切片与原Buffer共享内存
slice[0] = 110; // 小写n的ASCII码
console.log(buf.toString()); // node.js Buffer

合并Buffer

const buf1 = Buffer.from('Hello');
const buf2 = Buffer.from('World');
const combined = Buffer.concat([buf1, buf2]);
console.log(combined.toString()); // HelloWorld

// 指定总长度
const combined2 = Buffer.concat([buf1, buf2], 8);
console.log(combined2.toString()); // HelloWor

比较Buffer

const bufA = Buffer.from('ABC');
const bufB = Buffer.from('BCD');
const bufC = Buffer.from('ABC');

console.log(Buffer.compare(bufA, bufB)); // -1 (bufA在bufB之前)
console.log(Buffer.compare(bufA, bufC)); // 0 (相等)
console.log(bufA.equals(bufC)); // true

转换为JSON

const buf = Buffer.from([1, 2, 3, 4, 5]);
const json = buf.toJSON();
console.log(json); 
// { type: 'Buffer', data: [ 1, 2, 3, 4, 5 ] }


实际应用场景

文件操作

const fs = require('fs');

// 读取图片文件
fs.readFile('image.jpg', (err, data) => {
  if (err) throw err;
  
  // 检查文件类型
  const header = data.slice(0, 4);
  console.log('文件头:', header.toString('hex'));
  
  // 处理图片数据
  processImage(data);
});

// 写入文件
const bufferData = Buffer.from('要保存的二进制数据');
fs.writeFile('output.bin', bufferData, (err) => {
  if (err) throw err;
  console.log('文件保存成功');
});

网络通信

const http = require('http');
const server = http.createServer((req, res) => {
  // 收集请求数据
  const chunks = [];
  req.on('data', chunk => {
    chunks.push(chunk);
  });
  
  req.on('end', () => {
    // 合并所有数据块
    const requestData = Buffer.concat(chunks);
    
    // 处理请求数据
    const responseData = Buffer.from('处理结果');
    res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
    res.end(responseData);
  });
});

server.listen(3000);

数据编码转换

// Base64编码解码
const text = 'Hello, World!';
const base64Encoded = Buffer.from(text).toString('base64');
console.log('Base64:', base64Encoded); // SGVsbG8sIFdvcmxkIQ==

const decodedText = Buffer.from(base64Encoded, 'base64').toString();
console.log('解码后:', decodedText); // Hello, World!

// Hex编码
const hexString = Buffer.from(text).toString('hex');
console.log('Hex:', hexString); // 48656c6c6f2c20576f726c6421


处理乱码问题

在网络传输或文件读取时,多字节字符可能被拆分到不同的Buffer中,导致乱码。

问题示例

// 中文字符被拆分
const part1 = Buffer.from([0xe4, 0xb8]); // '中'字的前半部分
const part2 = Buffer.from([0xad]);       // '中'字的后半部分

console.log(part1.toString('utf8')); // 乱码
console.log(part2.toString('utf8')); // 乱码

解决方案

方法1:合并后转换

const chunks = [];
let totalSize = 0;

stream.on('data', chunk => {
  chunks.push(chunk);
  totalSize += chunk.length;
});

stream.on('end', () => {
  const completeBuffer = Buffer.concat(chunks, totalSize);
  const text = completeBuffer.toString('utf8');
  console.log('完整文本:', text);
});

方法2:使用StringDecoder

const { StringDecoder } = require('string_decoder');
const decoder = new StringDecoder('utf8');

const part1 = Buffer.from([0xe4, 0xb8]);
const part2 = Buffer.from([0xad]);

console.log(decoder.write(part1)); // 可能为空或部分字符
console.log(decoder.write(part2)); // 输出: 中


Buffer与TypedArray

Buffer实际上是Uint8Array的子类,可以与其他TypedArray互操作。

// Buffer 转 TypedArray
const buf = Buffer.from([1, 2, 3, 4, 5]);
const uint8Array = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);

// TypedArray 转 Buffer
const array = new Uint16Array([1000, 2000, 3000]);
const bufFromArray = Buffer.from(array.buffer);

console.log(bufFromArray); // <Buffer e8 03 d0 07 b8 0b>


注意事项和最佳实践

1. 内存安全

// 错误做法:可能泄漏敏感信息
const unsafeBuf = Buffer.allocUnsafe(100);
// 立即使用敏感数据填充
unsafeBuf.fill(0);

// 正确做法:使用Buffer.alloc
const safeBuf = Buffer.alloc(100);

2. 字符编码

const buf = Buffer.from('你好', 'utf8');
console.log(buf.toString('utf8')); // 你好
console.log(buf.toString('hex'));  // e4bda0e5a5bd

// 支持的编码:'utf8', 'ascii', 'utf16le', 'base64', 'hex', 'latin1'

3. 内存管理

// 大型Buffer要及时释放
function processLargeData() {
  const largeBuffer = Buffer.alloc(1024 * 1024); // 1MB
  // 处理数据...
  // 函数结束后,如果没有其他引用,Buffer会被垃圾回收
}

// 避免频繁创建小Buffer
const smallBuffers = [];
for (let i = 0; i < 1000; i++) {
  // 不好:频繁分配
  smallBuffers.push(Buffer.alloc(10));
}

// 更好:预分配或复用
const reusableBuffer = Buffer.alloc(10000);


性能优化技巧

1. 复用Buffer

// 创建可复用的Buffer池
class BufferPool {
  constructor(size, count) {
    this.pool = [];
    for (let i = 0; i < count; i++) {
      this.pool.push(Buffer.alloc(size));
    }
  }
  
  getBuffer() {
    return this.pool.pop() || Buffer.alloc(this.size);
  }
  
  returnBuffer(buf) {
    buf.fill(0);
    this.pool.push(buf);
  }
}

2. 批量操作

// 批量处理多个Buffer
function processBuffers(buffers) {
  const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);
  const result = Buffer.alloc(totalLength);
  let offset = 0;
  
  for (const buf of buffers) {
    buf.copy(result, offset);
    offset += buf.length;
  }
  
  return result;
}


总结

Buffer是Node.js中处理二进制数据的核心工具。掌握Buffer的使用对于文件操作、网络编程、数据加密等场景至关重要。记住这些要点:

  1. 优先使用Buffer.alloc()和Buffer.from()确保安全

  2. 注意字符编码,特别是在多语言环境中

  3. 合理管理内存,避免泄漏和性能问题

  4. 使用StringDecoder处理可能被拆分的多字节字符

  5. 了解Buffer与TypedArray的互操作

通过正确使用Buffer,你可以构建出高效可靠的Node.js应用程序。

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/13180

二进制和十进制相互转换、位移运算

自己的解题思路是将十进制的数转为二进制(不足32位补0),然后依次取8位转化为十进制的数字,再用.连接即为IP。里面的几个点记录一下:十进制转换为二进制 numObj.toString([radix]) radix可以指定进制

Js进制转换

参数radix支持 [2, 36] 之间的整数。例如:参数值为2,则表示二进制;为8,则表示八进制;为16,则表示十六进制。如果省略参数radix,则默认为10(十进制)。

理解二进制操作

最近在用 shell 写一个小工具,里面要用到复杂的二进制操作,对 int 值进行位操作和与或非,而 shell 的语法里, & 是取布尔与, >> 是重定向,不支持二进制操作,为了写出只需要默认系统环境就可以运行的程序

js 进制转换/进制编码解码

js 进制转换支持 2-36 , 即 0-9a-z .可以用于混淆、数值缩短、特殊符号转换…字符串36进制编码解码;ip地址端口号36进制编码解码

Js将十进制转换为十六进制?

JavaScript中有很多内置函数可以帮我们进行数(进)制转换。那么给定一个十进制数字,如何将数字从十进制转换为十六进制?下面本篇文章就来给大家介绍一个使用JavaScript将十进制转换为十六进制的方法

JavaScript 进制转换&位运算

在一般的代码中很少会接触到进制和位运算,但这不代表我们可以不去学习它。作为一位编程人员,这些都是基础知识。如果你没有学过这方面的知识,也不要慌,接下来的知识并不会很难。本文你将会学习到:进制转换,按位操作符,Javascript进制转换

nodejs怎么存取2进制数据?

在客户端javascript脚本代码中,对于二进制数据并没有提供一个很好的支持。然后在nodejs中需要处理像TCP流或文件流时,必须要处理二进制数据。因此在node.js中,定义了一个Buffer类,该类用来创建一个专门存放二进制数据的缓存区

十进制与十六进制之间的转换

将十进制数 x 除以 16, 即 x = q * 16 + r,取得余数 r 和 商 q,此时余数 r 就是 x 用十六进制表示时的最低位值; 之后商值 q 继续进行以上的除法操作, 获取每次的余数 r 作为 十六进制表示时的低位值, 直到 q 值小于 16 为值, 此时的

二进制数与位运算符

位运算符是基于二级制数进行操作的,即表示数字的 32 个数位,它由0和1组成…ECMAScript整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数)

JavaScript中的多种进制与进制转换

JavaScript 中提供的进制表示方法有四种:十进制、二进制、十六进制、八进制。对于数值字面量,主要使用不同的前缀来区分:

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!