Class: Ione::ByteBuffer
- Inherits:
-
Object
- Object
- Ione::ByteBuffer
- Defined in:
- lib/ione/byte_buffer.rb
Overview
A byte buffer is a more efficient way of working with bytes than using
a regular Ruby string. It also has convenient methods for reading integers
shorts and single bytes that are faster than String#unpack
.
When you use a string as a buffer, by adding to the end and taking away from the beginning, Ruby will continue to grow the backing array of characters. This means that the longer you use the string the worse the performance will get and the more memory you waste.
ByteBuffer solves the problem by using two strings: one is the read buffer and one is the write buffer. Writes go to the write buffer only, and reads read from the read buffer until it is empty, then a new write buffer is created and the old write buffer becomes the new read buffer.
Instance Attribute Summary collapse
-
#length ⇒ Object
(also: #size, #bytesize)
readonly
Returns the number of bytes in the buffer.
Instance Method Summary collapse
-
#append(bytes) ⇒ Ione::ByteBuffer
(also: #<<)
Append the bytes from a string or another byte buffer to this buffer.
-
#cheap_peek ⇒ String
Return as much of the buffer as possible without having to concatenate or allocate any unnecessary strings.
-
#discard(n) ⇒ Ione::ByteBuffer
Remove the first N bytes from the buffer.
- #dup ⇒ Object
-
#empty? ⇒ Boolean
Returns true when the number of bytes in the buffer is zero.
- #eql?(other) ⇒ Boolean (also: #==)
- #hash ⇒ Object
- #index(substring, start_index = 0) ⇒ Object
-
#initialize(initial_bytes = nil) ⇒ ByteBuffer
constructor
A new instance of ByteBuffer.
- #inspect ⇒ Object
-
#read(n) ⇒ String
Remove and return the first N bytes from the buffer.
-
#read_byte(signed = false) ⇒ Integer
Remove and return the first byte from the buffer and decode it as a signed or unsigned integer.
-
#read_int ⇒ Integer
Remove and return the first four bytes from the buffer and decode them as an unsigned integer.
-
#read_short ⇒ Integer
Remove and return the first two bytes from the buffer and decode them as an unsigned integer.
- #to_str ⇒ Object (also: #to_s)
-
#update(location, bytes) ⇒ Ione::ByteBuffer
Overwrite a portion of the buffer with new bytes.
Constructor Details
#initialize(initial_bytes = nil) ⇒ ByteBuffer
Returns a new instance of ByteBuffer.
20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/ione/byte_buffer.rb', line 20 def initialize(initial_bytes=nil) @read_buffer = '' @offset = 0 @length = 0 if initial_bytes && !initial_bytes.empty? @write_buffer = initial_bytes.dup @write_buffer.force_encoding(::Encoding::BINARY) @length = @write_buffer.bytesize else @write_buffer = '' end end |
Instance Attribute Details
#length ⇒ Object (readonly) Also known as: size, bytesize
Returns the number of bytes in the buffer.
The value is cached so this is a cheap operation.
36 37 38 |
# File 'lib/ione/byte_buffer.rb', line 36 def length @length end |
Instance Method Details
#append(bytes) ⇒ Ione::ByteBuffer Also known as: <<
When the bytes are not in an ASCII compatible encoding they are copied
and retagged as Encoding::BINARY
before they are appended to the
buffer – this is required to avoid Ruby retagging the whole buffer with
the encoding of the new bytes. If you can, make sure that the data you
append is ASCII compatible (i.e. responds true to #ascii_only?
),
otherwise you will pay a small penalty for each append due to the extra
copy that has to be made.
Append the bytes from a string or another byte buffer to this buffer.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/ione/byte_buffer.rb', line 60 def append(bytes) if bytes.is_a?(self.class) bytes.append_to(self) else bytes = bytes.to_s unless bytes.ascii_only? bytes = bytes.dup.force_encoding(::Encoding::BINARY) end retag = @write_buffer.empty? @write_buffer << bytes @write_buffer.force_encoding(::Encoding::BINARY) if retag @length += bytes.bytesize end self end |
#cheap_peek ⇒ String
Return as much of the buffer as possible without having to concatenate or allocate any unnecessary strings.
If the buffer is not empty this method will return something, but there are no guarantees as to how much it will return. It's primarily useful in situations where a loop wants to offer some bytes but can't be sure how many will be accepted — for example when writing to a socket.
255 256 257 258 259 260 |
# File 'lib/ione/byte_buffer.rb', line 255 def cheap_peek if @offset >= @read_buffer.bytesize swap_buffers end @read_buffer[@offset, @read_buffer.bytesize - @offset] end |
#discard(n) ⇒ Ione::ByteBuffer
Remove the first N bytes from the buffer.
82 83 84 85 86 87 88 |
# File 'lib/ione/byte_buffer.rb', line 82 def discard(n) raise RangeError, 'Cannot discard a negative number of bytes' if n < 0 raise RangeError, "#{n} bytes to discard but only #{@length} available" if @length < n @offset += n @length -= n self end |
#dup ⇒ Object
271 272 273 |
# File 'lib/ione/byte_buffer.rb', line 271 def dup self.class.new(to_str) end |
#empty? ⇒ Boolean
Returns true when the number of bytes in the buffer is zero.
The length is cached so this is a cheap operation.
43 44 45 |
# File 'lib/ione/byte_buffer.rb', line 43 def empty? length == 0 end |
#eql?(other) ⇒ Boolean Also known as: ==
262 263 264 |
# File 'lib/ione/byte_buffer.rb', line 262 def eql?(other) self.to_str.eql?(other.to_str) end |
#hash ⇒ Object
267 268 269 |
# File 'lib/ione/byte_buffer.rb', line 267 def hash to_str.hash end |
#index(substring, start_index = 0) ⇒ Object
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/ione/byte_buffer.rb', line 177 def index(substring, start_index=0) if @offset >= @read_buffer.bytesize swap_buffers end read_buffer_length = @read_buffer.bytesize if start_index + substring.bytesize <= read_buffer_length - @offset && (index = @read_buffer.index(substring, @offset + start_index)) index - @offset elsif start_index + substring.bytesize <= read_buffer_length - @offset + @write_buffer.bytesize merge_read_buffer start_index = read_buffer_length - substring.bytesize if read_buffer_length - substring.bytesize > start_index @read_buffer.index(substring, start_index) else nil end end |
#inspect ⇒ Object
280 281 282 |
# File 'lib/ione/byte_buffer.rb', line 280 def inspect %(#<#{self.class.name}: #{to_str.inspect}>) end |
#read(n) ⇒ String
Remove and return the first N bytes from the buffer.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/ione/byte_buffer.rb', line 96 def read(n) raise RangeError, 'Cannot read a negative number of bytes' if n < 0 raise RangeError, "#{n} bytes required but only #{@length} available" if @length < n if @offset >= @read_buffer.bytesize swap_buffers end if @offset + n > @read_buffer.bytesize s = read(@read_buffer.bytesize - @offset) s << read(n - s.bytesize) s else s = @read_buffer[@offset, n] @offset += n @length -= n s end end |
#read_byte(signed = false) ⇒ Integer
Remove and return the first byte from the buffer and decode it as a signed or unsigned integer.
165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/ione/byte_buffer.rb', line 165 def read_byte(signed=false) raise RangeError, "No bytes available to read byte" if empty? if @offset >= @read_buffer.bytesize swap_buffers end b = @read_buffer.getbyte(@offset) b = (b & 0x7f) - (b & 0x80) if signed @offset += 1 @length -= 1 b end |
#read_int ⇒ Integer
Remove and return the first four bytes from the buffer and decode them as an unsigned integer.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/ione/byte_buffer.rb', line 118 def read_int raise RangeError, "4 bytes required to read an int, but only #{@length} available" if @length < 4 if @offset >= @read_buffer.bytesize swap_buffers end if @read_buffer.bytesize >= @offset + 4 i0 = @read_buffer.getbyte(@offset + 0) i1 = @read_buffer.getbyte(@offset + 1) i2 = @read_buffer.getbyte(@offset + 2) i3 = @read_buffer.getbyte(@offset + 3) @offset += 4 @length -= 4 else i0 = read_byte i1 = read_byte i2 = read_byte i3 = read_byte end (i0 << 24) | (i1 << 16) | (i2 << 8) | i3 end |
#read_short ⇒ Integer
Remove and return the first two bytes from the buffer and decode them as an unsigned integer.
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/ione/byte_buffer.rb', line 143 def read_short raise RangeError, "2 bytes required to read a short, but only #{@length} available" if @length < 2 if @offset >= @read_buffer.bytesize swap_buffers end if @read_buffer.bytesize >= @offset + 2 i0 = @read_buffer.getbyte(@offset + 0) i1 = @read_buffer.getbyte(@offset + 1) @offset += 2 @length -= 2 else i0 = read_byte i1 = read_byte end (i0 << 8) | i1 end |
#to_str ⇒ Object Also known as: to_s
275 276 277 |
# File 'lib/ione/byte_buffer.rb', line 275 def to_str (@read_buffer + @write_buffer)[@offset, @length] end |
#update(location, bytes) ⇒ Ione::ByteBuffer
Overwrite a portion of the buffer with new bytes.
The number of bytes that will be replaced depend on the size of the replacement string. If you pass a five byte string the five bytes starting at the location will be replaced.
When you pass more bytes than the size of the buffer after the location only as many as needed to replace the remaining bytes of the buffer will actually be used.
Make sure that you get your location right, if you have discarded bytes from the buffer all of the offsets will have changed.
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/ione/byte_buffer.rb', line 220 def update(location, bytes) absolute_offset = @offset + location bytes_length = bytes.bytesize if absolute_offset >= @read_buffer.bytesize @write_buffer[absolute_offset - @read_buffer.bytesize, bytes_length] = bytes else overflow = absolute_offset + bytes_length - @read_buffer.bytesize read_buffer_portion = bytes_length - overflow @read_buffer[absolute_offset, read_buffer_portion] = bytes[0, read_buffer_portion] if overflow > 0 @write_buffer[0, overflow] = bytes[read_buffer_portion, bytes_length - 1] end end self end |