Class: IO::Buffer
Relationships & Source Files | |
Namespace Children | |
Exceptions:
| |
Super Chains via Extension / Inclusion / Inheritance | |
Instance Chain:
self,
::Comparable
|
|
Inherits: | Object |
Defined in: | io_buffer.c, io_buffer.c |
Overview
Buffer
is a efficient zero-copy buffer for input/output. There are typical use cases:
-
Create an empty buffer with .new, fill it with buffer using #copy or #set_value, #set_string, get buffer with #get_string or write it directly to some file with #write.
-
Create a buffer mapped to some string with .for, then it could be used both for reading with #get_string or #get_value, and writing (writing will change the source string, too).
-
Create a buffer mapped to some file with .map, then it could be used for reading and writing the underlying file.
-
Create a string of a fixed size with .string, then #read into it, or modify it using #set_value.
Interaction with string and file memory is performed by efficient low-level C mechanisms like memcpy
.
The class is meant to be an utility for implementing more high-level mechanisms like Fiber::Scheduler#io_read and Fiber::Scheduler#io_write and parsing binary protocols.
Examples of Usage
Empty buffer:
buffer = IO::Buffer.new(8) # create empty 8-byte buffer
# =>
# #<IO::Buffer 0x0000555f5d1a5c50+8 INTERNAL>
# ...
buffer
# =>
# <IO::Buffer 0x0000555f5d156ab0+8 INTERNAL>
# 0x00000000 00 00 00 00 00 00 00 00
buffer.set_string('test', 2) # put there bytes of the "test" string, starting from offset 2
# => 4
buffer.get_string # get the result
# => "\x00\x00test\x00\x00"
Buffer from string:
string = 'data'
IO::Buffer.for(string) do |buffer|
buffer
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# 0x00000000 64 61 74 61 data
buffer.get_string(2) # read content starting from offset 2
# => "ta"
buffer.set_string('---', 1) # write content, starting from offset 1
# => 3
buffer
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# 0x00000000 64 2d 2d 2d d---
string # original string changed, too
# => "d---"
end
Buffer from file:
File.write('test.txt', 'test data')
# => 9
buffer = IO::Buffer.map(File.open('test.txt'))
# =>
# #<IO::Buffer 0x00007f3f0768c000+9 MAPPED IMMUTABLE>
# ...
buffer.get_string(5, 2) # read 2 bytes, starting from offset 5
# => "da"
buffer.set_string('---', 1) # attempt to write
# in `set_string': Buffer is not writable! (IO::Buffer::AccessError)
# To create writable file-mapped buffer
# Open file for read-write, pass size, offset, and flags=0
buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 9, 0, 0)
buffer.set_string('---', 1)
# => 3 -- bytes written
File.read('test.txt')
# => "t--- data"
The class is experimental and the interface is subject to change, this is especially true of file mappings which may be removed entirely in the future.
Constant Summary
-
BIG_ENDIAN =
Refers to big endian byte order, where the most significant byte is stored first. See #get_value for more details.
RB_INT2NUM(RB_IO_BUFFER_BIG_ENDIAN)
-
DEFAULT_SIZE =
The default buffer size, typically a (small) multiple of the PAGE_SIZE.
Can be explicitly specified by setting the RUBY_IO_BUFFER_DEFAULT_SIZE environment variable.
SIZET2NUM(RUBY_IO_BUFFER_DEFAULT_SIZE)
-
EXTERNAL =
Indicates that the memory in the buffer is owned by someone else. See #external? for more details.
RB_INT2NUM(RB_IO_BUFFER_EXTERNAL)
-
HOST_ENDIAN =
Refers to the byte order of the host machine. See #get_value for more details.
RB_INT2NUM(RB_IO_BUFFER_HOST_ENDIAN)
-
INTERNAL =
Indicates that the memory in the buffer is owned by the buffer. See #internal? for more details.
RB_INT2NUM(RB_IO_BUFFER_INTERNAL)
-
LITTLE_ENDIAN =
Refers to little endian byte order, where the least significant byte is stored first. See #get_value for more details.
RB_INT2NUM(RB_IO_BUFFER_LITTLE_ENDIAN)
-
LOCKED =
RB_INT2NUM(RB_IO_BUFFER_LOCKED)
-
MAPPED =
Indicates that the memory in the buffer is mapped by the operating system. See #mapped? for more details.
RB_INT2NUM(RB_IO_BUFFER_MAPPED)
-
NETWORK_ENDIAN =
Refers to network byte order, which is the same as big endian. See #get_value for more details.
RB_INT2NUM(RB_IO_BUFFER_NETWORK_ENDIAN)
-
PAGE_SIZE =
The operating system page size. Used for efficient page-aligned memory allocations.
SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE)
-
PRIVATE =
Indicates that the memory in the buffer is mapped privately and changes won’t be replicated to the underlying file. See #private? for more details.
RB_INT2NUM(RB_IO_BUFFER_PRIVATE)
-
READONLY =
Indicates that the memory in the buffer is read only, and attempts to modify it will fail. See #readonly? for more details.
RB_INT2NUM(RB_IO_BUFFER_READONLY)
-
SHARED =
Indicates that the memory in the buffer is also mapped such that it can be shared with other processes. See #shared? for more details.
RB_INT2NUM(RB_IO_BUFFER_SHARED)
Class Method Summary
-
.for(string) ⇒ Buffer
Creates a zero-copy
Buffer
from the given string’s memory. -
.map(file, [size, [offset, [flags]]]) ⇒ Buffer
Create an
Buffer
for reading fromfile
by memory-mapping the file. -
.new([size = DEFAULT_SIZE, [flags) ⇒ Buffer
constructor
Create a new zero-filled
Buffer
of #size bytes. -
.size_of(buffer_type) ⇒ byte size
Returns the size of the given buffer type(s) in bytes.
-
.string(length) {|io_buffer| ... } ⇒ String
Creates a new string of the given length and yields a zero-copy
Buffer
instance to the block which uses the string as a source.
Instance Attribute Summary
- #empty? ⇒ Boolean readonly
- #external? ⇒ Boolean readonly
- #internal? ⇒ Boolean readonly
- #locked readonly
- #locked? ⇒ Boolean readonly
- #mapped? ⇒ Boolean readonly
- #null? ⇒ Boolean readonly
- #private? ⇒ Boolean readonly
- #readonly? ⇒ Boolean readonly
- #shared? ⇒ Boolean readonly
- #valid? ⇒ Boolean readonly
Instance Method Summary
-
#&(mask) ⇒ Buffer
Generate a new buffer the same size as the source by applying the binary AND operation to the source, using the mask, repeating as necessary.
- #<=>
-
#^(mask) ⇒ Buffer
Generate a new buffer the same size as the source by applying the binary XOR operation to the source, using the mask, repeating as necessary.
-
#and!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary AND operation to the source, using the mask, repeating as necessary.
-
#clear(value = 0, [offset, [length]]) ⇒ self
Fill buffer with
value
, starting withoffset
and going forlength
bytes. -
#copy(source, [offset, [length, [source_offset]]]) ⇒ size
Efficiently copy from a source
Buffer
into the buffer, atoffset
usingmemmove
. - #free
-
#get_string([offset, [length, [encoding]]]) ⇒ String
Read a chunk or all of the buffer into a string, in the specified
encoding
. -
#get_value(buffer_type, offset) ⇒ Numeric
Read from buffer a value of
type
atoffset
. -
#get_values(buffer_types, offset) ⇒ Array
Similar to #get_value, except that it can handle multiple buffer types and returns an array of values.
- #hexdump
-
#not! ⇒ Buffer
Modify the source buffer in place by applying the binary NOT operation to the source.
-
#or!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary OR operation to the source, using the mask, repeating as necessary.
-
#read(io, [length, [offset]]) ⇒ read length, -errno
Read at least
length
bytes from theio
, into the buffer starting atoffset
. - #resize
-
#set_string(string, [offset, [length, [source_offset]]]) ⇒ size
Efficiently copy from a source
::String
into the buffer, atoffset
usingmemmove
. -
#set_value(type, offset, value) ⇒ offset
Write to a buffer a
value
oftype
atoffset
. -
#set_values(buffer_types, offset, values) ⇒ offset
Write #values of
buffer_types
atoffset
to the buffer. - #size
- #slice
- #to_s
- #transfer
-
#values(buffer_type, [offset, [count]]) ⇒ Array
Returns an array of values of
buffer_type
starting fromoffset
. -
#write(io, [length, [offset]]) ⇒ written length, -errno
Write at least
length
bytes from the buffer starting atoffset
, into theio
. -
#xor!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary XOR operation to the source, using the mask, repeating as necessary.
-
#|(mask) ⇒ Buffer
Generate a new buffer the same size as the source by applying the binary OR operation to the source, using the mask, repeating as necessary.
-
#~ ⇒ Buffer
Generate a new buffer the same size as the source by applying the binary NOT operation to the source.
::Comparable
- Included
#< | Compares two objects based on the receiver’s #<=> method, returning true if it returns a value less than 0. |
#<= | Compares two objects based on the receiver’s #<=> method, returning true if it returns a value less than or equal to 0. |
#== | Compares two objects based on the receiver’s #<=> method, returning true if it returns 0. |
#> | Compares two objects based on the receiver’s #<=> method, returning true if it returns a value greater than 0. |
#>= | Compares two objects based on the receiver’s #<=> method, returning true if it returns a value greater than or equal to 0. |
#between? | |
#clamp |
Constructor Details
.new([size = DEFAULT_SIZE, [flags) ⇒ Buffer
Create a new zero-filled Buffer
of #size bytes. By default, the buffer will be internal: directly allocated chunk of the memory. But if the requested #size is more than OS-specific PAGE_SIZE, the buffer would be allocated using the virtual memory mechanism (anonymous mmap
on Unix, VirtualAlloc
on Windows). The behavior can be forced by passing MAPPED as a second parameter.
buffer = IO::Buffer.new(4)
# =>
# #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
# 0x00000000 00 00 00 00 ....
buffer.get_string(0, 1) # => "\x00"
buffer.set_string("test")
buffer
# =>
# #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
# 0x00000000 74 65 73 74 test
# File 'io_buffer.c', line 754
VALUE rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) { io_buffer_experimental(); rb_check_arity(argc, 0, 2); struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); size_t size; if (argc > 0) { size = io_buffer_extract_size(argv[0]); } else { size = RUBY_IO_BUFFER_DEFAULT_SIZE; } enum rb_io_buffer_flags flags = 0; if (argc >= 2) { flags = io_buffer_extract_flags(argv[1]); } else { flags |= io_flags_for_size(size); } io_buffer_initialize(self, buffer, NULL, size, flags, Qnil); return self; }
Class Method Details
.for(string) ⇒ Buffer
.for(string) {|io_buffer| ... }
Buffer
.for(string) {|io_buffer| ... }
Creates a zero-copy Buffer
from the given string’s memory. Without a block a frozen internal copy of the string is created efficiently and used as the buffer source. When a block is provided, the buffer is associated directly with the string’s internal buffer and updating the buffer will update the string.
Until #free is invoked on the buffer, either explicitly or via the garbage collector, the source string will be locked and cannot be modified.
If the string is frozen, it will create a read-only buffer which cannot be modified. If the string is shared, it may trigger a copy-on-write when using the block form.
string = 'test'
buffer = IO::Buffer.for(string)
buffer.external? #=> true
buffer.get_string(0, 1)
# => "t"
string
# => "best"
buffer.resize(100)
# in `resize': Cannot resize external buffer! (IO::Buffer::AccessError)
IO::Buffer.for(string) do |buffer|
buffer.set_string("T")
string
# => "Test"
end
# File 'io_buffer.c', line 556
VALUE rb_io_buffer_type_for(VALUE klass, VALUE string) { StringValue(string); // If the string is frozen, both code paths are okay. // If the string is not frozen, if a block is not given, it must be frozen. if (rb_block_given_p()) { struct io_buffer_for_yield_instance_arguments arguments = { .klass = klass, .string = string, .instance = Qnil, .flags = 0, }; return rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments); } else { // This internally returns the source string if it's already frozen. string = rb_str_tmp_frozen_acquire(string); return io_buffer_for_make_instance(klass, string, RB_IO_BUFFER_READONLY); } }
.map(file, [size, [offset, [flags]]]) ⇒ Buffer
Create an Buffer
for reading from file
by memory-mapping the file. file_io
should be a ::File
instance, opened for reading.
Optional #size and offset
of mapping can be specified.
By default, the buffer would be immutable (read only); to create a writable mapping, you need to open a file in read-write mode, and explicitly pass flags
argument without IO::Buffer::IMMUTABLE
.
File.write('test.txt', 'test')
buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
# => #<IO::Buffer 0x00000001014a0000+4 MAPPED READONLY>
buffer.readonly? # => true
buffer.get_string
# => "test"
buffer.set_string('b', 0)
# `set_string': Buffer is not writable! (IO::Buffer::AccessError)
# create read/write mapping: length 4 bytes, offset 0, flags 0
buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0)
buffer.set_string('b', 0)
# => 1
# Check it
File.read('test.txt')
# => "best"
Note that some operating systems may not have cache coherency between mapped buffers and file reads.
# File 'io_buffer.c', line 676
static VALUE io_buffer_map(int argc, VALUE *argv, VALUE klass) { rb_check_arity(argc, 1, 4); // We might like to handle a string path? VALUE io = argv[0]; size_t size; if (argc >= 2 && !RB_NIL_P(argv[1])) { size = io_buffer_extract_size(argv[1]); } else { rb_off_t file_size = rb_file_size(io); // Compiler can confirm that we handled file_size < 0 case: if (file_size < 0) { rb_raise(rb_eArgError, "Invalid negative file size!"); } // Here, we assume that file_size is positive: else if ((uintmax_t)file_size > SIZE_MAX) { rb_raise(rb_eArgError, "File larger than address space!"); } else { // This conversion should be safe: size = (size_t)file_size; } } // This is the file offset, not the buffer offset: rb_off_t offset = 0; if (argc >= 3) { offset = NUM2OFFT(argv[2]); } enum rb_io_buffer_flags flags = 0; if (argc >= 4) { flags = io_buffer_extract_flags(argv[3]); } return rb_io_buffer_map(io, size, offset, flags); }
# File 'io_buffer.c', line 1936
static VALUE io_buffer_size_of(VALUE klass, VALUE buffer_type) { if (RB_TYPE_P(buffer_type, T_ARRAY)) { size_t total = 0; for (long i = 0; i < RARRAY_LEN(buffer_type); i++) { total += io_buffer_buffer_type_size(RB_SYM2ID(RARRAY_AREF(buffer_type, i))); } return SIZET2NUM(total); } else { return SIZET2NUM(io_buffer_buffer_type_size(RB_SYM2ID(buffer_type))); } }
.string(length) {|io_buffer| ... } ⇒ String
Creates a new string of the given length and yields a zero-copy Buffer
instance to the block which uses the string as a source. The block is expected to write to the buffer and the string will be returned.
IO::Buffer.string(4) do |buffer|
buffer.set_string("Ruby")
end
# => "Ruby"
# File 'io_buffer.c', line 593
VALUE rb_io_buffer_type_string(VALUE klass, VALUE length) { VALUE string = rb_str_new(NULL, RB_NUM2LONG(length)); struct io_buffer_for_yield_instance_arguments arguments = { .klass = klass, .string = string, .instance = Qnil, }; rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments); return string; }
Instance Attribute Details
#empty? ⇒ Boolean
(readonly)
[ GitHub ]
#external? ⇒ Boolean
(readonly)
[ GitHub ]
#internal? ⇒ Boolean
(readonly)
[ GitHub ]
#locked (readonly)
[ GitHub ]
#locked? ⇒ Boolean
(readonly)
[ GitHub ]
#mapped? ⇒ Boolean
(readonly)
[ GitHub ]
#null? ⇒ Boolean
(readonly)
[ GitHub ]
#private? ⇒ Boolean
(readonly)
[ GitHub ]
#readonly? ⇒ Boolean
(readonly)
[ GitHub ]
#valid? ⇒ Boolean
(readonly)
[ GitHub ]
Instance Method Details
#&(mask) ⇒ Buffer
Generate a new buffer the same size as the source by applying the binary AND operation to the source, using the mask, repeating as necessary.
IO::Buffer.for("1234567890") & IO::Buffer.for("\xFF\x00\x00\xFF")
# =>
# #<IO::Buffer 0x00005589b2758480+4 INTERNAL>
# 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
# File 'io_buffer.c', line 3256
static VALUE io_buffer_and(VALUE self, VALUE mask) { struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); struct rb_io_buffer *mask_buffer = NULL; TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); io_buffer_check_mask(mask_buffer); VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); struct rb_io_buffer *output_buffer = NULL; TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); memory_and(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; }
#<=>
[ GitHub ]
#^(mask) ⇒ Buffer
Generate a new buffer the same size as the source by applying the binary XOR operation to the source, using the mask, repeating as necessary.
IO::Buffer.for("1234567890") ^ IO::Buffer.for("\xFF\x00\x00\xFF")
# =>
# #<IO::Buffer 0x000055a2d5d10480+10 INTERNAL>
# 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0
# File 'io_buffer.c', line 3336
static VALUE io_buffer_xor(VALUE self, VALUE mask) { struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); struct rb_io_buffer *mask_buffer = NULL; TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); io_buffer_check_mask(mask_buffer); VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); struct rb_io_buffer *output_buffer = NULL; TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); memory_xor(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; }
#and!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary AND operation to the source, using the mask, repeating as necessary.
source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
# =>
# #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL>
# 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
source.and!(IO::Buffer.for("\xFF\x00\x00\xFF"))
# =>
# #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL>
# 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
# File 'io_buffer.c', line 3433
static VALUE io_buffer_and_inplace(VALUE self, VALUE mask) { struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); struct rb_io_buffer *mask_buffer = NULL; TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); io_buffer_check_mask(mask_buffer); io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); memory_and_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; }
#clear(value = 0, [offset, [length]]) ⇒ self
Fill buffer with value
, starting with offset
and going for length
bytes.
buffer = IO::Buffer.for('test').dup
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 74 65 73 74 test
buffer.clear
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 00 00 00 00 ....
buf.clear(1) # fill with 1
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 01 01 01 01 ....
buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 01 02 02 01 ....
buffer.clear(2, 1) # fill with 2, starting from offset 1
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 01 02 02 02 ....
# File 'io_buffer.c', line 2667
static VALUE io_buffer_clear(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); uint8_t value = 0; if (argc >= 1) { value = NUM2UINT(argv[0]); } size_t offset, length; io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length); rb_io_buffer_clear(self, value, offset, length); return self; }
#copy(source, [offset, [length, [source_offset]]]) ⇒ size
Efficiently copy from a source Buffer
into the buffer, at offset
using memmove
. For copying ::String
instances, see #set_string.
buffer = IO::Buffer.new(32)
# =>
# #<IO::Buffer 0x0000555f5ca22520+32 INTERNAL>
# 0x00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
# 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *
buffer.copy(IO::Buffer.for("test"), 8)
# => 4 -- size of buffer copied
buffer
# =>
# #<IO::Buffer 0x0000555f5cf8fe40+32 INTERNAL>
# 0x00000000 00 00 00 00 00 00 00 00 74 65 73 74 00 00 00 00 ........test....
# 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *
#copy
can be used to put buffer into strings associated with buffer:
string = "data: "
# => "data: "
buffer = IO::Buffer.for(string) do |buffer|
buffer.copy(IO::Buffer.for("test"), 5)
end
# => 4
string
# => "data:test"
Attempt to copy into a read-only buffer will fail:
File.write('test.txt', 'test')
buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
buffer.copy(IO::Buffer.for("test"), 8)
# in `copy': Buffer is not writable! (IO::Buffer::AccessError)
See .map for details of creation of mutable file mappings, this will work:
buffer = IO::Buffer.map(File.open('test.txt', 'r+'))
buffer.copy(IO::Buffer.for("boom"), 0)
# => 4
File.read('test.txt')
# => "boom"
Attempt to copy the buffer which will need place outside of buffer’s bounds will fail:
buffer = IO::Buffer.new(2)
buffer.copy(IO::Buffer.for('test'), 0)
# in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError)
It is safe to copy between memory regions that overlaps each other. In such case, the data is copied as if the data was first copied from the source buffer to a temporary buffer, and then copied from the temporary buffer to the destination buffer.
buffer = IO::Buffer.new(10)
buffer.set_string("0123456789")
buffer.copy(buffer, 3, 7)
# => 7
buffer
# =>
# #<IO::Buffer 0x000056494f8ce440+10 INTERNAL>
# 0x00000000 30 31 32 30 31 32 33 34 35 36 0120123456
# File 'io_buffer.c', line 2526
static VALUE io_buffer_copy(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 4); struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE source = argv[0]; const void *source_base; size_t source_size; rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size); return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); }
#free
[ GitHub ]#get_string([offset, [length, [encoding]]]) ⇒ String
# File 'io_buffer.c', line 2557
static VALUE io_buffer_get_string(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 0, 3); size_t offset, length; struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length); const void *base; size_t size; io_buffer_get_bytes_for_reading(buffer, &base, &size); rb_encoding *encoding; if (argc >= 3) { encoding = rb_find_encoding(argv[2]); } else { encoding = rb_ascii8bit_encoding(); } io_buffer_validate_range(buffer, offset, length); return rb_enc_str_new((const char*)base + offset, length, encoding); }
#get_value(buffer_type, offset) ⇒ Numeric
Read from buffer a value of type
at offset
. buffer_type
should be one of symbols:
-
:U8
: unsigned integer, 1 byte -
:S8
: signed integer, 1 byte -
:u16
: unsigned integer, 2 bytes, little-endian -
:U16
: unsigned integer, 2 bytes, big-endian -
:s16
: signed integer, 2 bytes, little-endian -
:S16
: signed integer, 2 bytes, big-endian -
:u32
: unsigned integer, 4 bytes, little-endian -
:U32
: unsigned integer, 4 bytes, big-endian -
:s32
: signed integer, 4 bytes, little-endian -
:S32
: signed integer, 4 bytes, big-endian -
:u64
: unsigned integer, 8 bytes, little-endian -
:U64
: unsigned integer, 8 bytes, big-endian -
:s64
: signed integer, 8 bytes, little-endian -
:S64
: signed integer, 8 bytes, big-endian -
:f32
: float, 4 bytes, little-endian -
:F32
: float, 4 bytes, big-endian -
:f64
: double, 8 bytes, little-endian -
:F64
: double, 8 bytes, big-endian
A buffer type refers specifically to the type of binary buffer that is stored in the buffer. For example, a :u32
buffer type is a 32-bit unsigned integer in little-endian format.
string = [1.5].pack('f')
# => "\x00\x00\xC0?"
IO::Buffer.for(string).get_value(:f32, 0)
# => 1.5
# File 'io_buffer.c', line 2016
static VALUE io_buffer_get_value(VALUE self, VALUE type, VALUE _offset) { const void *base; size_t size; size_t offset = io_buffer_extract_offset(_offset); rb_io_buffer_get_bytes_for_reading(self, &base, &size); return rb_io_buffer_get_value(base, size, RB_SYM2ID(type), &offset); }
#get_values(buffer_types, offset) ⇒ Array
Similar to #get_value, except that it can handle multiple buffer types and returns an array of values.
string = [1.5, 2.5].pack('ff')
IO::Buffer.for(string).get_values([:f32, :f32], 0)
# => [1.5, 2.5]
# File 'io_buffer.c', line 2038
static VALUE io_buffer_get_values(VALUE self, VALUE buffer_types, VALUE _offset) { size_t offset = io_buffer_extract_offset(_offset); const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(self, &base, &size); if (!RB_TYPE_P(buffer_types, T_ARRAY)) { rb_raise(rb_eArgError, "Argument buffer_types should be an array!"); } VALUE array = rb_ary_new_capa(RARRAY_LEN(buffer_types)); for (long i = 0; i < RARRAY_LEN(buffer_types); i++) { VALUE type = rb_ary_entry(buffer_types, i); VALUE value = rb_io_buffer_get_value(base, size, RB_SYM2ID(type), &offset); rb_ary_push(array, value); } return array; }
#hexdump
[ GitHub ]
#not! ⇒ Buffer
Modify the source buffer in place by applying the binary NOT operation to the source.
source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
# =>
# #<IO::Buffer 0x000056307a33a450+10 INTERNAL>
# 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
source.not!
# =>
# #<IO::Buffer 0x000056307a33a450+10 INTERNAL>
# 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf ..........
# File 'io_buffer.c', line 3571
static VALUE io_buffer_not_inplace(VALUE self) { struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); memory_not_inplace(base, size); return self; }
#or!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary OR operation to the source, using the mask, repeating as necessary.
source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
# =>
# #<IO::Buffer 0x000056307a272350+10 INTERNAL>
# 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
source.or!(IO::Buffer.for("\xFF\x00\x00\xFF"))
# =>
# #<IO::Buffer 0x000056307a272350+10 INTERNAL>
# 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0
# File 'io_buffer.c', line 3479
static VALUE io_buffer_or_inplace(VALUE self, VALUE mask) { struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); struct rb_io_buffer *mask_buffer = NULL; TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); io_buffer_check_mask(mask_buffer); io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); memory_or_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; }
#read(io, [length, [offset]]) ⇒ read length
, -errno
Read at least length
bytes from the io
, into the buffer starting at offset
. If an error occurs, return -errno
.
If length
is not given or nil
, it defaults to the size of the buffer minus the offset, i.e. the entire buffer.
If length
is zero, exactly one read
operation will occur.
If offset
is not given, it defaults to zero, i.e. the beginning of the buffer.
IO::Buffer.for('test') do |buffer|
p buffer
# =>
# <IO::Buffer 0x00007fca40087c38+4 SLICE>
# 0x00000000 74 65 73 74 test
buffer.read(File.open('/dev/urandom', 'rb'), 2)
p buffer
# =>
# <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
# 0x00000000 05 35 73 74 .5st
end
# File 'io_buffer.c', line 2862
static VALUE io_buffer_read(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 3); VALUE io = argv[0]; size_t length, offset; io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset); return rb_io_buffer_read(self, io, length, offset); }
#resize
[ GitHub ]#set_string(string, [offset, [length, [source_offset]]]) ⇒ size
Efficiently copy from a source ::String
into the buffer, at offset
using memmove
.
buf = IO::Buffer.new(8)
# =>
# #<IO::Buffer 0x0000557412714a20+8 INTERNAL>
# 0x00000000 00 00 00 00 00 00 00 00 ........
# set buffer starting from offset 1, take 2 bytes starting from string's
# second
buf.set_string('test', 1, 2, 1)
# => 2
buf
# =>
# #<IO::Buffer 0x0000557412714a20+8 INTERNAL>
# 0x00000000 00 65 73 00 00 00 00 00 .es.....
See also #copy for examples of how buffer writing might be used for changing associated strings and files.
# File 'io_buffer.c', line 2605
static VALUE io_buffer_set_string(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 4); struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE string = rb_str_to_str(argv[0]); const void *source_base = RSTRING_PTR(string); size_t source_size = RSTRING_LEN(string); return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1); }
#set_value(type, offset, value) ⇒ offset
Write to a buffer a value
of type
at offset
. type
should be one of symbols described in #get_value.
buffer = IO::Buffer.new(8)
# =>
# #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
# 0x00000000 00 00 00 00 00 00 00 00
buffer.set_value(:U8, 1, 111)
# => 1
buffer
# =>
# #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
# 0x00000000 00 6f 00 00 00 00 00 00 .o......
Note that if the type
is integer and value
is ::Float
, the implicit truncation is performed:
buffer = IO::Buffer.new(8)
buffer.set_value(:U32, 0, 2.5)
buffer
# =>
# #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
# 0x00000000 00 00 00 02 00 00 00 00
# ^^ the same as if we'd pass just integer 2
# File 'io_buffer.c', line 2278
static VALUE io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) { void *base; size_t size; size_t offset = io_buffer_extract_offset(_offset); rb_io_buffer_get_bytes_for_writing(self, &base, &size); rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value); return SIZET2NUM(offset); }
#set_values(buffer_types, offset, values) ⇒ offset
Write #values of buffer_types
at offset
to the buffer. buffer_types
should be an array of symbols as described in #get_value. #values should be an array of values to write.
buffer = IO::Buffer.new(8)
buffer.set_values([:U8, :U16], 0, [1, 2])
buffer
# =>
# #<IO::Buffer 0x696f717561746978+8 INTERNAL>
# 0x00000000 01 00 02 00 00 00 00 00 ........
# File 'io_buffer.c', line 2306
static VALUE io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values) { if (!RB_TYPE_P(buffer_types, T_ARRAY)) { rb_raise(rb_eArgError, "Argument buffer_types should be an array!"); } if (!RB_TYPE_P(values, T_ARRAY)) { rb_raise(rb_eArgError, "Argument values should be an array!"); } if (RARRAY_LEN(buffer_types) != RARRAY_LEN(values)) { rb_raise(rb_eArgError, "Argument buffer_types and values should have the same length!"); } size_t offset = io_buffer_extract_offset(_offset); void *base; size_t size; rb_io_buffer_get_bytes_for_writing(self, &base, &size); for (long i = 0; i < RARRAY_LEN(buffer_types); i++) { VALUE type = rb_ary_entry(buffer_types, i); VALUE value = rb_ary_entry(values, i); rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value); } return SIZET2NUM(offset); }
#size
[ GitHub ]#slice
[ GitHub ]#to_s
[ GitHub ]#transfer
[ GitHub ]#values(buffer_type, [offset, [count]]) ⇒ Array
# File 'io_buffer.c', line 2152
static VALUE io_buffer_values(int argc, VALUE *argv, VALUE self) { const void *base; size_t size; rb_io_buffer_get_bytes_for_reading(self, &base, &size); ID buffer_type; if (argc >= 1) { buffer_type = RB_SYM2ID(argv[0]); } else { buffer_type = RB_IO_BUFFER_DATA_TYPE_U8; } size_t offset, count; io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count); VALUE array = rb_ary_new_capa(count); for (size_t i = 0; i < count; i++) { VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset); rb_ary_push(array, value); } return array; }
#write(io, [length, [offset]]) ⇒ written
length
, -errno
Write at least length
bytes from the buffer starting at offset
, into the io
. If an error occurs, return -errno
.
If length
is not given or nil
, it defaults to the size of the buffer minus the offset, i.e. the entire buffer.
If length
is zero, exactly one write
operation will occur.
If offset
is not given, it defaults to zero, i.e. the beginning of the buffer.
out = File.open('output.txt', 'wb')
IO::Buffer.for('1234567').write(out, 3)
This leads to 123
being written into output.txt
# File 'io_buffer.c', line 3092
static VALUE io_buffer_write(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, 3); VALUE io = argv[0]; size_t length, offset; io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset); return rb_io_buffer_write(self, io, length, offset); }
#xor!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary XOR operation to the source, using the mask, repeating as necessary.
source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
# =>
# #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL>
# 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890
source.xor!(IO::Buffer.for("\xFF\x00\x00\xFF"))
# =>
# #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL>
# 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0
# File 'io_buffer.c', line 3525
static VALUE io_buffer_xor_inplace(VALUE self, VALUE mask) { struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); struct rb_io_buffer *mask_buffer = NULL; TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); io_buffer_check_mask(mask_buffer); io_buffer_check_overlaps(buffer, mask_buffer); void *base; size_t size; io_buffer_get_bytes_for_writing(buffer, &base, &size); memory_xor_inplace(base, size, mask_buffer->base, mask_buffer->size); return self; }
#|(mask) ⇒ Buffer
Generate a new buffer the same size as the source by applying the binary OR operation to the source, using the mask, repeating as necessary.
IO::Buffer.for("1234567890") | IO::Buffer.for("\xFF\x00\x00\xFF")
# =>
# #<IO::Buffer 0x0000561785ae3480+10 INTERNAL>
# 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0
# File 'io_buffer.c', line 3296
static VALUE io_buffer_or(VALUE self, VALUE mask) { struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); struct rb_io_buffer *mask_buffer = NULL; TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer); io_buffer_check_mask(mask_buffer); VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); struct rb_io_buffer *output_buffer = NULL; TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); memory_or(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size); return output; }
#~ ⇒ Buffer
# File 'io_buffer.c', line 3376
static VALUE io_buffer_not(VALUE self) { struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); struct rb_io_buffer *output_buffer = NULL; TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); memory_not(output_buffer->base, buffer->base, buffer->size); return output; }