Ввод-вывод двоичных данных
Двоичные данные обычно хранятся в файлах без разделителей записей в блоках фиксированной длины. После открытия двоичного файла функцией open() нужно установить режим обработки двоичных данных с помощью функции binmode().
open(my $fh, ">$file") or die("Ошибка открытия: $!");
binmode($fh);
Запись двоичных данных или данных фиксированной длины может выполняться с помощью функции print($fh $record). Также имеется функция небуферизованного вывода syswrite(), которой при вызове указываются три аргумента: файловыйманипулятор, скалярная переменная с выводимыми данными и размер записываемого блока данных. Эта функция возвращает число фактически записанных байт (в случае ошибки syswrite возвращает undef ), что можно использовать для проверки успешности записи. Это делается так:
syswrite($fh, $record, length($record)) == length($record)
or die("Ошибка записи: $!");
Преобразование данных к двоичному виду производит функция pack(), которая упаковывает в скалярную переменную списокзначений в соответствии с указанным шаблоном. В шаблоне каждое преобразуемое поле обозначается с помощью латинской буквы. Полный перечень шаблонов преобразования для функций pack() и unpack() приводится в таблице 9.2. За каждым символом в шаблоне может следовать десятичное число, которое рассматривается как ширина преобразуемого поля. Поля в шаблоне могут разделяться пробелами для удобства чтения.
Таблица 9.2. Шаблоны упаковки и распаковки данных
Шаблон |
Мнемоника |
Описание преобразования |
a |
Arbitrary |
произвольная последовательность байтов, дополненная нулевым байтом \0 |
A |
ASCII |
строка символов ASCII, дополненная пробелами |
b / B |
Bit string |
строка битов с возрастающим / убывающим порядком битов |
c / C |
Character |
однобайтовые символы со знаком / без знака |
f / d |
Float / Double |
число с плавающей точкой одинарной / двойной точности |
F |
Float |
число с плавающей точкой одинарной точности во внутреннем представлении (NV) |
D |
long Double |
длинное число с плавающей точкой двойной точности |
h / H |
Hex string |
шестнадцатеричная строка с младшим / старшим полубайтом (nybble) в начале |
i / I |
Integer |
целое (>=32 бита) число со знаком / без знака |
j / J |
|
целое во внутреннем представлении со знаком (IV) / без знака (UV) |
l / L |
Long |
длинное (32 бита) целое со знаком / без знака |
n / N |
Network |
беззнаковое короткое (16 битов) / длинное (32 бита) целое с сетевым порядком байтов (big endian) |
p / P |
Pointer |
указатель на строку, оканчивающуюся \0 / фиксированной длины |
q / Q |
Quad |
сверхдлинное (64 бита) целое число со знаком / без знака |
s / S |
Short |
короткое (16 битов) целое со знаком / без знака |
u |
uuencoded |
строка, кодированная по алгоритму uuencode |
U |
Unicode |
строка символов Unicode |
v / V |
VAX |
беззнаковое короткое (16 битов) / длинное (32 бита) целое с VAX-порядком байтов (little endian) |
w |
|
целое, сжатое в соответствии с кодировкой BER |
x |
|
вставка \0 (pack) / пропуск байта по направлению вперед (unpack) |
X |
|
пропуск байта по направлению назад |
Z |
ASCIIZ |
строка ASCIIZ (оканчивающаяся \0), дополненная \0 |
@ |
|
заполнение \0 до указанной позиции |
Например, целочисленное значение, возвращаемое функцией time(), и дробное значение, возвращаемое функцией rand(), можно упаковать в переменную $record с помощью шаблона 'l1 d1', который означает: "одно длинное целое число (long) и одно число с плавающей точкой двойной точности (double)".
$record = pack 'l1 d1', time(), rand(); #
Вот еще несколько несложных примеров использования разных шаблонов для функции pack():
$bin = pack('a5', 'Yes'); # в $bin будет: 'Yes\0\0'
$bin = pack('A5', 'Yes'); # в $bin будет: 'Yes '
$bin = pack('a4', 'abcd','x','y','z'); # в $bin: 'abcd'
$bin = pack('aaaa', 'abcd','x','y','z'); # в $bin: 'axyz'
$bin = pack('C2', 65,66,67); # в $bin будет: 'AB'
$bin = pack('U2', 0x263A, 0x263B); # в $bin будет: '??'
$bin = pack ('cxxc', 65,66); # в $bin будет: 'A\0\0B'
Для преобразования данных из двоичного вида применяется функция unpack(), которая распаковывает из скалярной переменной в список или массив значения двоичных данных в соответствии с указанным шаблоном.
@list_of_values = unpack($template, $binary_record);
Кроме того, с помощью функции unpack() можно из строки извлекать подстроки фиксированной длины. Например, так можно извлечь из записи файла поля определенной длины в переменные:
# Поля данных в записи файла:
# c 1 по 7 байт - номер телефона
# с 8 длиной 30 - фамилия, имя, отчество абонента
# с 38 длиной 25 - адрес
# 1234567Бендер Остап Ибрагимович РСФСР, Черноморск
($phone, $name, $address)= unpack('A7A30A25', $record);
Чтобы пропустить ненужные поля, достаточно указать в шаблоне пропуск определенного количества байтов. Например, так можно не извлекать поле с телефонным номером:
($name, $address)= unpack('x7A30A25', $record);
Подробное описание шаблонов и работы функций pack() и unpack() можно найти в стандартной документации с помощью все той же утилиты чтения документации:
Для чтения двоичных данных или текстовых данных фиксированной длины применяется функция read(), которой в качествеаргументов передаются файловый манипулятор, скалярная переменная для вводимых данных и размер считываемого блока данных. Вот так, например, выглядит типичный цикл чтения двоичных данных:
until(eof($fh)) { # читать до достижения конца файла
# считать очередной блок данных и проверить его длину
read($fh, $record, $record_size) == $record_size
or die('Неправильная длина данных');
# распаковать данные по шаблону из $record в @data
@data = unpack($template, $record);
# обработать введенные данные...
}
Ввод-вывод с произвольным доступом
При работе с данными фиксированной длины обычной практикой является считывание или запись данных в произвольном местефайла, например, при изменении только что считанного блока данных. Для этого нужно указать позицию чтения или записи. Это делается с помощью функции seek(), которой передается три аргумента: файловый манипулятор, смещение в байтах иуказатель позиции отсчета. Позиция отсчета задается числами: 0 - от начала файла, 1 - от текущей позиции, 2 - от конца файла. Например:
seek($handle, 64, 0); # переместиться на 64 байта от начала
seek($handle, 25, 1); # сместиться на 25 байт вперед
seek($handle, -10, 2); # установиться на 10 байт до конца
seek($handle, 0, 0); # установить позицию в начало файла
С помощью функции tell(), которая возвращает смещение относительно начала файла, можно узнать текущую позицию чтения-записи и использовать ее для дальнейших перемещений по файлу.
$pos = tell($handle); # запомнить текущую позицию в $pos
seek($handle, $pos-5, 0); # сместиться на 5 байт назад
В следующем примере увеличивается поле счетчика длиной 2 байта, расположенное в файле с позиции $new_pos:
seek($file, $new_pos, 0); # установить позицию чтения
$pos = tell($file); # и запомнить ее в переменной
read($file, $number, 2); # прочитать 2-байтовое поле
seek($file, $pos, 0); # установить в исходную позицию
syswrite($file, ++$number, 2); # записать новое значение
Операции ввода-вывода с произвольным доступом часто используются для работы с базами данных, основанных на записях фиксированной длины, например, с файлами в формате DBF. Они позволяют организовать быструю выборку данных и записьизмененных данных на прежнее место.
|