nodejs中Buffer对象的实例属性(三)

Buffer对象在前面说了Buffer实例化时的几种不同方法,接下来说了Buffer构造函数中的几种静态属性和方法,既然可以实例化,那么实例化之后的buffer对象,又存在哪些属性和方法呢,这里就看下我觉得会比较常用的一些实例化方法。

概总

首先呢,就先看下,在buffer实例中,有哪些属性和方法:


C:\nodejs\node
>buf = new Buffer([1,2,3]);
<Buffer 01 02 03>
>for(i in buf){
..  console.log(i);
.. }

//这里包括了六七十个属性和方法,基本上很多方法是我们不怎么用到的,所以这里只列举几个可能经常会用到的属性

length
copy
toString
equals
inspect
compare
fill
write
toJSON
slice


Buffer实例化对象支持的一些方法

接下来就对上面的属性和方法,一一进行学习吧。

1:length属性

如果你使用过jQuery,研究过jQuery的源码,或者其他一些框架的源码,或者DOM原生的一些HTMLCollectionNodeList等对象,那么你肯定就听说过“类数组”的概念了,这里的Buffer生成的buffer对象也是一个类数组。

所以,这里的length属性,也是和类数组相同的概念,表示buffer对象中元素的个数。并且可以通过改变length的值,对buffer对象进行截断处理。

看下示例:


C:\nodejs>node
>buf = new Buffer([1,2,3]);
<Buffer 01 02 03>
>buf.length
3
>buf.length = 1
1
>buf
<Buffer 01>
//改变length的值,则buf数组,也跟着改变了。


2:copy方法

当需要将Buffer对象中保存的二进制数据复制到另外一个Buffer对象中时,可以使用Buffer对象的copy方法,copy方法的使用方法:


buf.copy(targetBuffer,[targetStart],[sourceStart],[sourceEnd]);


copy支持四个参数:

第一个参数:必须。用于指定复制的目标Buffer对象。

第二个参数,用于指定目标Buffer对象中从第几个字节开始写入数据,参数值为一个小于目标Buffer对象长度的整数值,默认为0(从开始出写入数据)

第三个参数用于指定从复制源Buffer对象中获取数据时的开始位置,默认值为0,即从第一个数据开始获取数据。

第四个参数为指定从复制源Buffer对象中获取数据的结束位置,默认值为复制源Buffer对象的长度,即Buffer对象的结尾。

最终,buf是不会变动的,而第一个参数的值会变动。

看下REPL下的测试例子吧:


C:\nodejs>node
>buf1 = new Buffer([1,2,3]);
<Buffer 01 02 03>
>buf2 = new Buffer([4,5,6]);
<Buffer 04 05 06>
>buf1.copy(buf2,1);
2
//说明,buf2有两个值被改变
>buf2
<Buffer 04 01 02>


当然啦,也可以继续添加第三个和第四个参数,来控制把buf对象中的哪些元素,添加到目标buf2对象中去,这个就个人测试吧。

但是有一点,我却没有找到copy实现的源码在哪里~~

3:toString方法

toString方法,是可以把Buffer对象中的数据转换为字符串的方法,使用方法如下:


buf.toString([encoding],[start],[end]);


该方法支持三个参数,这三个参数都是可选的:

第一个参数:用于指定Buffer对象中保存的文字编码方式,默认为utf8编码。

第二个参数和第三个参数,用于指定被转换数据的起始位置和终止位置,是以保存后的字节为单位。

toString返回值为被转换后的字符串。

所以,可以如下测试:



C:\nodejs>node
>buf = new Buffer("我喜欢Nodejs");
<Buffer e6 88 91 e5 96 9c e6 ac a2 4e 6f 64 65 6a 73>
>buf.toString("utf8",9,15);
'Nodejs'
>buf.toString();
"我喜欢Nodejs"


用法起始很简单,这里继续看下源码中,关于toString方法的实现了:


