PHP и UTF-8: пятый этап
Итак, пятый этап. Точнее на этап, он, пожалуй, не тянет, но после этапов 4,5 и 4,6 мне хочется целых чисел.
В самом деле, если простое взятие символа по индексу в строке с фиксированным размером символа имеет сложность O(1), то Я написал небольшие тесты. Замерял три прохода по тексту объёмом 187,5КБ. Проход по строке с однобайтной кодировкой — 0,2 секунды, по строке Так вот, одна из вещей, которой мало кто пользуется — возможность создать в PHP свой итератор. У меня тут не руководство по итераторам, поэтому я ограничусь лишь кодом и примером использования. Основная идея — если мы двигаемся по строке последовательно, Использовать итератор достаточно просто: Так как в итерации сначала вызывается «current», а потом «next» мне показалось странным терять информацию о положении следующего символа, которую мы в «current» уже знаем, так что я передаю её в «next» через защищённое свойство. Список функций (из моего списка используемых функций), которые я перепишу с итератором (или скорость которых увеличится): trim, ltrim, rtrim, wordwrap, strcspn и strspn, а так же некоторые места в коде проекта.Итак, пятый этап
class UtfString implements Iterator
{
protected $str; // строка
protected $idx; // бинарный индекс строки
protected $pos; // UTF-8-индекс
protected $len; // бинарная длина
protected $next; // бинарный указатель на следующий
protected $idx_start; // бинарный индекс откуда стартуем
protected $pos_start; // UTF-8-индекс откуда стартуем
// проверка — байт не является началом символа
protected function isNotBegin($ch)
{
return (ord($ch) & 192) == 128;
}
public function __construct($str, $pos_start = 0)
{
$this->str = $str;
$this->pos = $this->pos_start = $pos_start;
$this->len = strlen($str);
$this->next = null;
if ($pos_start) {
for ($i = 0; $i<$pos_start; $i++) {
$this->idx_start++;
while ($this->isNotBegin($this->str[$this->idx_start])) {
$this->idx_start++;
}
}
$this->idx = $this->idx_start;
} else {
$this->idx = $this->idx_start = 0;
}
}
public function current()
{
$idx = $this->idx;
$out = $this->str[$idx++];
while ($this->isNotBegin($ch = $this->str[$idx])) {
$out .= $ch; // быстрее, чем массив!
$idx++;
}
$this->next = $idx;
return $out;
}
public function key()
{
return $this->pos;
}
public function next()
{
$this->pos++;
if ($this->next) {
$this->idx = $this->next;
$this->next = null;
} else {
for ($this->idx++; $this->isNotBegin($this->str[$this->idx]); $this->idx++);
}
}
public function rewind()
{
$this->idx = $this->idx_start;
$this->pos = $this->pos_start;
}
public function valid()
{
return $this->idx < $this->len;
}
public function __toString()
{
return $this->str;
}
}$str = str_repeat('Привет', 64000);
foreach (new UtfString($str) as $position => $char) {
// …
}
Комментарии 10
http://fabien.potencier.org/article/43/find-your-files
http://fabien.potencier.org/article/44/php …
Так
1. Итераторы в документации слабовато отражены по сравнению со всем остальным (напр. http://php.net/manual/en/class.directoryiterator.php%29. Т.е. чуть ли не рефлексией надо было пользоваться, чтобы посмотреть, что итератор умеет, например вот так:
http://www.phpro.org/tutorials/Introductio … rator.html
2. Я вот нашел новую работу и надеюсь скоро смогу писать на php 5.3. А так, когда пишешь, для шаредов, где ничего нет и все сурово, то надежды на скорый приход 5.3 нету.
Да не за что :)
Мы сейчас тоже заняты перетаскиванием нескольких проектов на PHP 5.3, кстати :)
Комментарий для Евгения Степанищева:
Попробовал использовать Ваш итератор. Нашел небольшую ошибку:
В строке
надо бы проверку на конец строки:
Это не ошибка. Мне подумалось, что notice+такая проверка быстрее, чем две проверки. Но может это и не так, надо проверить, когда время будет.
А почему бы тогда не подавить нотис?
или это тоже медленно, ночто-то я этого не читал нигде…
Это медленно, увы. Информация о медленности подавления через «собачку» есть