Полный цикл в digital

Работа с бинарными данными в PHP

Для чтения и записи бинарных данных в php есть две полезные функции:

  1. pack() упаковка данных. Запаковывает данные в требуемом формате по нашей маске, на выходе отдаёт строку в бинарном формате
  2. 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) "Мария"
    }
  }
}
Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!
Эйч Маркетинг