// toString(encoding, start=0, end=buffer.length)
Buffer.prototype.toString = function(encoding, start, end) {
  var loweredCase = false;

  start = start >>> 0;
  //变为数字,该方法,如果start不是数字,则会返回0,也可以用来初始化为0
  
  //判断end的值。默认值为length
  end = util.isUndefined(end) || end === Infinity ? this.length : end >>> 0;

  //默认的编码方式
  if (!encoding) encoding = 'utf8';
  if (start < 0) start = 0;
  if (end > this.length) end = this.length;
  if (end <= start) return '';
  //一些非法性的判断,以及合理性智能处理
  
  //根据不同的类型,使用不同的方法,截取字符串
  //hexSlice,utf8Slice,asciiSlice,binarySlice,base64Slice,ucs2Slice
  //这些方法,都是实例化之后,buffer对象中的一些方法,本篇不做说明
  while (true) {
    switch (encoding) {
      case 'hex':
        return this.hexSlice(start, end);

      case 'utf8':
      case 'utf-8':
        return this.utf8Slice(start, end);

      case 'ascii':
        return this.asciiSlice(start, end);

      case 'binary':
        return this.binarySlice(start, end);

      case 'base64':
        return this.base64Slice(start, end);

      case 'ucs2':
      case 'ucs-2':
      case 'utf16le':
      case 'utf-16le':
        return this.ucs2Slice(start, end);

      default:
        if (loweredCase)
          throw new TypeError('Unknown encoding: ' + encoding);
        encoding = (encoding + '').toLowerCase();
        loweredCase = true;
    }
  }
};


由源码中,也可以看出一个问题,就是,如果你想要传入第二个和第三个参数,那么就必须传入第二个参数,这里,不会对传入参数的多少,进行智能判断的,和jQuery中的一些方法,是有区别的。

4:equals和compare

equalscompare其实是完全相同的两个方法,请参考前一篇文章中compare方法即可

5:inspect

该方法是用来显示“省略号”的,就比如,我们在创建一个Buffer对象时,数据量过长,那么为了能不让数据完全占据空间,就会把后面的数据省略掉。

该方法一般不会被使用到,使用方法:


buf.inspect();


没有参数,只是控制buf对象的显示数据量,先看下示例,然后再看下源代码就能理解到了:


C:\nodejs>node
>buf = new Buffer(55);
<Buffer 02 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 ..>


buffer对象的数据量超过50之后,就只会在控制面板中显示50个数据。而这个50的变量,是可以通过INSPECT_MAX_BYTES属性控制的。

接下来,看下源码中的实现:


//做省略的~~当超过一定的大小,只显示最初的50个数据
// Inspect
Buffer.prototype.inspect = function inspect() {
  var str = '';
  var max = exports.INSPECT_MAX_BYTES;
  //获取被设置好的显示最大字节数。
  
  if (this.length > 0) {
    str = this.toString('hex', 0, max).match(/.{2}/g).join(' ');
    //获取出buffer对象的前max个元素,并把这些元素规则化之后,转换为字符串
    if (this.length > max)
      str += ' ... ';
	  
      //如果buffer对象的元素长度是大于max的,则在str的结尾处,添加省略号。
  }
  
  //返回数据<Buffer 01 02 03 ...>的样式
  return '<' + this.constructor.name + ' ' + str + '>';
};


OK,关于inspect的方法,现在看来,其实是不怎么会用得到的~~~

6:fill方法

记得在Buffer实例化时,如果只给了一个数字的参数,实例化之后的buffer对象的值是什么吗?对,是一些随机的值,那么如何对这些值进行初始化为一个统一的值呢,那就是使用这里的fill方法了。

fill的使用方法:


buf.fill(value,[offset],[end]);


fill支持三个参数:

第一个参数为必须制定的参数,参数值为需要被写入的数值。

第二个参数为可选参数,用于指定从第几个字节出开始写入被制定的数值。默认值为0,即从缓存区的起始位置写入。

第三个参数为可选参数,用于指定将数值一直写入到第几个字节出,默认值为Buffer对象的length大小,即书写到缓存区底部。

来一个测试下,fill的用法吧:


C:\nodejs>node
>buf = new Buffer(5);
<Buffer a0 9b 49 01 18>
>buf.fill(10);
//把buf中的所有元素改为统一的10值
<Buffer 0a 0a 0a 0a 0a>


如果只想要改变一部分的话,那么就可以传入第二个和第三个参数了,这里不多做改变

接下来看下源码中的实现:


