Nodejs中关于Buffer的binary格式问题
Nodejs中关于Buffer的binary格式问题
这格式到底什么意思?比方说console.log(new Buffer(‘牛’)); 输出<Buffer,e7,89,9b> e7,89,9b是utf-8没错. 然后 var buf0=new Buffer(‘牛’); console.log(new Buffer(buf0.toString(‘binary’))); 输出<Buffer,c3,a7,c2,89,c2,9b> 这转换算法怎么来的?我追踪了下V8代码,无果,某地转换成binary似乎是把字符全部转到int16_t宽度然后又拷回去,但这样不应该得出这样的结果啊.
在 Node.js 中,Buffer
对象用于处理二进制数据,它是 Node.js 中非常重要的一个特性。对于 Buffer
的 binary
格式问题,我们可以深入了解一下。
首先,让我们理解一下 binary
格式。binary
是一种编码方式,它将每个字符视为一个字节(8位),因此可以表示 256 种不同的值。这种编码方式常用于处理原始二进制数据,例如图像或音频文件。
在您的例子中,new Buffer('牛')
创建了一个包含三个字节的缓冲区,因为 “牛” 在 UTF-8 编码中占用 3 个字节(e7, 89, 9b)。
接下来,当我们使用 buf0.toString('binary')
将 Buffer
转换为 binary
字符串时,实际上会将每个字节视为一个独立的字节,并将其转换为对应的 ASCII 码。
让我们看一个具体的例子来更好地理解这一点:
// 创建一个包含中文字符 '牛' 的 Buffer
var buf0 = new Buffer('牛');
// 打印原始 Buffer
console.log(buf0); // 输出: <Buffer e7 89 9b>
// 将 Buffer 转换为 binary 字符串
var binaryString = buf0.toString('binary');
console.log(binaryString); // 输出: '\xe7\x89\x9b'
// 将 binary 字符串转换回 Buffer
var buf1 = new Buffer(binaryString, 'binary');
console.log(buf1); // 输出: <Buffer e7 89 9b>
在这个例子中,我们创建了一个包含 “牛” 的 Buffer
。当我们使用 toString('binary')
方法将这个 Buffer
转换为 binary
字符串时,每个字节都被转换为对应的 ASCII 码。这些 ASCII 码被打印出来后,看起来像是 c3 a7 c2 89 c2 9b
,这是因为 binary
编码实际上是将每个字节视为独立的字节,并将其转换为相应的 ASCII 码。
最后,我们将这个 binary
字符串再次转换回 Buffer
,可以看到结果与原始 Buffer
相同。
希望这个解释和示例代码能帮助您更好地理解 Node.js 中 Buffer
的 binary
格式问题。
完整的Buffer构造函数是new Buffer(str, [encoding]), encoding的默认值是utf-8。
console.log(new Buffer(buf0.toString('binary')));
等于
console.log(new Buffer(buf0.toString('binary'),'utf-8'));
这里第二个初始化参数encoding不能用默认的utf-8,而应该用 ?(你猜)。
我把工程告诉你们好了:enter link description here
file.js里面 让人下载文件的代码:
var filename = allpath.replace(/^.*[\\\/]/, '');
var buf0=new Buffer(filename);
res.download(allpath,buf0.toString('binary'));
return;
这里filename 本身就是utf-8的,但我非得用binary参数否则中文的文件名乱码.我想知道原因,因为 binary据说要废弃,没替代方案岂不傻眼
你这种下载是错误且低效的
自己看API实现吧,不要迷信这些仅仅实现了功能的框架
现在我认为主要是Buffer问题,用api实现也要附加上文件名啊. 和express的实现里set(‘Content-Disposition’, ‘attachment; filename="’+filename+’"'会有什么不同?不就是文件名数据不同嘛.
如此,实际你想问的其实与本帖的主题关系不大,虽然你认为是Buffer出问题了,不过呢这只是表象,导致中文乱码的元凶是http模块发送header的方式。 先从结论而言:nodejs仅支持单字节字符(准确的说是ascii)的header,所有的多字节字符都会被去掉高位字节,只保留最低位字符。
filename='中文名.txt';// \u4e2d\u6587\u540d.txt;js内部字符编码是unicode
res.download(allpath,filename);
客户端得到的文件名就变成:\x2d\x87\x0d.txt,高位被去掉了。 testcase: https://gist.github.com/shiedman/5472925
进一步挖掘源码看看究竟怎么回事,从response.write这个方法开始追踪,最终定位到OutgoingMessage.prototype._send(https://github.com/joyent/node/blob/v0.10.5-release/lib/http.js#L488)
this.output.unshift(this._header);
this.outputEncodings.unshift('ascii');
实际的写入操作发生在OutgoingMessage.prototype._writeRaw(https://github.com/joyent/node/blob/v0.10.5-release/lib/http.js#L505)
var c = this.output.shift();
var e = this.outputEncodings.shift();
this.connection.write(c, e);
一入队一出队,相互对应,header的发送相当于固定为connection.write(header,'ascii')
,当header包含中文字符,高字节被削,然后乱码;当header包含binary编码的字符,与ascii同属单字节,字节流得以完整保留,文件名显示正常。(PS:response.end的实现与response.write稍有出入,在特定的情况下可指定header的编码,有兴趣的同学可翻下源码)
至于如何输出正确的中文文件名,提供2个思路:
1.拦截_writeRaw的调用,将this._header
的对应outputEncoding设为utf-8后,恢复执行流程
var _fn=res._writeRaw;
res._writeRaw=function(){
res.output.forEach(function(e,i){
if(e==res._headers){res.outputEncodings[i]=='utf-8';}
})
_fn.apply(res,arguments);
}
(大概如此,未实际验证)
2.遵循RFC规范 详细的讨论见http://stackoverflow.com/questions/93551/how-to-encode-the-filename-parameter-of-content-disposition-header-in-http
var userAgent=(request.headers['user-agent']||'').toLowerCase();
if(userAgent.indexOf('msie')>=0 || userAgent.indexOf('chrome')>=0){
headers['Content-Disposition']='attachment; filename='+encodeURIComponent(filename);
}else if(userAgent.indexOf('firefox')>=0){
headers['Content-Disposition']='attachment; filename*="utf8\'\''+encodeURIComponent(filename)+'"';
} else{
/** safari等其他非主流浏览器只能自求多福了 **/
headers['Content-Disposition']='attachment; filename='+new Buffer(filename).toString('binary');
}
除开某个畸形的IE版本和特殊的浏览器,大部分情况下都能正确显示中文名。 PS:心中虽明白,写出来却花了大半小时,郁闷。
谢了,不过这些解决方案一点不优雅,还是继续binary好了…这应该算nodejs的bug,也许后面会矫正.
唉正如你所说下载都是二进制,和什么编码没有关系的,BUFFER这个相对低效的元素最好不要出现在常用函数中 看了半天不知道你说的是什么乱码,是需要读取的文件名是中文还是什么 如果NODE不支持二进制字符串,自己也是可以实现的,不需要用BUFFER来转 / 不过还是看不明白LZ具体是什么问题
因为BINARY STRING就是“乱码”,对UNICODE友好的JS不合适处理这种东西,NODE才要把它去除
在Node.js中,Buffer
对象用于处理二进制数据。当你使用new Buffer('牛')
创建一个Buffer时,默认情况下它会根据指定的字符串编码(如UTF-8)来存储字符。
例如:
console.log(new Buffer('牛'));
// 输出: <Buffer e7 89 9b>
这里,'牛'
被编码为UTF-8,因此得到的是三个字节e7 89 9b
。
接下来,当你尝试将这个Buffer转换为二进制格式并重新生成一个新的Buffer时,会发生一些不同的事情。buf.toString('binary')
实际上是将Buffer中的每个字节视为一个8位无符号整数,并将其转换为对应的ASCII字符。这些ASCII字符在字符串中以字节的形式表示。
例如:
var buf0 = new Buffer('牛');
console.log(new Buffer(buf0.toString('binary')));
// 输出: <Buffer c3 a7 c2 89 c2 9b>
这里,buf0.toString('binary')
实际上返回了一个包含原始字节的字符串,每个字节作为一个字符存在。当这个字符串再次转换为Buffer时,这些字符会被作为ASCII字符存储,因此得到的是c3 a7 c2 89 c2 9b
。
简而言之,toString('binary')
方法将Buffer的每个字节视为一个独立的字节值,并将其转换为相应的ASCII字符。这些字符在新的Buffer中以字节形式存储,因此出现了你看到的结果。
如果你希望保持原始的UTF-8编码,可以直接使用原始Buffer,而不需要转换为binary
格式。