Массивы как хэш-таблицы и подводные камни строк
«Что такое массив в PHP?» — вопрос-обманка: правильный ответ не «список» и не «словарь».
Массив в PHP — это упорядоченная хэш-таблица: набор пар «ключ → значение», который дополнительно помнит порядок вставки. Одна структура играет роль и списка, и словаря, и очереди, и множества.
Вопрос 1: «Как устроен массив внутри?»
Что проверяет интервьюер
Понимаете ли вы, что отдельного типа «список» в PHP нет. Отсюда следствия: доступ по ключу — в среднем O(1), in_array() — O(n), а foreach всегда идёт в порядке вставки, а не в порядке возрастания ключей. Начиная с PHP 7 движок хранит «упакованные» (packed) массивы с последовательными целыми ключами компактнее, но логическая модель остаётся хэш-таблицей.
Правила ключей
Ключом может быть только int или string. Всё остальное приводится:
<?php
$a = [];
$a["1"] = 'строковый ключ "1"';
$a[1] = 'целый ключ 1';
$a[true] = 'булев ключ true';
$a["01"] = 'строковый ключ "01"';
var_dump($a);
Вывод:
array(2) {
[1]=>
string(24) "булев ключ true"
["01"]=>
string(32) "строковый ключ "01""
}
Три первые записи попали в один и тот же ключ 1: строка "1" — каноничная запись целого числа, поэтому она приводится к int; true тоже становится 1. А вот "01" каноничной записью не является — ключ остался строкой. Практический вывод: артикулы с ведущими нулями в качестве ключей безопасны, а «числовые» строки без нулей — нет.
Вопрос 2: «Чем array_merge() отличается от оператора +?»
<?php
$a = ['x' => 1, 'y' => 2];
$b = ['y' => 20, 'z' => 30];
print_r($a + $b);
print_r(array_merge($a, $b));
$nums = [3 => 'три', 1 => 'один', 2 => 'два'];
ksort($nums);
print_r($nums);
Вывод:
Array
(
[x] => 1
[y] => 2
[z] => 30
)
Array
(
[x] => 1
[y] => 20
[z] => 30
)
Array
(
[1] => один
[2] => два
[3] => три
)
Оператор + при конфликте ключей оставляет значение левого массива и никогда не перенумеровывает. array_merge() при конфликте строковых ключей берёт значение правого массива, а числовые ключи перенумеровывает с нуля — из-за этого склейка списков товаров иногда неожиданно теряет исходные id.
Третий пример напоминает: сортировка меняет порядок обхода. sort() сбрасывает ключи, ksort()/asort() — сохраняют. Это тоже частый уточняющий вопрос.
Вопрос 3: «Почему strlen("Привет") возвращает 12?»
Потому что строка в PHP — это последовательность байтов, а не символов, и о кодировке она ничего не знает. В UTF-8 кириллическая буква занимает два байта.
<?php
$s = "Привет, мир";
echo strlen($s), "\n";
echo mb_strlen($s), "\n";
echo mb_strtoupper($s), "\n";
echo mb_substr($s, 0, 6), "\n";
echo str_contains($s, "мир") ? "да\n" : "нет\n";
Вывод:
20 11 ПРИВЕТ, МИР Привет да
Правило простое: всё, что связано с длиной, обрезкой и регистром, для многобайтовых строк делается через расширение mbstring (mb_strlen, mb_substr, mb_strtoupper). Функции поиска подстроки (str_contains, strpos) работают с байтами и для UTF-8 корректны, потому что кодировка самосинхронизирующаяся — байты одного символа не встречаются внутри другого. А вот substr() без mb_ легко разрежет строку посреди символа и выдаст «кракозябры» в превью текста.
Типичные ошибки кандидатов
- Отвечают «массив — это список», не упоминая ключи и порядок вставки.
- Считают
in_array()быстрым поиском. Для больших наборов используйтеisset($map[$key]): массив с ключами вместо массива со значениями превращает O(n) в O(1). - Не знают, что
array_merge()перенумеровывает числовые ключи, и удивляются потере id. - Уверены, что
foreachидёт по возрастанию ключей. Он идёт по порядку вставки. - Забывают про
mb_-функции и режут UTF-8 посередине символа. - Путают
array_key_exists()иisset(): второй вернётfalse, если по существующему ключу лежитnull.
Как ответить кратко
«Массив в PHP — упорядоченная хэш-таблица с ключами int или string; каноничные числовые строки-ключи приводятся к int, порядок обхода — порядок вставки. Оператор
+сохраняет значения левого массива,array_mergeперезаписывает строковые ключи правыми и перенумеровывает числовые. Строки — это байты, поэтому для UTF-8 длину и обрезку делаю черезmb_-функции».