@@ -58,9 +58,18 @@ def block_size=(value)
5858
5959 # Read data from the stream.
6060 # @parameter size [Integer | Nil] The number of bytes to read. If nil, read until end of stream.
61- # @returns [String] The data read from the stream.
62- def read ( size = nil )
63- return String . new ( encoding : Encoding ::BINARY ) if size == 0
61+ # @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
62+ # @returns [String] The data read from the stream, or the provided buffer filled with data.
63+ def read ( size = nil , buffer = nil )
64+ if size == 0
65+ if buffer
66+ buffer . clear
67+ buffer . force_encoding ( Encoding ::BINARY )
68+ return buffer
69+ else
70+ return String . new ( encoding : Encoding ::BINARY )
71+ end
72+ end
6473
6574 if size
6675 until @done or @read_buffer . bytesize >= size
@@ -76,26 +85,37 @@ def read(size = nil)
7685 end
7786 end
7887
79- return consume_read_buffer ( size )
88+ return consume_read_buffer ( size , buffer )
8089 end
8190
8291 # Read at most `size` bytes from the stream. Will avoid reading from the underlying stream if possible.
83- def read_partial ( size = nil )
84- return String . new ( encoding : Encoding ::BINARY ) if size == 0
92+ # @parameter size [Integer | Nil] The number of bytes to read. If nil, read all available data.
93+ # @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
94+ # @returns [String] The data read from the stream, or the provided buffer filled with data.
95+ def read_partial ( size = nil , buffer = nil )
96+ if size == 0
97+ if buffer
98+ buffer . clear
99+ buffer . force_encoding ( Encoding ::BINARY )
100+ return buffer
101+ else
102+ return String . new ( encoding : Encoding ::BINARY )
103+ end
104+ end
85105
86106 if !@done and @read_buffer . empty?
87107 fill_read_buffer
88108 end
89109
90- return consume_read_buffer ( size )
110+ return consume_read_buffer ( size , buffer )
91111 end
92112
93113 # Read exactly the specified number of bytes.
94114 # @parameter size [Integer] The number of bytes to read.
95115 # @parameter exception [Class] The exception to raise if not enough data is available.
96116 # @returns [String] The data read from the stream.
97- def read_exactly ( size , exception : EOFError )
98- if buffer = read ( size )
117+ def read_exactly ( size , buffer = nil , exception : EOFError )
118+ if buffer = read ( size , buffer )
99119 if buffer . bytesize != size
100120 raise exception , "Could not read enough data!"
101121 end
@@ -107,8 +127,11 @@ def read_exactly(size, exception: EOFError)
107127 end
108128
109129 # This is a compatibility shim for existing code that uses `readpartial`.
110- def readpartial ( size = nil )
111- read_partial ( size ) or raise EOFError , "Encountered done while reading data!"
130+ # @parameter size [Integer | Nil] The number of bytes to read.
131+ # @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
132+ # @returns [String] The data read from the stream.
133+ def readpartial ( size = nil , buffer = nil )
134+ read_partial ( size , buffer ) or raise EOFError , "Encountered done while reading data!"
112135 end
113136
114137 # Find the index of a pattern in the read buffer, reading more data if needed.
@@ -330,25 +353,43 @@ def fill_read_buffer(size = @minimum_read_size)
330353
331354 # Consumes at most `size` bytes from the buffer.
332355 # @parameter size [Integer | Nil] The amount of data to consume. If nil, consume entire buffer.
333- def consume_read_buffer ( size = nil )
356+ # @parameter buffer [String | Nil] An optional buffer to fill with data instead of allocating a new string.
357+ # @returns [String | Nil] The consumed data, or nil if no data available.
358+ def consume_read_buffer ( size = nil , buffer = nil )
334359 # If we are at done, and the read buffer is empty, we can't consume anything.
335- return nil if @done && @read_buffer . empty?
360+ if @done && @read_buffer . empty?
361+ # Clear the buffer even when returning nil
362+ if buffer
363+ buffer . clear
364+ buffer . force_encoding ( Encoding ::BINARY )
365+ end
366+ return nil
367+ end
336368
337369 result = nil
338370
339371 if size . nil? or size >= @read_buffer . bytesize
340372 # Consume the entire read buffer:
341- result = @read_buffer
373+ if buffer
374+ buffer . clear
375+ buffer << @read_buffer
376+ result = buffer
377+ else
378+ result = @read_buffer
379+ end
342380 @read_buffer = StringBuffer . new
343381 else
344- # This approach uses more memory.
345- # result = @read_buffer.slice!(0, size)
346-
347382 # We know that we are not going to reuse the original buffer.
348383 # But byteslice will generate a hidden copy. So let's freeze it first:
349384 @read_buffer . freeze
350385
351- result = @read_buffer . byteslice ( 0 , size )
386+ if buffer
387+ # Use replace instead of clear + << for better performance
388+ buffer . replace ( @read_buffer . byteslice ( 0 , size ) )
389+ result = buffer
390+ else
391+ result = @read_buffer . byteslice ( 0 , size )
392+ end
352393 @read_buffer = @read_buffer . byteslice ( size , @read_buffer . bytesize )
353394 end
354395
0 commit comments