Buffer.prototype.fill = function fill(val, start, end) {
  start = start >> 0;
  end = (end === undefined) ? this.length : end >> 0;
  //对start和end取默认值

  if (start < 0 || end > this.length)
    throw new RangeError('out of range index');
  if (end <= start)
    return this;
   //对start和end的值,进行合法性验证
   
  if (typeof val !== 'string') {
    val = val >>> 0;
  } else if (val.length === 1) {
    var code = val.charCodeAt(0);
    if (code < 256)
      val = code;
  }
  //对val的类型,获取最终保存到内存中的值数据值
  
  //引用internal中的fill方法,进行赋值
  internal.fill(this, val, start, end);

  return this;
  //返回this的概念就是,可以在赋值之后,进行链式操作
};


7:write方法

如果要将字符串当做二进制数据来使用,只需将该字符串黄作为Buffer类的构造函数的参数来创建Buffer对象即可,但是有时候 ,我们需要向已经创建的Buffer对象中写入字符串,这时,就可以使用write方法来完成,write的使用方法如下:


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


write方法中,可以使用四个参数:

第一个参数:必须,用于指定需要写入的字符串

第二个参数offset和第三个参数length用于指定字符串转换为字节数据后的写入位置以及写入的长度。

第四个参数用于指定写入字符串时,使用的编码格式,默认为utf8格式

在REPL环境下,测试如下结果:


C:\nodejs>node
>buf = new Buffer("我喜爱Nodejs");
<Buffer e6 88 91 e5 96 9c e7 88 b1 4e 6f 64 65 6a 73>
>buf.write("热",3,3);
3
>buf.toString();
"我热爱Nodejs"


看到上面的测试例子,也应该能看到了一点使用write的不太方便的地方,它需要你确切的知道,你原本要修改的字体,在哪里出现的。并且知道,编码后,待修改的数据,所占据的字节数是多少~~~

让我觉得,这个东西,有点鸡肋的样子。

接下来再看下源码中,是怎么实现write的吧:


// write(string, offset = 0, length = buffer.length, encoding = 'utf8')
var writeWarned = false;
var writeMsg = '.write(string, encoding, offset, length) is deprecated.' +
               ' Use write(string[, offset[, length]][, encoding]) instead.';
Buffer.prototype.write = function(string, offset, length, encoding) {
  // Buffer#write(string);
  
  //这里的if-else就是处理默认值的一些判断。
  if (util.isUndefined(offset)) {
    encoding = 'utf8';
    length = this.length;
    offset = 0;

  // Buffer#write(string, encoding)
  } else if (util.isUndefined(length) && util.isString(offset)) {
    encoding = offset;
    length = this.length;
    offset = 0;

  // Buffer#write(string, offset[, length][, encoding])
  } else if (isFinite(offset)) {
    offset = offset >>> 0;
    if (isFinite(length)) {
      length = length >>> 0;
      if (util.isUndefined(encoding))
        encoding = 'utf8';
    } else {
      encoding = length;
      length = undefined;
    }

  // XXX legacy write(string, encoding, offset, length) - remove in v0.13
  } else {
    if (!writeWarned) {
      if (process.throwDeprecation)
        throw new Error(writeMsg);
      else if (process.traceDeprecation)
        console.trace(writeMsg);
      else
        console.error(writeMsg);
      writeWarned = true;
    }

    var swap = encoding;
    encoding = offset;
    offset = length >>> 0;
    length = swap;
  }

  var remaining = this.length - offset;
  if (util.isUndefined(length) || length > remaining)
    length = remaining;

  encoding = !!encoding ? (encoding + '').toLowerCase() : 'utf8';

  if (string.length > 0 && (length < 0 || offset < 0))
    throw new RangeError('attempt to write outside buffer bounds');
  //继续取默认值,并且执行一些合法性验证
  
  var ret;
  //根据编码方式,进行不同的处理~~
  switch (encoding) {
    case 'hex':
      ret = this.hexWrite(string, offset, length);
      break;

    case 'utf8':
    case 'utf-8':
      ret = this.utf8Write(string, offset, length);
      break;

    case 'ascii':
      ret = this.asciiWrite(string, offset, length);
      break;

    case 'binary':
      ret = this.binaryWrite(string, offset, length);
      break;

    case 'base64':
      // Warning: maxLength not taken into account in base64Write
      ret = this.base64Write(string, offset, length);
      break;

    case 'ucs2':
    case 'ucs-2':
    case 'utf16le':
    case 'utf-16le':
      ret = this.ucs2Write(string, offset, length);
      break;

    default:
      throw new TypeError('Unknown encoding: ' + encoding);
  }

  return ret;
};


