Работа с бинарными данными в PHP
Для чтения и записи бинарных данных в php есть две полезные функции:
pack()
упаковка данных. Запаковывает данные в требуемом формате по нашей маске, на выходе отдаёт строку в бинарном форматеunpack()
распаковка бинарных данных в массив. Выполняет обратное преобразование, на входе ожидает строку в бинарном формате, на выходе отдаёт массив с данными в привычном нам формате.
Записываем данные
Нам нужно записать в файл числа от 0
до 256
. Важно что бы файл легко читался программами на других языках и что бы по размеру он занимал минимум места.
Традиционным способом мы бы записывали примерно так:
$data = '';
for ($i = 0; $i < 256; $i++) {
$data .= $i;
}
file_put_contents(__DIR__ . '/ int.txt', $data);
Запаковывая каждый байт по отдельности, мы сделали бы так:
$data = '';
for ($i = 0; $i < 256; $i++) {
$data .= pack('c', $i);
}
file_put_contents(__DIR__ . '/int.bin', $data);
Теперь сравним размеры получившихся файлов и увидим, файл где мы упаковывали в байт, меньше почти в три раза чем файл где мы все значения записывали в виде строки:
-rw-rw-r-- 1 ukko ukko 256 июня 17 21:31 int.bin
-rw-rw-r-- 1 ukko ukko 914 июня 17 21:31 int.txt
Что бы понять как именно были сохранены файлы, давайте рассмотрим их в бинарном редакторе.
Файл сохранённый обычным способом:
00000000 30 0a 31 0a 32 0a 33 0a 34 0a 35 0a 36 0a 37 0a |0.1.2.3.4.5.6.7.|
00000010 38 0a 39 0a 31 30 0a 31 31 0a 31 32 0a 31 33 0a |8.9.10.11.12.13.|
00000020 31 34 0a 31 35 0a 31 36 0a 31 37 0a 31 38 0a 31 |14.15.16.17.18.1|
00000030 39 0a 32 30 0a 32 31 0a 32 32 0a 32 33 0a 32 34 |9.20.21.22.23.24|
00000040 0a 32 35 0a 32 36 0a 32 37 0a 32 38 0a 32 39 0a |.25.26.27.28.29.|
00000050 33 30 0a 33 31 0a 33 32 0a 33 33 0a 33 34 0a 33 |30.31.32.33.34.3|
00000060 35 0a 33 36 0a 33 37 0a 33 38 0a 33 39 0a 34 30 |5.36.37.38.39.40|
00000070 0a 34 31 0a 34 32 0a 34 33 0a 34 34 0a 34 35 0a |.41.42.43.44.45.|
00000080 34 36 0a 34 37 0a 34 38 0a 34 39 0a 35 30 0a 35 |46.47.48.49.50.5|
00000090 31 0a 35 32 0a 35 33 0a 35 34 0a 35 35 0a 35 36 |1.52.53.54.55.56|
000000a0 0a 35 37 0a 35 38 0a 35 39 0a 36 30 0a 36 31 0a |.57.58.59.60.61.|
000000b0 36 32 0a 36 33 0a 36 34 0a 36 35 0a 36 36 0a 36 |62.63.64.65.66.6|
000000c0 37 0a 36 38 0a 36 39 0a 37 30 0a 37 31 0a 37 32 |7.68.69.70.71.72|
000000d0 0a 37 33 0a 37 34 0a 37 35 0a 37 36 0a 37 37 0a |.73.74.75.76.77.|
000000e0 37 38 0a 37 39 0a 38 30 0a 38 31 0a 38 32 0a 38 |78.79.80.81.82.8|
000000f0 33 0a 38 34 0a 38 35 0a 38 36 0a 38 37 0a 38 38 |3.84.85.86.87.88|
00000100 0a 38 39 0a 39 30 0a 39 31 0a 39 32 0a 39 33 0a |.89.90.91.92.93.|
00000110 39 34 0a 39 35 0a 39 36 0a 39 37 0a 39 38 0a 39 |94.95.96.97.98.9|
00000120 39 0a 31 30 30 0a 31 30 31 0a 31 30 32 0a 31 30 |9.100.101.102.10|
00000130 33 0a 31 30 34 0a 31 30 35 0a 31 30 36 0a 31 30 |3.104.105.106.10|
00000140 37 0a 31 30 38 0a 31 30 39 0a 31 31 30 0a 31 31 |7.108.109.110.11|
00000150 31 0a 31 31 32 0a 31 31 33 0a 31 31 34 0a 31 31 |1.112.113.114.11|
00000160 35 0a 31 31 36 0a 31 31 37 0a 31 31 38 0a 31 31 |5.116.117.118.11|
00000170 39 0a 31 32 30 0a 31 32 31 0a 31 32 32 0a 31 32 |9.120.121.122.12|
00000180 33 0a 31 32 34 0a 31 32 35 0a 31 32 36 0a 31 32 |3.124.125.126.12|
00000190 37 0a 31 32 38 0a 31 32 39 0a 31 33 30 0a 31 33 |7.128.129.130.13|
000001a0 31 0a 31 33 32 0a 31 33 33 0a 31 33 34 0a 31 33 |1.132.133.134.13|
000001b0 35 0a 31 33 36 0a 31 33 37 0a 31 33 38 0a 31 33 |5.136.137.138.13|
000001c0 39 0a 31 34 30 0a 31 34 31 0a 31 34 32 0a 31 34 |9.140.141.142.14|
000001d0 33 0a 31 34 34 0a 31 34 35 0a 31 34 36 0a 31 34 |3.144.145.146.14|
000001e0 37 0a 31 34 38 0a 31 34 39 0a 31 35 30 0a 31 35 |7.148.149.150.15|
000001f0 31 0a 31 35 32 0a 31 35 33 0a 31 35 34 0a 31 35 |1.152.153.154.15|
00000200 35 0a 31 35 36 0a 31 35 37 0a 31 35 38 0a 31 35 |5.156.157.158.15|
00000210 39 0a 31 36 30 0a 31 36 31 0a 31 36 32 0a 31 36 |9.160.161.162.16|
00000220 33 0a 31 36 34 0a 31 36 35 0a 31 36 36 0a 31 36 |3.164.165.166.16|
00000230 37 0a 31 36 38 0a 31 36 39 0a 31 37 30 0a 31 37 |7.168.169.170.17|
00000240 31 0a 31 37 32 0a 31 37 33 0a 31 37 34 0a 31 37 |1.172.173.174.17|
00000250 35 0a 31 37 36 0a 31 37 37 0a 31 37 38 0a 31 37 |5.176.177.178.17|
00000260 39 0a 31 38 30 0a 31 38 31 0a 31 38 32 0a 31 38 |9.180.181.182.18|
00000270 33 0a 31 38 34 0a 31 38 35 0a 31 38 36 0a 31 38 |3.184.185.186.18|
00000280 37 0a 31 38 38 0a 31 38 39 0a 31 39 30 0a 31 39 |7.188.189.190.19|
00000290 31 0a 31 39 32 0a 31 39 33 0a 31 39 34 0a 31 39 |1.192.193.194.19|
000002a0 35 0a 31 39 36 0a 31 39 37 0a 31 39 38 0a 31 39 |5.196.197.198.19|
000002b0 39 0a 32 30 30 0a 32 30 31 0a 32 30 32 0a 32 30 |9.200.201.202.20|
000002c0 33 0a 32 30 34 0a 32 30 35 0a 32 30 36 0a 32 30 |3.204.205.206.20|
000002d0 37 0a 32 30 38 0a 32 30 39 0a 32 31 30 0a 32 31 |7.208.209.210.21|
000002e0 31 0a 32 31 32 0a 32 31 33 0a 32 31 34 0a 32 31 |1.212.213.214.21|
000002f0 35 0a 32 31 36 0a 32 31 37 0a 32 31 38 0a 32 31 |5.216.217.218.21|
00000300 39 0a 32 32 30 0a 32 32 31 0a 32 32 32 0a 32 32 |9.220.221.222.22|
00000310 33 0a 32 32 34 0a 32 32 35 0a 32 32 36 0a 32 32 |3.224.225.226.22|
00000320 37 0a 32 32 38 0a 32 32 39 0a 32 33 30 0a 32 33 |7.228.229.230.23|
00000330 31 0a 32 33 32 0a 32 33 33 0a 32 33 34 0a 32 33 |1.232.233.234.23|
00000340 35 0a 32 33 36 0a 32 33 37 0a 32 33 38 0a 32 33 |5.236.237.238.23|
00000350 39 0a 32 34 30 0a 32 34 31 0a 32 34 32 0a 32 34 |9.240.241.242.24|
00000360 33 0a 32 34 34 0a 32 34 35 0a 32 34 36 0a 32 34 |3.244.245.246.24|
00000370 37 0a 32 34 38 0a 32 34 39 0a 32 35 30 0a 32 35 |7.248.249.250.25|
00000380 31 0a 32 35 32 0a 32 35 33 0a 32 35 34 0a 32 35 |1.252.253.254.25|
00000390 35 0a |5.|
00000392
Файл сохранённый через функцию char()
:
00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................|
00000020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !”#$%&‘()*+,-./|
00000030 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?|
00000040 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |@ABCDEFGHIJKLMNO|
00000050 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f |PQRSTUVWXYZ[\]^_|
00000060 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f |`abcdefghijklmno|
00000070 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f |pqrstuvwxyz{|}~.|
00000080 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f |................|
00000090 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f |................|
000000a0 a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af |................|
000000b0 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf |................|
000000c0 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf |................|
000000d0 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df |................|
000000e0 e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef |................|
000000f0 f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff |................|
00000100
Причина такого разбухания файла, это символ 0A
, используемый в качестве разделителя значений (0x0A = 10 = символ новой строки), плюс каждый из знаков в переменной сохраняется в своём байте. Например, когда требуется сохранить число 248, записываются 3 байта 32 34 38.
Считываем данные
Теперь при попытке прочитать данные из файлов могут возникнуть проблемы при чтении запакованных данных. Что бы их обойти считываем данные так:
$data = file_get_contents(__DIR__ . '/int.bin');
$array = unpack('c*', $data);
Подробнее о функциях pack() и unpack()
Как вы уже могли заметить, функция pack принимает обязательных два аргумента: форматную строку и значение, или несколько значений которые необходимо запаковать.
Для того что бы оценить гибкость запаковывания данных давайте попробуем решить такую задачу: нам необходимо реализовать обмен данными между программами написанными на разных языках.
Запаковываем массив содержащий числа и строки. Обратите внимание, если для чисел мы знаем размер, хотя бы предполагаем что число обязано уместится в тип char
, от 0
до 256
. То строка может содержать любое количество символов. Для простоты будем считать что строка тоже должна уместиться в 256
символов, тогда мы можем количество символов строки записать в переменную типа char
.
Вот так выглядит сохранённый файл:
00000000 01 0e d0 92 d0 b0 d1 81 d0 b8 d0 bb d0 b8 d0 b9 |................|
00000010 02 08 d0 9f d1 91 d1 82 d1 80 03 0a d0 9c d0 b0 |................|
00000020 d1 80 d0 b8 d1 8f |......|
00000026
Попробуем теперь прочитать файл:
$handle = fopen(__DIR__ . '/person.bin', "rb");
$persons = array();
while (!feof($handle)) {
$data = fread($handle, 2);
if (strlen($data) == 2) {
$int = unpack('cid/ccount', $data);
$name = unpack('a*', fread($handle, $int['count']));
$persons[] = array($int['id'], $name);
}
}
fclose($handle);
var_dump($persons);
На выходе мы получим такой массив:
array(3) {
[0] =>
array(2) {
[0] =>
int(5)
[1] =>
array(1) {
[1] =>
string(14) "Василий"
}
}
[1] =>
array(2) {
[0] =>
int(9)
[1] =>
array(1) {
[1] =>
string(8) "Пётр"
}
}
[2] =>
array(2) {
[0] =>
int(12)
[1] =>
array(1) {
[1] =>
string(10) "Мария"
}
}
}