stream: don't create unnecessary buffers in Readable

If there is an encoding, and we do 'stream.push(chunk, enc)', and the
encoding argument matches the stated encoding, then we're converting from
a string, to a buffer, and then back to a string.  Of course, this is a
completely pointless bit of work, so it's best to avoid it when we know
that we can do so safely.
This commit is contained in:
isaacs 2013-04-30 15:09:54 -07:00
parent 0b8af89363
commit bdb78b9945
3 changed files with 24 additions and 11 deletions

View File

@ -131,13 +131,15 @@ TLS, may ignore this argument, and simply provide data whenever it
becomes available. There is no need, for example to "wait" until becomes available. There is no need, for example to "wait" until
`size` bytes are available before calling `stream.push(chunk)`. `size` bytes are available before calling `stream.push(chunk)`.
### readable.push(chunk) ### readable.push(chunk, [encoding])
* `chunk` {Buffer | null | String} Chunk of data to push into the read queue * `chunk` {Buffer | null | String} Chunk of data to push into the read queue
* `encoding` {String} Encoding of String chunks. Must be a valid
Buffer encoding, such as `'utf8'` or `'ascii'`
* return {Boolean} Whether or not more pushes should be performed * return {Boolean} Whether or not more pushes should be performed
Note: **This function should be called by Readable implementors, NOT Note: **This function should be called by Readable implementors, NOT
by consumers of Readable subclasses.** The `_read()` function will not by consumers of Readable streams.** The `_read()` function will not
be called again until at least one `push(chunk)` call is made. If no be called again until at least one `push(chunk)` call is made. If no
data is available, then you MAY call `push('')` (an empty string) to data is available, then you MAY call `push('')` (an empty string) to
allow a future `_read` call, without adding any data to the queue. allow a future `_read` call, without adding any data to the queue.

View File

@ -83,10 +83,12 @@ function ReadableState(options, stream) {
this.readingMore = false; this.readingMore = false;
this.decoder = null; this.decoder = null;
this.encoding = null;
if (options.encoding) { if (options.encoding) {
if (!StringDecoder) if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder; StringDecoder = require('string_decoder').StringDecoder;
this.decoder = new StringDecoder(options.encoding); this.decoder = new StringDecoder(options.encoding);
this.encoding = options.encoding;
} }
} }
@ -106,19 +108,27 @@ function Readable(options) {
// This returns true if the highWaterMark has not been hit yet, // This returns true if the highWaterMark has not been hit yet,
// similar to how Writable.write() returns true if you should // similar to how Writable.write() returns true if you should
// write() some more. // write() some more.
Readable.prototype.push = function(chunk) { Readable.prototype.push = function(chunk, encoding) {
var state = this._readableState; var state = this._readableState;
if (typeof chunk === 'string' && !state.objectMode)
chunk = new Buffer(chunk, arguments[1]); if (typeof chunk === 'string' && !state.objectMode) {
return readableAddChunk(this, state, chunk, false); encoding = encoding || 'utf8';
if (encoding !== state.encoding) {
chunk = new Buffer(chunk, encoding);
encoding = '';
}
}
return readableAddChunk(this, state, chunk, encoding, false);
}; };
// Unshift should *always* be something directly out of read()
Readable.prototype.unshift = function(chunk) { Readable.prototype.unshift = function(chunk) {
var state = this._readableState; var state = this._readableState;
return readableAddChunk(this, state, chunk, true); return readableAddChunk(this, state, chunk, '', true);
}; };
function readableAddChunk(stream, state, chunk, addToFront) { function readableAddChunk(stream, state, chunk, encoding, addToFront) {
var er = chunkInvalid(state, chunk); var er = chunkInvalid(state, chunk);
if (er) { if (er) {
stream.emit('error', er); stream.emit('error', er);
@ -134,7 +144,7 @@ function readableAddChunk(stream, state, chunk, addToFront) {
var e = new Error('stream.unshift() after end event'); var e = new Error('stream.unshift() after end event');
stream.emit('error', e); stream.emit('error', e);
} else { } else {
if (state.decoder && !addToFront) if (state.decoder && !addToFront && !encoding)
chunk = state.decoder.write(chunk); chunk = state.decoder.write(chunk);
// update the buffer info. // update the buffer info.
@ -179,6 +189,7 @@ Readable.prototype.setEncoding = function(enc) {
if (!StringDecoder) if (!StringDecoder)
StringDecoder = require('string_decoder').StringDecoder; StringDecoder = require('string_decoder').StringDecoder;
this._readableState.decoder = new StringDecoder(enc); this._readableState.decoder = new StringDecoder(enc);
this._readableState.encoding = enc;
}; };
// Don't raise the hwm > 128MB // Don't raise the hwm > 128MB

View File

@ -135,9 +135,9 @@ function Transform(options) {
}); });
} }
Transform.prototype.push = function(chunk) { Transform.prototype.push = function(chunk, encoding) {
this._transformState.needTransform = false; this._transformState.needTransform = false;
return Duplex.prototype.push.call(this, chunk); return Duplex.prototype.push.call(this, chunk, encoding);
}; };
// This is the part where you do stuff! // This is the part where you do stuff!