класс 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'))
# =>
# #<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"
Класс является экспериментальным, и интерфейс может быть изменён, особенно это касается сопоставлений файлов, которые могут быть полностью удалены в будущем.
Константы
- BIG_ENDIAN
-
Относится к порядку байтов big endian, где самый старший байт хранится первым. Подробнее см.
get_value. - DEFAULT_SIZE
-
Размер буфера по умолчанию, обычно кратный (небольшой) размеру страницы
PAGE_SIZE. Можно явно указать, задав переменную окружения RUBY_IO_BUFFER_DEFAULT_SIZE. - EXTERNAL
-
Указывает, что память в буфере принадлежит кому-то другому. Подробнее см.
external?. - HOST_ENDIAN
-
Относится к порядку байтов машины-хоста. Подробнее см.
get_value. - INTERNAL
-
Указывает, что память в буфере принадлежит буферу. Подробнее см.
internal?. - LITTLE_ENDIAN
-
Относится к порядку байтов little endian, где младший байт хранится первым. Подробнее см.
get_value. - LOCKED
-
Указывает, что память в буфере заблокирована и не может быть изменена по размеру или освобождена. Подробнее см.
locked?иlocked. - MAPPED
-
Указывает, что память в буфере сопоставлена операционной системой. Подробнее см.
mapped?. - NETWORK_ENDIAN
-
Относится к порядку байтов сети, который совпадает с big 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
# => "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
Исходный код
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);
} Создаёт IO::Buffer для чтения из file путём сопоставления файла с памятью. file_io должен быть экземпляром File, открытым для чтения.
Можно указать необязательные size и offset сопоставления.
По умолчанию буфер будет неизменяемым (только для чтения); для создания изменяемого сопоставления необходимо открыть файл в режиме чтения-записи и явно передать аргумент flags без 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"
Обратите внимание, что некоторые операционные системы могут не иметь согласованности кэша между сопоставленными буферами и чтением из файла.
Исходный код
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;
} Создаёт новый заполненный нулями IO::Buffer размером size байт. По умолчанию буфер будет внутренним: непосредственно выделенный фрагмент памяти. Но если запрашиваемый size превышает размер страницы OS 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);
return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
} Эффективно копирует из исходного 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-1, argv+1, &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);
} Если буфер имеет размер 0: он создан с помощью ::new с размером 0 или с помощью ::for из пустой строки. (Обратите внимание, что пустые файлы не могут быть сопоставлены, поэтому буфер, созданный с помощью ::map, никогда не будет пустым.)
Исходный код
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 байта, little-endian -
:U16: беззнаковое целое число, 2 байта, big-endian -
:s16: знаковое целое число, 2 байта, little-endian -
:S16: знаковое целое число, 2 байта, big-endian -
:u32: беззнаковое целое число, 4 байта, little-endian -
:U32: беззнаковое целое число, 4 байта, big-endian -
:s32: знаковое целое число, 4 байта, little-endian -
:S32: знаковое целое число, 4 байта, big-endian -
:u64: беззнаковое целое число, 8 байт, little-endian -
:U64: беззнаковое целое число, 8 байт, big-endian -
:s64: знаковое целое число, 8 байт, little-endian -
:S64: знаковое целое число, 8 байт, big-endian -
:f32: число с плавающей точкой, 4 байта, little-endian -
:F32: число с плавающей точкой, 4 байта, big-endian -
:f64: число с плавающей точкой двойной точности, 8 байт, little-endian -
:F64: число с плавающей точкой двойной точности, 8 байт, big-endian
Тип буфера относится к типу бинарного буфера, хранящегося в буфере. Например, тип буфера :u32 представляет собой 32-битное беззнаковое целое число в формате little-endian.
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);
return io_buffer_copy_from(buffer, source_base, source_size, 0, NULL);
} Создаёт внутреннюю копию исходного буфера. Изменения в копии не повлияют на исходный буфер.
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;
} Проверяет буфер и сообщает полезную информацию о его внутреннем состоянии. Только ограниченная часть буфера будет отображаться в формате hexdump.
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);
} Если буфер был освобожден с помощью free, передан с помощью transfer или вообще не был выделен.
buffer = IO::Buffer.new(0) buffer.null? #=> true buffer = IO::Buffer.new(4) buffer.null? #=> false buffer.free buffer.null? #=> true
Исходный код
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);
return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
} Эффективно копирует данные из исходной 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);
} Возвращает размер буфера, который был явно задан (при создании с помощью ::new или при resize), или выведен из строки или файла при создании буфера.
Исходный код
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, если смещение или длина выходят за границы текущего буфера.
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–2024 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.