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 low-level efficient buffer for input/output. There are three ways of using buffer:
-
Create an empty buffer with .new, fill it with data using #copy or #set_value, #set_string, get data with #get_string;
-
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.
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::SchedulerInterface#io_read and Fiber::SchedulerInterface#io_write.
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'
buffer = IO::Buffer.for(str)
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# ...
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---"
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.
Constant Summary
-
BIG_ENDIAN =
# File 'io_buffer.c', line 2238RB_INT2NUM(RB_IO_BUFFER_BIG_ENDIAN)
-
DEFAULT_SIZE =
# File 'io_buffer.c', line 2213SIZET2NUM(RUBY_IO_BUFFER_DEFAULT_SIZE)
-
EXTERNAL =
Flags:
RB_INT2NUM(RB_IO_BUFFER_EXTERNAL)
-
HOST_ENDIAN =
# File 'io_buffer.c', line 2239RB_INT2NUM(RB_IO_BUFFER_HOST_ENDIAN)
-
INTERNAL =
# File 'io_buffer.c', line 2230RB_INT2NUM(RB_IO_BUFFER_INTERNAL)
-
LITTLE_ENDIAN =
Endian:
RB_INT2NUM(RB_IO_BUFFER_LITTLE_ENDIAN)
-
LOCKED =
# File 'io_buffer.c', line 2232RB_INT2NUM(RB_IO_BUFFER_LOCKED)
-
MAPPED =
# File 'io_buffer.c', line 2231RB_INT2NUM(RB_IO_BUFFER_MAPPED)
-
NETWORK_ENDIAN =
# File 'io_buffer.c', line 2240RB_INT2NUM(RB_IO_BUFFER_NETWORK_ENDIAN)
-
PAGE_SIZE =
Efficient sizing of mapped buffers:
SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE)
-
PRIVATE =
# File 'io_buffer.c', line 2233RB_INT2NUM(RB_IO_BUFFER_PRIVATE)
-
READONLY =
# File 'io_buffer.c', line 2234RB_INT2NUM(RB_IO_BUFFER_READONLY)
Class Method Summary
-
.for(string) ⇒ Buffer
Creates a
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.
Instance Attribute Summary
-
#external? ⇒ Boolean
readonly
If the buffer is external, meaning it references from memory which is not allocated or mapped by the buffer itself.
- #external? ⇒ Boolean readonly
-
#internal? ⇒ Boolean
readonly
If the buffer is internal, meaning it references memory allocated by the buffer itself.
-
#locked
readonly
Allows to process a buffer in exclusive way, for concurrency-safety.
-
#locked? ⇒ Boolean
readonly
If the buffer is locked, meaning it is inside #locked block execution.
-
#mapped? ⇒ Boolean
readonly
If the buffer is mapped, meaning it references memory mapped by the buffer.
-
#null? ⇒ Boolean
readonly
If the buffer was freed with #free or was never allocated in the first place.
- #readonly? ⇒ Boolean readonly
-
#valid? ⇒ Boolean
readonly
Returns whether the buffer data is accessible.
Instance Method Summary
-
#<=>(other) ⇒ Boolean
Buffers are compared by size and exact contents of the memory they are referencing using
memcmp
. -
#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 data from a source
Buffer
into the buffer, atoffset
usingmemcpy
. -
#free ⇒ self
If the buffer references memory, release it back to the operating system.
-
#get_string([offset, [length, [encoding]]]) ⇒ String
Read a chunk or all of the buffer into a string, in the specified
encoding
. -
#get_value(type, offset) ⇒ Numeric
Read from buffer a value of
type
atoffset
. - #hexdump
-
#read(io, length)
::IO
operations: -
#resize(new_size) ⇒ self
Resizes a buffer to a
new_size
bytes, preserving its content. - #set_string(*args)
-
#set_value(type, offset, value) ⇒ offset
Write to a buffer a
value
oftype
atoffset
. - #size ⇒ Integer
-
#slice(offset, length) ⇒ Buffer
Produce another
Buffer
which is a slice (or view into) the current one starting atoffset
bytes and going forlength
bytes. -
#to_s ⇒ String
Short representation of the buffer.
-
#transfer ⇒ Buffer
Transfers ownership to a new buffer, deallocating the current one.
- #write(io, length)
::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 IO::Bufer::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.
Examples
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 548
VALUE rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self) { io_buffer_experimental(); if (argc < 0 || argc > 2) { rb_error_arity(argc, 0, 2); } struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); size_t size; if (argc > 0) { size = RB_NUM2SIZE(argv[0]); } else { size = RUBY_IO_BUFFER_DEFAULT_SIZE; } enum rb_io_buffer_flags flags = 0; if (argc >= 2) { flags = RB_NUM2UINT(argv[1]); } else { flags |= io_flags_for_size(size); } io_buffer_initialize(data, NULL, size, flags, Qnil); return self; }
Class Method Details
.for(string) ⇒ Buffer
.for(string) {|io_buffer| ... }
Buffer
.for(string) {|io_buffer| ... }
Creates a 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 data 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 375
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 466
static VALUE io_buffer_map(int argc, VALUE *argv, VALUE klass) { if (argc < 1 || argc > 4) { rb_error_arity(argc, 2, 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 = RB_NUM2SIZE(argv[1]); } else { 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; } } off_t offset = 0; if (argc >= 3) { offset = NUM2OFFT(argv[2]); } enum rb_io_buffer_flags flags = 0; if (argc >= 4) { flags = RB_NUM2UINT(argv[3]); } return rb_io_buffer_map(io, size, offset, flags); }
Instance Attribute Details
#external? ⇒ Boolean
(readonly)
If the buffer is external, meaning it references from memory which is not allocated or mapped by the buffer itself.
A buffer created using .for has an external reference to the string’s memory.
External buffer can’t be resized.
# File 'io_buffer.c', line 814
static VALUE rb_io_buffer_empty_p(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); return RBOOL(data->size == 0); }
#external? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'io_buffer.c', line 823
static VALUE rb_io_buffer_external_p(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); return RBOOL(data->flags & RB_IO_BUFFER_EXTERNAL); }
#internal? ⇒ Boolean
(readonly)
If the buffer is internal, meaning it references memory allocated by the buffer itself.
An internal buffer is not associated with any external memory (e.g. string) or file mapping.
Internal buffers are created using .new and is the default when the requested size is less than the PAGE_SIZE and it was not requested to be mapped on creation.
Internal buffers can be resized, and such an operation will typically invalidate all slices, but not always.
# File 'io_buffer.c', line 849
static VALUE rb_io_buffer_internal_p(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); return RBOOL(data->flags & RB_IO_BUFFER_INTERNAL); }
#locked (readonly)
Allows to process a buffer in exclusive way, for concurrency-safety. While the block is performed, the buffer is considered locked, and no other code can enter the lock. Also, locked buffer can’t be changed with #resize or #free.
buffer = IO::Buffer.new(4)
buffer.locked? #=> false
Fiber.schedule do
buffer.locked do
buffer.write(io) # theoretical system call interface
end
end
Fiber.schedule do
# in `locked': Buffer already locked! (IO::Buffer::LockedError)
buffer.locked do
buffer.set_string(...)
end
end
The following operations acquire a lock: #resize, #free.
Locking is not thread safe. It is designed as a safety net around non-blocking system calls. You can only share a buffer between threads with appropriate synchronisation techniques.
# File 'io_buffer.c', line 1003
VALUE rb_io_buffer_locked(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); if (data->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Buffer already locked!"); } data->flags |= RB_IO_BUFFER_LOCKED; VALUE result = rb_yield(self); data->flags &= ~RB_IO_BUFFER_LOCKED; return result; }
#locked? ⇒ Boolean
(readonly)
If the buffer is locked, meaning it is inside #locked block execution. Locked buffer can’t be resized or freed, and another lock can’t be acquired on it.
Locking is not thread safe, but is a semantic used to ensure buffers don’t move while being used by a system call.
buffer.locked do
buffer.write(io) # theoretical system call interface
end
# File 'io_buffer.c', line 896
static VALUE rb_io_buffer_locked_p(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); return RBOOL(data->flags & RB_IO_BUFFER_LOCKED); }
#mapped? ⇒ Boolean
(readonly)
If the buffer is mapped, meaning it references memory mapped by the buffer.
Mapped buffers are either anonymous, if created by .new with the MAPPED flag or if the size was at least PAGE_SIZE, or backed by a file if created with .map.
Mapped buffers can usually be resized, and such an operation will typically invalidate all slices, but not always.
# File 'io_buffer.c', line 872
static VALUE rb_io_buffer_mapped_p(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); return RBOOL(data->flags & RB_IO_BUFFER_MAPPED); }
#null? ⇒ Boolean
(readonly)
If the buffer was freed with #free or was never allocated in the first place.
# File 'io_buffer.c', line 793
static VALUE rb_io_buffer_null_p(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); return RBOOL(data->base == NULL); }
#readonly? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'io_buffer.c', line 923
static VALUE io_buffer_readonly_p(VALUE self) { return RBOOL(rb_io_buffer_readonly_p(self)); }
#valid? ⇒ Boolean
(readonly)
Returns whether the buffer data is accessible.
A buffer becomes invalid if it is a slice of another buffer which has been freed.
# File 'io_buffer.c', line 777
static VALUE rb_io_buffer_valid_p(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); return RBOOL(io_buffer_validate(data)); }
Instance Method Details
#<=>(other) ⇒ Boolean
Buffers are compared by size and exact contents of the memory they are referencing using memcmp
.
# File 'io_buffer.c', line 1375
static VALUE rb_io_buffer_compare(VALUE self, VALUE other) { const void *ptr1, *ptr2; size_t size1, size2; rb_io_buffer_get_bytes_for_reading(self, &ptr1, &size1); rb_io_buffer_get_bytes_for_reading(other, &ptr2, &size2); if (size1 < size2) { return RB_INT2NUM(-1); } if (size1 > size2) { return RB_INT2NUM(1); } return RB_INT2NUM(memcmp(ptr1, ptr2, size1)); }
#clear(value = 0, [offset, [length]]) ⇒ self
Fill buffer with value
, starting with offset
and going for length
bytes.
buffer = IO::Buffer.for('test')
# =>
# <IO::Buffer 0x00007fca40087c38+4 SLICE>
# 0x00000000 74 65 73 74 test
buffer.clear
# =>
# <IO::Buffer 0x00007fca40087c38+4 SLICE>
# 0x00000000 00 00 00 00 ....
buf.clear(1) # fill with 1
# =>
# <IO::Buffer 0x00007fca40087c38+4 SLICE>
# 0x00000000 01 01 01 01 ....
buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes
# =>
# <IO::Buffer 0x00007fca40087c38+4 SLICE>
# 0x00000000 01 02 02 01 ....
buffer.clear(2, 1) # fill with 2, starting from offset 1
# =>
# <IO::Buffer 0x00007fca40087c38+4 SLICE>
# 0x00000000 01 02 02 02 ....
# File 'io_buffer.c', line 1886
static VALUE io_buffer_clear(int argc, VALUE *argv, VALUE self) { if (argc > 3) rb_error_arity(argc, 0, 3); struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); uint8_t value = 0; if (argc >= 1) { value = NUM2UINT(argv[0]); } size_t offset = 0; if (argc >= 2) { offset = NUM2SIZET(argv[1]); } size_t length; if (argc >= 3) { length = NUM2SIZET(argv[2]); } else { length = data->size - offset; } rb_io_buffer_clear(self, value, offset, length); return self; }
#copy(source, [offset, [length, [source_offset]]]) ⇒ size
Efficiently copy data from a source Buffer
into the buffer, at offset
using memcpy
. 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 data 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 data into strings associated with buffer:
string= "data: "
# => "data: "
buffer = IO::Buffer.for(str)
buffer.copy(IO::Buffer.for("test"), 5)
# => 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("boom", 0)
# => 4
File.read('test.txt')
# => "boom"
Attempt to copy the data which will need place outside of buffer’s bounds will fail:
buffer = IO::Buffer.new(2)
buffer.copy('test', 0)
# in `copy': Specified offset+length exceeds source size! (ArgumentError)
# File 'io_buffer.c', line 1755
static VALUE io_buffer_copy(int argc, VALUE *argv, VALUE self) { if (argc < 1 || argc > 4) rb_error_arity(argc, 1, 4); struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); 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(data, source_base, source_size, argc-1, argv+1); }
#free ⇒ self
If the buffer references memory, release it back to the operating system.
-
for a mapped buffer (e.g. from file): unmap.
-
for a buffer created from scratch: free memory.
-
for a buffer created from string: undo the association.
After the buffer is freed, no further operations can’t be performed on it.
buffer = IO::Buffer.for('test')
buffer.free
# => #<IO::Buffer 0x0000000000000000+0 NULL>
buffer.get_value(:U8, 0)
# in `get_value': The buffer is not allocated! (IO::Buffer::AllocationError)
buffer.get_string
# in `get_string': The buffer is not allocated! (IO::Buffer::AllocationError)
buffer.null?
# => true
You can resize a freed buffer to re-allocate it.
# File 'io_buffer.c', line 1048
VALUE rb_io_buffer_free(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); if (data->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Buffer is locked!"); } io_buffer_free(data); return self; }
#get_string([offset, [length, [encoding]]]) ⇒ String
# File 'io_buffer.c', line 1788
static VALUE io_buffer_get_string(int argc, VALUE *argv, VALUE self) { if (argc > 3) rb_error_arity(argc, 0, 3); struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); const void *base; size_t size; io_buffer_get_bytes_for_reading(data, &base, &size); size_t offset = 0; size_t length = size; rb_encoding *encoding = rb_ascii8bit_encoding(); if (argc >= 1) { offset = NUM2SIZET(argv[0]); } if (argc >= 2 && !RB_NIL_P(argv[1])) { length = NUM2SIZET(argv[1]); } else { length = size - offset; } if (argc >= 3) { encoding = rb_find_encoding(argv[2]); } io_buffer_validate_range(data, offset, length); return rb_enc_str_new((const char*)base + offset, length, encoding); }
#get_value(type, offset) ⇒ Numeric
Read from buffer a value of type
at offset
. 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
Example:
string = [1.5].pack('f')
# => "\x00\x00\xC0?"
IO::Buffer.for(string).get_value(:f32, 0)
# => 1.5
# File 'io_buffer.c', line 1561
static VALUE io_buffer_get_value(VALUE self, VALUE type, VALUE _offset) { const void *base; size_t size; size_t offset = NUM2SIZET(_offset); rb_io_buffer_get_bytes_for_reading(self, &base, &size); return rb_io_buffer_get_value(base, size, RB_SYM2ID(type), offset); }
#hexdump
[ GitHub ]# File 'io_buffer.c', line 717
static VALUE rb_io_buffer_hexdump(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); VALUE result = Qnil; if (io_buffer_validate(data) && data->base) { result = rb_str_buf_new(data->size*3 + (data->size/16)*12 + 1); io_buffer_hexdump(result, 16, data->base, data->size, 1); } return result; }
#read(io, length)
::IO
operations:
# File 'io_buffer.c', line 1968
static VALUE io_buffer_read(VALUE self, VALUE io, VALUE length) { return rb_io_buffer_read(self, io, RB_NUM2SIZE(length)); }
#resize(new_size) ⇒ self
Resizes a buffer to a new_size
bytes, preserving its content. Depending on the old and new size, the memory area associated with the buffer might be either extended, or rellocated at different address with content being copied.
buffer = IO::Buffer.new(4)
buffer.set_string("test", 0)
buffer.resize(8) # resize to 8 bytes
# =>
# #<IO::Buffer 0x0000555f5d1a1630+8 INTERNAL>
# 0x00000000 74 65 73 74 00 00 00 00 test....
External buffer (created with .for), and locked buffer can not be resized.
# File 'io_buffer.c', line 1360
static VALUE io_buffer_resize(VALUE self, VALUE size) { rb_io_buffer_resize(self, NUM2SIZET(size)); return self; }
#set_string(*args)
[ GitHub ]# File 'io_buffer.c', line 1823
static VALUE io_buffer_set_string(int argc, VALUE *argv, VALUE self) { if (argc < 1 || argc > 4) rb_error_arity(argc, 1, 4); struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); 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(data, 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 1631
static VALUE io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value) { void *base; size_t size; size_t offset = NUM2SIZET(_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); }
#size ⇒ Integer
# File 'io_buffer.c', line 759
VALUE rb_io_buffer_size(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); return SIZET2NUM(data->size); }
#slice(offset, length) ⇒ Buffer
Produce another Buffer
which is a slice (or view into) the current one starting at offset
bytes and going for length
bytes.
The slicing happens without copying of memory, and the slice keeps being associated with the original buffer’s source (string, or file), if any.
Raises RuntimeError if the offset
length<tt> is out of the current buffer’s bounds.
string = 'test'
buffer = IO::Buffer.for(string)
slice = buffer.slice(1, 2)
# =>
# #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE>
# 0x00000000 65 73 es
# Put "o" into 0s position of the slice
slice.set_string('o', 0)
slice
# =>
# #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE>
# 0x00000000 6f 73 os
# it is also visible at position 1 of the original buffer
buffer
# =>
# #<IO::Buffer 0x00007fc3d31e2d80+4 SLICE>
# 0x00000000 74 6f 73 74 tost
# ...and original string
string
# => tost
# File 'io_buffer.c', line 1113
VALUE rb_io_buffer_slice(VALUE self, VALUE _offset, VALUE _length) { // TODO fail on negative offets/lengths. size_t offset = NUM2SIZET(_offset); size_t length = NUM2SIZET(_length); struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); io_buffer_validate_range(data, offset, length); VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self)); struct rb_io_buffer *slice = NULL; TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, slice); slice->base = (char*)data->base + offset; slice->size = length; // The source should be the root buffer: if (data->source != Qnil) slice->source = data->source; else slice->source = self; return instance; }
#to_s ⇒ String
# File 'io_buffer.c', line 632
VALUE rb_io_buffer_to_s(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); VALUE result = rb_str_new_cstr("#<"); rb_str_append(result, rb_class_name(CLASS_OF(self))); rb_str_catf(result, " %p+%"PRIdSIZE, data->base, data->size); if (data->base == NULL) { rb_str_cat2(result, " NULL"); } if (data->flags & RB_IO_BUFFER_EXTERNAL) { rb_str_cat2(result, " EXTERNAL"); } if (data->flags & RB_IO_BUFFER_INTERNAL) { rb_str_cat2(result, " INTERNAL"); } if (data->flags & RB_IO_BUFFER_MAPPED) { rb_str_cat2(result, " MAPPED"); } if (data->flags & RB_IO_BUFFER_LOCKED) { rb_str_cat2(result, " LOCKED"); } if (data->flags & RB_IO_BUFFER_READONLY) { rb_str_cat2(result, " READONLY"); } if (data->source != Qnil) { rb_str_cat2(result, " SLICE"); } if (!io_buffer_validate(data)) { rb_str_cat2(result, " INVALID"); } return rb_str_cat2(result, ">"); }
#transfer ⇒ Buffer
# File 'io_buffer.c', line 1235
VALUE rb_io_buffer_transfer(VALUE self) { struct rb_io_buffer *data = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); if (data->flags & RB_IO_BUFFER_LOCKED) { rb_raise(rb_eIOBufferLockedError, "Cannot transfer ownership of locked buffer!"); } VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self)); struct rb_io_buffer *transferred; TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, transferred); *transferred = *data; io_buffer_zero(data); return instance; }
#write(io, length)
[ GitHub ]# File 'io_buffer.c', line 2051
static VALUE io_buffer_write(VALUE self, VALUE io, VALUE length) { return rb_io_buffer_write(self, io, RB_NUM2SIZE(length)); }