这里,关于写数据和读数据的方法,都没有涉及,所以,在write方法中,涉及到的一些根据不同的编码方式写数据的方法,也没有涉及到,请自行查看源代码。

8:toJSON方法

把这个方法,列在这里,也只是因为在前端中JSON格式是一个很重要的概念,这里,虽然我还理解不到,这个方法,到底在什么时候,会被使用到,但是呢,这也只能代表着,我对Nodejs的应用,接触的不多,我相信这是一个很重要的方法,所以,并没有省略该方法。

Buffer对象,转换为JSON格式,使用方法是:


buf.toJSON();


这个属于比较简单的,不需要参数,只是把本身的Buffer对象,转换为JSON格式?但是,我看了下,最后转换的结果,却是转换为了一个对象,而Buffer对象,则转换为了该对象的一个数组。

测试以及源代码如下:


C:\nodejs>node
>buf = new Buffer([1,2,3])
<Buffer 01 02 03>
>json = buf.toJSON();
{type:'Buffer',data:[1,2,3]}
>typeof json
'object'

//源码这里就直接一起看了吧
Buffer.prototype.toJSON = function() {
  return {
    type: 'Buffer',
    data: Array.prototype.slice.call(this, 0)
  };
};


关于源码,也没有什么好说的了~~

9:slice方法

关于slice方法,就是再熟悉不过的了,所以,截取数组的功能,在Buffer对象中,也是完成的同样的功能。

关于这个,使用方法:


buf.slice([start],[end]);


两个参数,都是可有可无的,并且,startend也可以是负值,当startend为负值时,会首先把这个负值和buf的长度相加,然后变为正值之后,再做处理。

那么,就看下使用示例吧:


C:\nodejs>node
>buf = new Buffer([1,2,3,4,5]);
<Buffer 01 02 03 04 05>
>buf2 = buf.slice(1,2);
<Buffer 02>
>buf2 = buf.slice(2,1);
<Buffer >
//如果前后两个取值不合法,则只返回一个空的Buffe对象
>buf2 = buf.slice(-2,-1);
<Buffer 04>
//与buf2 = buf.slice(3,4)取值相同
>buf2 = buf.slice(-2)
<Buffer 04 05>
//与buf2 = buf.slice(3);取值相同
>buf2[0] = 1;
1
>buf
<Buffer 01 02 03 01 05>


唯一与JS中表现不同的是,如果你修改了slice返回的Buffer对象中的属性值,那么原来的Buffer对象中的对应的值,也会被修改,就比如,前面buf2[0] = 1之后,buf[3]的值,也被相应的修改了,因为在Buffer中,是保存的一个类似指针的东西,指向同一段存储空间,不管以哪一个变量或者指针,都可以修改这段存储空间的值,而再通过其他变量或者指针访问该属性时,也是获取到的以及被修改的值。

OK,继续看下源码中的处理吧:


Buffer.prototype.slice = function(start, end) {
  var len = this.length;
  start = ~~start;
  end = util.isUndefined(end) ? len : ~~end;
  //对start和end取默认值
  
  if (start < 0) {
    start += len;
    if (start < 0)
      start = 0;
  } else if (start > len) {
    start = len;
  }

  if (end < 0) {
    end += len;
    if (end < 0)
      end = 0;
  } else if (end > len) {
    end = len;
  }
  //对start和end进行合法性验证,并且修正为合法的数字
  //如果start和end为负值,则使用该值与length的值相加之后
  //重新赋值
  
  if (end < start)
    end = start;
  //如果最后start大于end,则表示出错,不获取数据

  //返回数据~~
  var buf = new NativeBuffer();
  sliceOnto(this, buf, start, end);
  buf.length = end - start;
  if (buf.length > 0)
    buf.parent = util.isUndefined(this.parent) ? this : this.parent;

  return buf;
};


关于slice,熟悉前端JS的人,都应该是再熟悉不过的了,所以,这里不再多说。

总结

Buffer对象,是Nodejs操作数据中的一个基础对象,它可以使的Nodejs拥有处理二进制的能力,当然,如果我们只是使用别人开发好的框架或者模块的话,估计这里是可以完全不知道的,只是作为一个学习者,知道底层的源码,总归是好处多多的,写一遍也是思考几遍,学习几遍的结果。

本文地址:http://www.zhangyunling.com/?p=401

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>