diff --git a/lib/internal/streams/readable.js b/lib/internal/streams/readable.js index a129b1b6f4b75d..01533b6ad254cc 100644 --- a/lib/internal/streams/readable.js +++ b/lib/internal/streams/readable.js @@ -278,7 +278,8 @@ function ReadableState(options, stream, isDuplex) { // A linked list is used to store data chunks instead of an array because the // linked list can remove elements from the beginning faster than // array.shift(). - this.buffer = new BufferList(); + this.buffer = []; + this.bufferIndex = 0; this.length = 0; this.pipes = []; @@ -546,10 +547,15 @@ function addChunk(stream, state, chunk, addToFront) { } else { // Update the buffer info. state.length += (state[kState] & kObjectMode) !== 0 ? 1 : chunk.length; - if (addToFront) - state.buffer.unshift(chunk); - else + if (addToFront) { + if (state.bufferIndex > 0) { + state.buffer[--state.bufferIndex] = chunk; + } else { + state.buffer.unshift(chunk); // Slow path + } + } else { state.buffer.push(chunk); + } if ((state[kState] & kNeedReadable) !== 0) emitReadable(stream); @@ -564,21 +570,24 @@ Readable.prototype.isPaused = function() { // Backwards compatibility. Readable.prototype.setEncoding = function(enc) { + const state = this._readableState; + const decoder = new StringDecoder(enc); - this._readableState.decoder = decoder; + state.decoder = decoder; // If setEncoding(null), decoder.encoding equals utf8. - this._readableState.encoding = this._readableState.decoder.encoding; + state.encoding = state.decoder.encoding; - const buffer = this._readableState.buffer; // Iterate over current buffer to convert already stored Buffers: let content = ''; - for (const data of buffer) { + for (const data of state.buffer.slice(state.bufferIndex)) { content += decoder.write(data); } - buffer.clear(); + state.buffer.length = 0; + state.bufferIndex = 0; + if (content !== '') buffer.push(content); - this._readableState.length = content.length; + state.length = content.length; return this; }; @@ -611,7 +620,7 @@ function howMuchToRead(n, state) { if (NumberIsNaN(n)) { // Only flow one buffer at a time. if ((state[kState] & kFlowing) !== 0 && state.length) - return state.buffer.first().length; + return state.buffer[state.bufferIndex].length; return state.length; } if (n <= state.length) @@ -1550,20 +1559,93 @@ function fromList(n, state) { return null; let ret; - if (state.objectMode) - ret = state.buffer.shift(); - else if (!n || n >= state.length) { + if ((state[kState] & kObjectMode) !== 0) { + ret = state.buffer[state.bufferIndex++]; + } else if (!n || n >= state.length) { // Read it all, truncate the list. - if (state.decoder) - ret = state.buffer.join(''); - else if (state.buffer.length === 1) - ret = state.buffer.first(); - else - ret = state.buffer.concat(state.length); - state.buffer.clear(); + if ((state[kState] & kDecoder) !== 0) { + ret = '' + for (let n = state.bufferIndex; n < state.buffer.length; n++) { + ret += state.buffer[n]; + } + } else if (state.buffer.length - state.bufferIndex === 0) { + ret = Buffer.alloc(0) + } else if (state.buffer.length - state.bufferIndex === 1) { + ret = state.buffer[state.bufferIndex]; + } else { + ret = Buffer.allocUnsafe(n >>> 0); + let i = 0; + for (let n = state.bufferIndex; n < state.buffer.length; n++) { + const data = state.buffer[n]; + ret.set(data, i); + i += data.length; + } + } + state.buffer.length = 0; + state.bufferIndex = 0; } else { // read part of list. - ret = state.buffer.consume(n, state.decoder); + + const data = state.buffer[state.bufferIndex]; + + if (n < data.length) { + // `slice` is the same for buffers and strings. + const slice = data.slice(0, n); + state.buffer[state.bufferIndex] = data.slice(n); + return slice; + } + + if (n === data.length) { + // First chunk is a perfect match. + return state.buffer[state.bufferIndex++]; + } + + if ((state[kState] & kDecoder) !== 0) { + ret = ''; + while (state.bufferIndex < state.buffer.length) { + const str = state.buffer[state.bufferIndex]; + if (n > str.length) { + ret += str; + n -= str.length; + state.bufferIndex++; + } else { + if (n === buf.length) { + ret += str; + state.bufferIndex++; + } else { + ret += str.slice(0, n); + state.buffer[state.bufferIndex] = str.slice(n); + } + break; + } + } + } + + ret = Buffer.allocUnsafe(n); + + const retLen = n; + while (state.bufferIndex < state.buffer.length) { + const buf = state.buffer[state.bufferIndex]; + if (n > buf.length) { + ret.set(buf, retLen - n); + n -= buf.length; + state.bufferIndex++; + } else { + if (n === buf.length) { + ret.set(buf, retLen - n); + state.bufferIndex++; + } else { + ret.set(new Uint8Array(buf.buffer, buf.byteOffset, n), retLen - n); + state.buffer[state.bufferIndex] = buf.slice(n); + } + break; + } + } + + if (state.bufferIndex === state.buffer.length) { + state.bufferIndex = 0 + state.buffer.length = 0; + } } return ret;