class IO::Buffer
IO::Buffer — это эффективный буфер ввода-вывода без копирования. Типичные варианты использования:
-
Создайте пустой буфер с помощью
::new, заполните его данными с помощьюcopyилиset_value,set_string, получите данные из буфера с помощьюget_stringили запишите их напрямую в файл с помощьюwrite. -
Создайте буфер, связанный со строкой, с помощью
::for. Его можно использовать как для чтения с помощьюget_stringилиget_value, так и для записи (запись также изменит исходную строку). -
Создайте буфер, связанный с файлом, с помощью
::map. Затем его можно использовать для чтения из базового файла и записи в него. -
Создайте строку фиксированного размера с помощью
::string, а затем выполните в неё чтение с помощьюreadили измените её с помощьюset_value.
Взаимодействие со строковой и файловой памятью выполняется с помощью эффективных низкоуровневых механизмов C, таких как ‘memcpy`.
Этот класс предназначен для реализации более высокоуровневых механизмов, таких как Fiber::Scheduler#io_read и Fiber::Scheduler#io_write, а также для разбора двоичных протоколов.
Примеры использования
Пустой буфер:
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"
Буфер из строки:
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
Буфер из файла:
File.write('test.txt', 'test data')
# => 9
buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
# =>
# #<IO::Buffer 0x00007f3f0768c000+9 EXTERNAL MAPPED FILE SHARED READONLY>
# ...
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"
Класс является экспериментальным, и его интерфейс может измениться; это особенно касается отображений файлов, которые в будущем могут быть полностью удалены.
Константы
- BIG_ENDIAN
-
Обозначает порядок байтов от старшего к младшему, при котором сначала хранится наиболее значимый байт. Подробнее см. в разделе
get_value. - DEFAULT_SIZE
-
Размер буфера по умолчанию, обычно равный небольшому кратному
PAGE_SIZE. Его можно явно задать с помощью переменной окружения RUBY_IO_BUFFER_DEFAULT_SIZE. - EXTERNAL
-
Указывает, что память буфера принадлежит кому-то другому. Подробнее см. в разделе
external?. - HOST_ENDIAN
-
Обозначает порядок байтов на хост-компьютере. Подробнее см. в разделе
get_value. - INTERNAL
-
Указывает, что память буфера принадлежит самому буферу. Подробнее см. в разделе
internal?. - LITTLE_ENDIAN
-
Обозначает порядок байтов от младшего к старшему, при котором сначала хранится наименее значимый байт. Подробнее см. в разделе
get_value. - LOCKED
-
Указывает, что память буфера заблокирована и её нельзя изменить в размере или освободить. Подробнее см. в разделах
locked?иlocked. - MAPPED
-
Указывает, что память буфера отображена операционной системой. Подробнее см. в разделе
mapped?. - NETWORK_ENDIAN
-
Обозначает сетевой порядок байтов, совпадающий с порядком от старшего к младшему. Подробнее см. в разделе
get_value. - PAGE_SIZE
-
Размер страницы операционной системы. Используется для эффективного выделения памяти с выравниванием по границам страниц.
- PRIVATE
-
Указывает, что память буфера отображена в приватном режиме, поэтому изменения не будут перенесены в базовый файл. Подробнее см. в разделе
private?. - READONLY
-
Указывает, что память буфера доступна только для чтения и попытки изменить её завершатся ошибкой. Подробнее см. в разделе
readonly?. - SHARED
-
Указывает, что память буфера также отображена таким образом, чтобы ею могли совместно пользоваться другие процессы. Подробнее см. в разделе
shared?.
Публичные методы класса
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);
}
} Создаёт буфер IO::Buffer без копирования на основе памяти заданной строки. Если блок не передан, эффективным способом создаётся замороженная внутренняя копия строки, которая используется в качестве источника буфера. Если блок передан, буфер напрямую связывается с внутренним буфером строки, и обновление буфера приводит к обновлению строки.
Пока для буфера не будет вызван free — явно или сборщиком мусора, — исходная строка будет заблокирована, и её нельзя будет изменить.
Если строка заморожена, будет создан буфер только для чтения, который нельзя изменить. Если строка является общей, при использовании формы с блоком может произойти копирование при записи.
string = 'test'
buffer = IO::Buffer.for(string)
buffer.external? #=> true
buffer.get_string(0, 1)
# => "t"
string
# => "test"
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
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];
rb_off_t file_size = rb_file_size(io);
// Compiler can confirm that we handled file_size <= 0 case:
if (UNLIKELY(file_size <= 0)) {
rb_raise(rb_eArgError, "Invalid negative or zero file size!");
}
// Here, we assume that file_size is positive:
else if (UNLIKELY((uintmax_t)file_size > SIZE_MAX)) {
rb_raise(rb_eArgError, "File larger than address space!");
}
size_t size;
if (argc >= 2 && !RB_NIL_P(argv[1])) {
size = io_buffer_extract_size(argv[1]);
if (UNLIKELY(size == 0)) {
rb_raise(rb_eArgError, "Size can't be zero!");
}
if (UNLIKELY(size > (size_t)file_size)) {
rb_raise(rb_eArgError, "Size can't be larger than file size!");
}
}
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]);
if (UNLIKELY(offset < 0)) {
rb_raise(rb_eArgError, "Offset can't be negative!");
}
if (UNLIKELY(offset >= file_size)) {
rb_raise(rb_eArgError, "Offset too large!");
}
if (RB_NIL_P(argv[1])) {
// Decrease size if it's set from the actual file size:
size = (size_t)(file_size - offset);
}
else if (UNLIKELY((size_t)(file_size - offset) < size)) {
rb_raise(rb_eArgError, "Offset too large!");
}
}
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);
} Создаёт IO::Buffer для чтения из file посредством отображения файла в память. file должен быть экземпляром File, открытым для чтения или для чтения и записи.
Можно задать необязательные size и offset отображения. Попытка отобразить пустой файл или указать size равный 0 приведёт к ошибке. Допустимые значения для offset зависят от системы.
По умолчанию буфер доступен для записи, и предполагается, что файл доступен для записи. Кроме того, буфер является общим, поэтому одно и то же отображение могут использовать несколько процессов.
Чтобы создать буфер только для чтения, можно передать IO::Buffer::READONLY в качестве аргумента flags; это позволяет работать с файлами, открытыми только для чтения. Указание IO::Buffer::PRIVATE в flags создаёт приватное отображение, которое не влияет на другие процессы или базовый файл. Это также позволяет обновлять буфер, созданный на основе файла, доступного только для чтения.
File.write('test.txt', 'test')
buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
# => #<IO::Buffer 0x00000001014a0000+4 EXTERNAL MAPPED FILE SHARED READONLY>
buffer.readonly? # => true
buffer.get_string
# => "test"
buffer.set_string('b', 0)
# 'IO::Buffer#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"
Обратите внимание, что в некоторых операционных системах согласованность кэша между отображёнными буферами и чтением файлов может отсутствовать.
VALUE
rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self)
{
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;
} Создаёт новый заполненный нулями IO::Buffer размером size байт. По умолчанию буфер является внутренним: для него напрямую выделяется участок памяти. Однако если запрошенный size превышает зависящий от операционной системы IO::Buffer::PAGE_SIZE, память для буфера будет выделена с помощью механизма виртуальной памяти (анонимный mmap в Unix, VirtualAlloc в Windows). Такое поведение можно принудительно включить, передав IO::Buffer::MAPPED в качестве второго параметра.
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
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)));
}
} Возвращает размер заданного типа буфера или типов буферов в байтах.
IO::Buffer.size_of(:u32) # => 4 IO::Buffer.size_of([:u32, :u32]) # => 8
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;
} Создаёт строку заданной длины и передаёт блоку экземпляр IO::Buffer без копирования, использующий эту строку в качестве источника. Предполагается, что блок будет записывать данные в буфер; после этого строка будет возвращена.
IO::Buffer.string(4) do |buffer|
buffer.set_string("Ruby")
end
# => "Ruby"
Открытые методы экземпляра
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;
} Создаёт новый буфер того же размера, что и исходный, применяя к исходному побитовую операцию AND с маской и при необходимости повторяя маску.
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.
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));
} Буферы сравниваются по размеру и точному содержимому памяти, на которую они ссылаются, с использованием memcmp.
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;
} Создаёт новый буфер того же размера, что и исходный, применяя к исходному побитовую операцию XOR с маской и при необходимости повторяя маску.
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
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;
} Создаёт новый буфер того же размера, что и исходный, применяя к исходному побитовую операцию OR с маской и при необходимости повторяя маску.
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
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;
} Создаёт новый буфер того же размера, что и исходный, применяя к исходному побитовую операцию NOT.
~IO::Buffer.for("1234567890")
# =>
# #<IO::Buffer 0x000055a5ac42f120+10 INTERNAL>
# 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf ..........
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;
} Изменяет исходный буфер на месте, применяя к нему побитовую операцию AND с маской и при необходимости повторяя маску.
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.
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;
} Заполняет буфер значением value, начиная с offset и заполняя length байт.
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 ....
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);
VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
RB_GC_GUARD(source);
return result;
} Эффективно копирует данные из исходного IO::Buffer в буфер, начиная с offset и используя memmove. Для копирования экземпляров String см. 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 можно использовать для помещения буфера в связанные с ним строки:
string = "data: "
# => "data: "
buffer = IO::Buffer.for(string) do |buffer|
buffer.copy(IO::Buffer.for("test"), 5)
end
# => 4
string
# => "data:test"
Попытка копирования в буфер только для чтения завершится неудачей:
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)
Сведения о создании изменяемых отображений файлов см. в ::map; следующий пример выполнится успешно:
buffer = IO::Buffer.map(File.open('test.txt', 'r+'))
buffer.copy(IO::Buffer.for("boom"), 0)
# => 4
File.read('test.txt')
# => "boom"
Попытка скопировать буфер за его границы завершится неудачей:
buffer = IO::Buffer.new(2)
buffer.copy(IO::Buffer.for('test'), 0)
# in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError)
Копировать данные между перекрывающимися областями памяти безопасно. В этом случае данные копируются так, как если бы они сначала были скопированы из исходного буфера во временный буфер, а затем из временного буфера в целевой.
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
static VALUE
io_buffer_each(int argc, VALUE *argv, VALUE self)
{
RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
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);
for (size_t i = 0; i < count; i++) {
size_t current_offset = offset;
VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
rb_yield_values(2, SIZET2NUM(current_offset), value);
}
return self;
} Перебирает буфер, передавая каждый value из buffer_type, начиная с offset.
Если задано count, будут переданы только значения count.
IO::Buffer.for("Hello World").each(:U8, 2, 2) do |offset, value|
puts "#{offset}: #{value}"
end
# 2: 108
# 3: 108
static VALUE
io_buffer_each_byte(int argc, VALUE *argv, VALUE self)
{
RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
const void *base;
size_t size;
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
size_t offset, count;
io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc, argv, &offset, &count);
for (size_t i = 0; i < count; i++) {
unsigned char *value = (unsigned char *)base + i + offset;
rb_yield(RB_INT2FIX(*value));
}
return self;
} Перебирает буфер, передавая каждый байт, начиная с offset.
Если задано count, будут переданы только count байт.
IO::Buffer.for("Hello World").each_byte(2, 2) do |offset, byte|
puts "#{offset}: #{byte}"
end
# 2: 108
# 3: 108
static VALUE
rb_io_buffer_empty_p(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return RBOOL(buffer->size == 0);
} static VALUE
rb_io_buffer_external_p(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return RBOOL(buffer->flags & RB_IO_BUFFER_EXTERNAL);
} Буфер является внешним, если он ссылается на память, выделенную или отображённую не самим буфером.
Буфер, созданный с помощью ::for, содержит внешнюю ссылку на память строки.
Размер внешнего буфера нельзя изменить.
VALUE
rb_io_buffer_free(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
if (buffer->flags & RB_IO_BUFFER_LOCKED) {
rb_raise(rb_eIOBufferLockedError, "Buffer is locked!");
}
io_buffer_free(buffer);
return self;
} Если буфер ссылается на память, освобождает её, возвращая операционной системе.
-
для отображённого буфера (например, файла): отменяет отображение.
-
для буфера, созданного с нуля: освобождает память.
-
для буфера, созданного из строки: отменяет связь со строкой.
После освобождения буфера выполнять над ним дальнейшие операции нельзя.
Освобождённый буфер можно изменить по размеру, чтобы выделить память заново.
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
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);
} Считывает часть буфера или весь буфер в строку в указанной encoding. Если кодировка не указана, используется Encoding::BINARY.
buffer = IO::Buffer.for('test')
buffer.get_string
# => "test"
buffer.get_string(2)
# => "st"
buffer.get_string(2, 1)
# => "s"
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);
} Считывает из буфера значение типа type по смещению offset. buffer_type должно быть одним из следующих символов:
-
:U8: беззнаковое целое число, 1 байт -
:S8: знаковое целое число, 1 байт -
:u16: беззнаковое целое число, 2 байта, порядок байтов от младшего к старшему -
:U16: беззнаковое целое число, 2 байта, порядок байтов от старшего к младшему -
:s16: знаковое целое число, 2 байта, порядок байтов от младшего к старшему -
:S16: знаковое целое число, 2 байта, порядок байтов от старшего к младшему -
:u32: беззнаковое целое число, 4 байта, порядок байтов от младшего к старшему -
:U32: беззнаковое целое число, 4 байта, порядок байтов от старшего к младшему -
:s32: знаковое целое число, 4 байта, порядок байтов от младшего к старшему -
:S32: знаковое целое число, 4 байта, порядок байтов от старшего к младшему -
:u64: беззнаковое целое число, 8 байт, порядок байтов от младшего к старшему -
:U64: беззнаковое целое число, 8 байт, порядок байтов от старшего к младшему -
:s64: знаковое целое число, 8 байт, порядок байтов от младшего к старшему -
:S64: знаковое целое число, 8 байт, порядок байтов от старшего к младшему -
:u128: беззнаковое целое число, 16 байт, порядок байтов от младшего к старшему -
:U128: беззнаковое целое число, 16 байт, порядок байтов от старшего к младшему -
:s128: знаковое целое число, 16 байт, порядок байтов от младшего к старшему -
:S128: знаковое целое число, 16 байт, порядок байтов от старшего к младшему -
:f32: число с плавающей запятой, 4 байта, порядок байтов от младшего к старшему -
:F32: число с плавающей запятой, 4 байта, порядок байтов от старшего к младшему -
:f64: число двойной точности, 8 байт, порядок байтов от младшего к старшему -
:F64: число двойной точности, 8 байт, порядок байтов от старшего к младшему
Тип буфера указывает конкретно на тип двоичных данных, хранящихся в буфере. Например, тип буфера :u32 — это 32-разрядное беззнаковое целое число в формате с порядком байтов от младшего к старшему.
string = [1.5].pack('f')
# => "\x00\x00\xC0?"
IO::Buffer.for(string).get_value(:f32, 0)
# => 1.5
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;
} Подобно get_value, но поддерживает несколько типов буфера и возвращает массив значений.
string = [1.5, 2.5].pack('ff')
IO::Buffer.for(string).get_values([:f32, :f32], 0)
# => [1.5, 2.5]
static VALUE
rb_io_buffer_hexdump(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);
size_t width = RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH;
if (argc >= 3) {
width = io_buffer_extract_width(argv[2], 1);
}
// This may raise an exception if the offset/length is invalid:
io_buffer_validate_range(buffer, offset, length);
VALUE result = Qnil;
if (io_buffer_validate(buffer) && buffer->base) {
result = rb_str_buf_new(io_buffer_hexdump_output_size(width, length, 1));
io_buffer_hexdump(result, width, buffer->base, offset+length, offset, 1);
}
return result;
} Возвращает удобное для чтения строковое представление буфера. Точный формат может измениться.
buffer = IO::Buffer.for("Hello World")
puts buffer.hexdump
# 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
Поскольку буферы обычно довольно большие, при необходимости можно ограничить вывод, указав смещение и длину:
puts buffer.hexdump(6, 5) # 0x00000006 57 6f 72 6c 64 World
static VALUE
rb_io_buffer_initialize_copy(VALUE self, VALUE source)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
const void *source_base;
size_t source_size;
rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size);
io_buffer_initialize(self, buffer, NULL, source_size, io_flags_for_size(source_size), Qnil);
VALUE result = io_buffer_copy_from(buffer, source_base, source_size, 0, NULL);
RB_GC_GUARD(source);
return result;
} Создаёт внутреннюю копию исходного буфера. Изменения копии не повлияют на исходный буфер.
source = IO::Buffer.for("Hello World")
# =>
# #<IO::Buffer 0x00007fd598466830+11 EXTERNAL READONLY SLICE>
# 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
buffer = source.dup
# =>
# #<IO::Buffer 0x0000558cbec03320+11 INTERNAL>
# 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
VALUE
rb_io_buffer_inspect(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
VALUE result = rb_io_buffer_to_s(self);
if (io_buffer_validate(buffer)) {
// Limit the maximum size generated by inspect:
size_t size = buffer->size;
int clamped = 0;
if (size > RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE) {
size = RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE;
clamped = 1;
}
io_buffer_hexdump(result, RB_IO_BUFFER_INSPECT_HEXDUMP_WIDTH, buffer->base, size, 0, 0);
if (clamped) {
rb_str_catf(result, "\n(and %" PRIuSIZE " more bytes not printed)", buffer->size - size);
}
}
return result;
} Исследует буфер и выводит полезные сведения о его внутреннем состоянии. В формате шестнадцатеричного дампа будет показана только ограниченная часть буфера.
buffer = IO::Buffer.for("Hello World")
puts buffer.inspect
# #<IO::Buffer 0x000000010198ccd8+11 EXTERNAL READONLY SLICE>
# 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
static VALUE
rb_io_buffer_internal_p(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return RBOOL(buffer->flags & RB_IO_BUFFER_INTERNAL);
} Буфер является внутренним, если он ссылается на память, выделенную самим буфером.
Внутренний буфер не связан с внешней памятью (например, строкой) или отображением файла.
Внутренние буферы создаются с помощью ::new и используются по умолчанию, если запрошенный размер меньше IO::Buffer::PAGE_SIZE и при создании не запрашивалось отображение в памяти.
Размер внутренних буферов можно изменять; такая операция обычно, но не всегда, делает все срезы недействительными.
VALUE
rb_io_buffer_locked(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
if (buffer->flags & RB_IO_BUFFER_LOCKED) {
rb_raise(rb_eIOBufferLockedError, "Buffer already locked!");
}
buffer->flags |= RB_IO_BUFFER_LOCKED;
VALUE result = rb_yield(self);
buffer->flags &= ~RB_IO_BUFFER_LOCKED;
return result;
} Позволяет обрабатывать буфер в монопольном режиме для обеспечения безопасности при параллельном выполнении. Пока выполняется блок, буфер считается заблокированным, и никакой другой код не может получить блокировку. Кроме того, заблокированный буфер нельзя изменить с помощью resize или free.
Блокировку захватывают следующие операции: resize, 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("test", 0)
end
end
static VALUE
rb_io_buffer_locked_p(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return RBOOL(buffer->flags & RB_IO_BUFFER_LOCKED);
} Буфер является заблокированным, если выполняется блок locked. Заблокированный буфер нельзя изменить по размеру или освободить, а повторно захватить блокировку для него нельзя.
Блокировка не является потокобезопасной, но используется как семантический механизм, гарантирующий, что буфер не переместится во время системного вызова.
buffer.locked do buffer.write(io) # theoretical system call interface end
static VALUE
rb_io_buffer_mapped_p(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return RBOOL(buffer->flags & RB_IO_BUFFER_MAPPED);
} Буфер является отображённым, если он ссылается на память, отображённую буфером.
Отображённые буферы бывают анонимными, если они созданы с помощью ::new с флагом IO::Buffer::MAPPED или если их размер не меньше IO::Buffer::PAGE_SIZE, либо подкреплёнными файлом, если они созданы с помощью ::map.
Размер отображённых буферов обычно можно изменить; такая операция обычно, но не всегда, делает все срезы недействительными.
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;
} Изменяет исходный буфер на месте, применяя к нему побитовую операцию NOT.
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 ..........
static VALUE
rb_io_buffer_null_p(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return RBOOL(buffer->base == NULL);
} 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;
} Изменяет исходный буфер на месте, применяя к нему побитовую операцию OR с маской и при необходимости повторяя маску.
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
static VALUE
io_buffer_pread(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 2, 4);
VALUE io = argv[0];
rb_off_t from = NUM2OFFT(argv[1]);
size_t length, offset;
io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset);
return rb_io_buffer_pread(self, io, from, length, offset);
} Считывает не менее length байт из io, начиная с указанной позиции from, в буфер, начиная с offset. При возникновении ошибки возвращает -errno.
Если length не задано или равно nil, по умолчанию используется размер буфера за вычетом смещения, то есть весь буфер.
Если length равно нулю, будет выполнена ровно одна операция pread.
Если offset не задано, по умолчанию используется ноль, то есть начало буфера.
IO::Buffer.for('test') do |buffer|
p buffer
# =>
# <IO::Buffer 0x00007fca40087c38+4 SLICE>
# 0x00000000 74 65 73 74 test
# take 2 bytes from the beginning of urandom,
# put them in buffer starting from position 2
buffer.pread(File.open('/dev/urandom', 'rb'), 0, 2, 2)
p buffer
# =>
# <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
# 0x00000000 05 35 73 74 te.5
end
static VALUE
rb_io_buffer_private_p(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return RBOOL(buffer->flags & RB_IO_BUFFER_PRIVATE);
} Буфер является приватным, если изменения в нём не отражаются в соответствующем отображении файла.
# Create a test file:
File.write('test.txt', 'test')
# Create a private mapping from the given file. Note that the file here
# is opened in read-only mode, but it doesn't matter due to the private
# mapping:
buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::PRIVATE)
# => #<IO::Buffer 0x00007fce63f11000+4 MAPPED PRIVATE>
# Write to the buffer (invoking CoW of the underlying file buffer):
buffer.set_string('b', 0)
# => 1
# The file itself is not modified:
File.read('test.txt')
# => "test"
static VALUE
io_buffer_pwrite(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 2, 4);
VALUE io = argv[0];
rb_off_t from = NUM2OFFT(argv[1]);
size_t length, offset;
io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset);
return rb_io_buffer_pwrite(self, io, from, length, offset);
} Записывает не менее length байт из буфера, начиная с offset, в io, начиная с указанной позиции from. При возникновении ошибки возвращает -errno.
Если length не задано или равно nil, по умолчанию используется размер буфера за вычетом смещения, то есть весь буфер.
Если length равно нулю, будет выполнена ровно одна операция pwrite.
Если offset не задано, по умолчанию используется ноль, то есть начало буфера.
Если позиция from находится за концом файла, промежуток будет заполнен нулевыми байтами (со значением 0).
out = File.open('output.txt', File::RDWR) # open for read/write, no truncation
IO::Buffer.for('1234567').pwrite(out, 2, 3, 1)
В результате 234 (3 байта, начиная с позиции 1) записываются в output.txt, начиная с позиции 2 в файле.
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);
} Считывает не менее length байт из io в буфер, начиная с offset. При возникновении ошибки возвращает -errno.
Если length не задано или равно nil, по умолчанию используется размер буфера за вычетом смещения, то есть весь буфер.
Если length равно нулю, будет выполнена ровно одна операция read.
Если offset не задано, по умолчанию используется ноль, то есть начало буфера.
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
static VALUE
io_buffer_readonly_p(VALUE self)
{
return RBOOL(rb_io_buffer_readonly_p(self));
} Буфер является доступным только для чтения, если его нельзя изменить с помощью set_value, set_string, copy и подобных методов.
Замороженные строки и файлы, открытые только для чтения, создают буферы только для чтения.
static VALUE
io_buffer_resize(VALUE self, VALUE size)
{
rb_io_buffer_resize(self, io_buffer_extract_size(size));
return self;
} Изменяет размер буфера до new_size байт, сохраняя его содержимое. В зависимости от исходного и нового размера область памяти, связанная с буфером, может быть расширена или перемещена по другому адресу с копированием содержимого.
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....
Размер внешнего буфера (созданного с помощью ::for) и заблокированного буфера изменить нельзя.
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);
VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
RB_GC_GUARD(string);
return result;
} Эффективно копирует данные из исходной String в буфер, начиная с offset и используя 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.....
Примеры использования записи в буфер для изменения связанных с ним строк и файлов см. также в разделе copy.
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);
} Записывает в буфер значение value типа type по смещению offset. type должно быть одним из символов, описанных в 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......
Обратите внимание: если type — целое число, а value — Float, выполняется неявное усечение:
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
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);
} Записывает в буфер values значений типа buffer_types, начиная с позиции offset. buffer_types должен быть массивом символов, описанных в get_value. values должен быть массивом записываемых значений.
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 ........
Буфер является общим, если он ссылается на память, которая может совместно использоваться другими процессами (и поэтому может изменяться без локальных изменений).
# Create a test file:
File.write('test.txt', 'test')
# Create a shared mapping from the given file, the file must be opened in
# read-write mode unless we also specify IO::Buffer::READONLY:
buffer = IO::Buffer.map(File.open('test.txt', 'r+'), nil, 0)
# => #<IO::Buffer 0x00007f1bffd5e000+4 EXTERNAL MAPPED SHARED>
# Write to the buffer, which will modify the mapped file:
buffer.set_string('b', 0)
# => 1
# The file itself is modified:
File.read('test.txt')
# => "best"
VALUE
rb_io_buffer_size(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return SIZET2NUM(buffer->size);
} static VALUE
io_buffer_slice(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 2);
size_t offset, length;
struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
return rb_io_buffer_slice(buffer, self, offset, length);
} Создаёт другой IO::Buffer, представляющий собой срез (или представление) текущего буфера, начинающийся со смещения offset байт и имеющий длину length байт.
Срез создаётся без копирования памяти и, если у исходного буфера есть источник (строка или файл), остаётся связанным с ним.
Если смещение не указано, оно будет равно нулю. Если смещение отрицательное, будет вызвано исключение ArgumentError.
Если длина не указана, срез будет иметь длину, равную длине исходного буфера за вычетом заданного смещения. Если длина отрицательная, будет вызвано исключение ArgumentError.
Вызывает RuntimeError, если offset+length выходит за границы текущего буфера.
string = 'test'
buffer = IO::Buffer.for(string).dup
slice = buffer.slice
# =>
# #<IO::Buffer 0x0000000108338e68+4 SLICE>
# 0x00000000 74 65 73 74 test
buffer.slice(2)
# =>
# #<IO::Buffer 0x0000000108338e6a+2 SLICE>
# 0x00000000 73 74 st
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 INTERNAL>
# 0x00000000 74 6f 73 74 tost
VALUE
rb_io_buffer_to_s(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
VALUE result = rb_str_new_cstr("#<");
rb_str_append(result, rb_class_name(CLASS_OF(self)));
rb_str_catf(result, " %p+%"PRIdSIZE, buffer->base, buffer->size);
if (buffer->base == NULL) {
rb_str_cat2(result, " NULL");
}
if (buffer->flags & RB_IO_BUFFER_EXTERNAL) {
rb_str_cat2(result, " EXTERNAL");
}
if (buffer->flags & RB_IO_BUFFER_INTERNAL) {
rb_str_cat2(result, " INTERNAL");
}
if (buffer->flags & RB_IO_BUFFER_MAPPED) {
rb_str_cat2(result, " MAPPED");
}
if (buffer->flags & RB_IO_BUFFER_FILE) {
rb_str_cat2(result, " FILE");
}
if (buffer->flags & RB_IO_BUFFER_SHARED) {
rb_str_cat2(result, " SHARED");
}
if (buffer->flags & RB_IO_BUFFER_LOCKED) {
rb_str_cat2(result, " LOCKED");
}
if (buffer->flags & RB_IO_BUFFER_PRIVATE) {
rb_str_cat2(result, " PRIVATE");
}
if (buffer->flags & RB_IO_BUFFER_READONLY) {
rb_str_cat2(result, " READONLY");
}
if (buffer->source != Qnil) {
rb_str_cat2(result, " SLICE");
}
if (!io_buffer_validate(buffer)) {
rb_str_cat2(result, " INVALID");
}
return rb_str_cat2(result, ">");
} Краткое представление буфера. Оно содержит адрес, размер и символьные флаги. Этот формат может измениться.
puts IO::Buffer.new(4) # uses to_s internally # #<IO::Buffer 0x000055769f41b1a0+4 INTERNAL>
VALUE
rb_io_buffer_transfer(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
if (buffer->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 = *buffer;
io_buffer_zero(buffer);
return instance;
} Передаёт владение выделенной памятью новому буферу, в результате чего текущий буфер становится неинициализированным.
buffer = IO::Buffer.new('test')
other = buffer.transfer
other
# =>
# #<IO::Buffer 0x00007f136a15f7b0+4 SLICE>
# 0x00000000 74 65 73 74 test
buffer
# =>
# #<IO::Buffer 0x0000000000000000+0 NULL>
buffer.null?
# => true
static VALUE
rb_io_buffer_valid_p(VALUE self)
{
struct rb_io_buffer *buffer = NULL;
TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);
return RBOOL(io_buffer_validate(buffer));
} Возвращает, доступен ли буфер.
Буфер становится недействительным, если он является срезом другого буфера (или строки), который был освобождён или перевыделен по другому адресу.
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;
} Возвращает массив значений buffer_type, начиная с offset.
Если задан count, будут возвращены только count значений.
IO::Buffer.for("Hello World").values(:U8, 2, 2)
# => [108, 108]
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);
} Записывает не менее length байт из буфера, начиная с offset, в io. Если возникает ошибка, возвращает -errno.
Если length не задан или равен nil, по умолчанию используется размер буфера за вычетом смещения, то есть весь буфер.
Если length равен нулю, будет выполнена ровно одна операция write.
Если offset не задан, по умолчанию он равен нулю, то есть указывает на начало буфера.
out = File.open('output.txt', 'wb')
IO::Buffer.for('1234567').write(out, 3)
В результате 123 записывается в output.txt
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;
} Изменяет исходный буфер на месте, применяя к нему побитовую операцию XOR с маской и повторяя её при необходимости.
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
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.