Привет. В PHP довольно часто приходится работать со строками и массивами и почти во всех случаях требуется узнать их длину (length). Вполне типичная ситуация и для нее есть встроенные функции в PHP. Но есть некоторые нюансы, к примеру то, что одна из функций, которая показывает длину строки — srtlen считает не количество символов в тексте, а количество байт, который занимает каждый символ. Если латинский символ занимает 1 байт, то на кириллице он займет 2 байта. Об этом я же упоминал в статье по теме: как обрезать текст по количеству слов и символов. Но сейчас постараемся рассмотреть некоторые примеры более детально.
Узнать длину строки в PHP
Первая функция, которая будет вычислять длину строки в PHP, будет strlen.
$str = «Hello World»; echo strlen($str); // 11 символов вместе с пробелом
А если мы напишем примерно то же самое, но на русском, то получим такой вариант:
$str = «Привет Мир»; echo strlen($str); // 19 символов вместе с пробелом
В этом случае, как я уже говорил ранее, каждый символ займет 2 байта + 1 байт — это пробел. В итоге мы получим не совсем то, что ожидали. Поэтому в случае с кириллицей, чтобы определить длину строки, следует использовать другие функции. Первая — mb_strlen
$str = «Привет Мир»; echo mb_strlen($str); // 10 символов вместе с пробелом
В этом случае подсчет символов в строки будет одинаковым как на английском, так и на русском языках. Даже если символ занимает несколько байт, то будет посчитан, как один. Так же есть еще одна функция, чтобы узнать длину строки в символах — iconv_strlen
$str = «Привет Мир»; echo iconv_strlen($str); // 10 символов вместе с пробелом
iconv_strlen учитывает кодировку строки, ее можно указать вторым параметром. Если она не указана, то кодировка будет внутренней. То есть самого файла.
echo iconv_strlen($str, «UTF-8»);
Если возникла необходимость проверить длину строки без пробелов, то потребуется дополнительная функция str_replace
Узнать длину массива в PHP
функция, которая позволяет узнать длину массива в PHP — count.
То же самое будет с массивом, где есть ключи и значения.
strlen()
Подсчет количества байт в строке
mb_stren()
Подсчет символов в строке
iconv_strlen()
Подсчет символов строки с учетом кодировки
count()
Подсчет элементов массива
На этом можно завершить. Теперь вы можете самостоятельно узнать длину строки в PHP и определить длину массива. А если возникнут вопросы, задавайте их в комментариях.
Функция strlen() возвратит количество байт, а не число символов в строке.
Смотрите также
User Contributed Notes 8 notes
I want to share something seriously important for newbies or beginners of PHP who plays with strings of UTF8 encoded characters or the languages like: Arabic, Persian, Pashto, Dari, Chinese (simplified), Chinese (traditional), Japanese, Vietnamese, Urdu, Macedonian, Lithuanian, and etc. As the manual says: «strlen() returns the number of bytes rather than the number of characters in a string.», so if you want to get the number of characters in a string of UTF8 so use mb_strlen() instead of strlen().
// the Arabic (Hello) string below is: 59 bytes and 32 characters $utf8 = «السلام علیکم ورحمة الله وبرکاته!» ;
The easiest way to determine the character count of a UTF8 string is to pass the text through utf8_decode() first:
We just ran into what we thought was a bug but turned out to be a documented difference in behavior between PHP 5.2 & 5.3. Take the following code example:
?>
This is because in 5.2 strlen will automatically cast anything passed to it as a string, and casting an array to a string yields the string «Array». In 5.3, this changed, as noted in the following point in the backward incompatible changes in 5.3 (http://www.php.net/manual/en/migration53.incompatible.php):
«The newer internal parameter parsing API has been applied across all the extensions bundled with PHP 5.3.x. This parameter parsing API causes functions to return NULL when passed incompatible parameters. There are some exceptions to this rule, such as the get_class() function, which will continue to return FALSE on error.»
So, in PHP 5.3, strlen($attributes) returns NULL, while in PHP 5.2, strlen($attributes) returns the integer 5. This likely affects other functions, so if you are getting different behaviors or new bugs suddenly, check if you have upgraded to 5.3 (which we did recently), and then check for some warnings in your logs like this:
strlen() expects parameter 1 to be string, array given in /var/www/sis/lib/functions/advanced_search_lib.php on line 1028
If so, then you are likely experiencing this changed behavior.
When checking for length to make sure a value will fit in a database field, be mindful of using the right function.
There are three possible situations:
1. Most likely case: the database column is UTF-8 with a length defined in unicode code points (e.g. mysql varchar(200) for a utf-8 database).
Find the character set used, and pass it explicitly to the length function.
There’s a LOT of misinformation here, which I want to correct! Many people have warned against using strlen(), because it is «super slow». Well, that was probably true in old versions of PHP. But as of PHP7 that’s definitely no longer true. It’s now SUPER fast!
I created a 20,00,000 byte string (
20 megabytes), and iterated ONE HUNDRED MILLION TIMES in a loop. Every loop iteration did a new strlen() on that very, very long string.
The result: 100 million strlen() calls on a 20 megabyte string only took a total of 488 milliseconds. And the strlen() calls didn’t get slower/faster even if I made the string smaller or bigger. The strlen() was pretty much a constant-time, super-fast operation
So either PHP7 stores the length of every string as a field that it can simply always look up without having to count characters. Or it caches the result of strlen() until the string contents actually change. Either way, you should now never, EVER worry about strlen() performance again. As of PHP7, it is super fast!
Here is the complete benchmark code if you want to reproduce it on your machine:
Сколько памяти потребляют объекты в PHP и стоит ли использовать 64-битную версию?
На этот пост меня вдохновило исследование потребления памяти для моего текущего большого проекта на ZendFramework. Как обычно, по результатам исследования, я был шокирован нашей программистской самонадеянностью, которая нередко присутствует, когда мы пишем что-либо большое на PHP. Да и, наверное, не только на PHP.
Но обо всём по порядку.
Как будем измерять
Для начала определимся, как мы будем измерять «вес». Вот шаблон:
Такой шаблон подходит для измерения новой выделяемой памяти, то есть памяти под переменные. А вот измерить, сколько едят определения, то есть описания функций и классов, таким подходом нельзя, так как они заносятся в память до начала выполнения скрипта. Для того чтобы измерить определения, воспользуемся следующим шаблоном:
Сколько весят «объекты»
Можно заметить, что сборка Gentoo потребляет на 10-20% меньше памяти, а в редких случаях экономия доходит до 50%. Видимо, размер внутренних структур зависит от оптимизаций для процессора. Для экперимента я пересобирал php с разными вариантами CFLAGS, но он от этого не стал потреблять больше. Видимо разница проявляется не из-за пересборки самого PHP, а из пересборки стандартных Сишных библиотек.
Поговорим о тестах, связанных с ZendFramework. Загрузка определений классов Zend`а в память отжирает существенные ресурсы, тогда как ссылки на объекты уже потребляют не так много. Controller2 нужен, чтобы проверить, сколько будет отжирать аналогичный контроллер, если все промежуточные классы уже в памяти. Model2 создана для этих же целей. В потенциале использование PHP акселератора сэкономит нам память на всех определениях, ибо они уже будут храниться в памяти. Давайте проверим это утверждение.
Тестирование акселераторов
Для тестирования был взят APC, и тесты запускались через web с помощью скрипта:
Я также производил некоторые тесты с xcache и заметил 2 отличия от APC. Во-первых: xcache проигрывает (почти всегда) на 10-15% по экономии памяти. А во-вторых: xcache сразу отдаёт файлы из кеша, тогда как APC — только после повторного обращения. Хоть и бесполезное, но преимущество.
Как мы видим, акселератор хоть и экономит нам память для определений, но не полностью, поскольку PHP, видимо, переносит какие-то куски из кеша в текущую сессию. Теперь перейдем от абстрактных тестов к вполне реальным.
Вообще этот вопрос возник вот из такого вот положения дел:
Есть форма, она передает параметры скрипту PHP, который загружает на сервер картинки (JPEG). Для этого используется следующий код:
при этом все проходит нормально, т.е. картинки успешно загружаются, но далее, по ходу скрипта, на основе одной из картинок должно создаваться превью фото, для чего использую следующий код:
и вот тут, когда читаем в память JPEG-файл, возникает сложность (в которой мне и надо разобраться) для картинок с размером более
390 kb при вызове функции: $source = imagecreatefromjpeg($FFL) or die(‘Cannot load original JPEG’); //(см. в коде выше. 6 строка)
скрипт «вылетает» с ошибкой:
Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 2560 bytes) in /скрипт.php on line 70
памяти занято: 71880
Я не могу понять, почему не хватает памяти. Как определить, сколько надо этой функции при наличии картинок того или иного размера. Как вообще можно решить это?
Не хочтелось бы просить хостера повышать лимит, если есть иные решения по оптимизации данной процедуры.
Заранее благодаоен за любые подсказки и ссылки по существу вопроса. Спасибо.
к примеру $source = imagecreatefromjpeg($FFL) or die(‘Cannot load original JPEG’); echo sizeof($source);
я наверно не совсем точно выразился. Мне надо было знать, сколько занимает ресурсов значение переменной в памяти.
К примеру при считывании файла размером 292 kb до считывания скрипт занимает:
72784 байта а сразу после вызова функции imagecreatefromjpeg уже 10897632 байт, и это при том, что файл всего 292 kb!
Приблизительно оценить размер распакованной картинки можно, сконвертировав ее в 24-битный BMP. Размер полученного файла будет примерно равняться объему памяти, занимаемой картинкой.
Всего записей: 545 | Зарегистр. 23-10-2003 | Отправлено:19:10 06-04-2007
Delphi6
BANNED
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору dacuan Может вы скажете зачем вам это нужно и мы сможем найти обходное решение, как мне кажется у РНР нет такой возможности, даже за памяти переменных следит сам движок РНР, сколько я не пытался самолично подтирать за своими действиями переменные и ими используемую память присваивав им nil результаты были мизерными и не оправдывали сложность кода.
Всего записей: 2631 | Зарегистр. 06-07-2004 | Отправлено:19:27 06-04-2007
Kokoc
Advanced Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору Delphi6 Тогда уж надо было через unset удалять.
Всего записей: 793 | Зарегистр. 06-06-2002 | Отправлено:14:08 07-04-2007
Может вы скажете зачем вам это нужно и мы сможем найти обходное решение
Вообще задача состоит в том, чтобы закачивать картинки на сервер, при этом есть два НО. 1. Картинки посылаются программой (некая бд в офлайн режиме. затем сброс данных на сервер) в кодировке base64 (от этого никуда не деться), и потому, когда мне надо сохранить картинку иначе как пользуясь функциями библиотеки GD2, я не имею иного варианта (может есть какой?) 2. Помимо этого, для некоторых картинок мне необходимо делать превью-фото, т.е. опять же через ф-ции GD2
Сегодня объекты используются очень активно, хотя это трудно было предположить после выхода PHP 5 в 2005 году. Тогда я ещё мало что знал о возможностях этого языка. Пятую версию PHP сравнивали с предыдущей, четвёртой, и главным преимуществом нового релиза стала новая, очень мощная объектная модель. И сегодня, десять лет спустя, около 90% всего PHP-кода содержит объекты, не изменившиеся со времени PHP 5.0. Это убедительно говорит о том, какую роль сыграло внедрение объектной модели, неоднократно улучшавшейся на протяжении последующих лет. В этом посте я хотел бы рассказать о том, как всё устроено «под капотом». Чтобы люди понимали суть процессов — почему сделано так, а не иначе — и лучше, полнее использовали возможности языка. Также я затрону тему использования памяти объектами, в том числе в сравнении с эквивалентными массивами (когда это возможно).
Я буду рассказывать на примере версии PHP 5.4, и описываемые мной вещи справедливы для 5.5 и 5.6, потому что устройство объектной модели там почти не претерпело изменений. Обратите внимание, что в версии 5.3 всё не так хорошо с точки зрения возможностей и общей производительности.
В PHP 7, который пока ещё активно разрабатывается, объектная модель переработана не сильно, были внесены лишь незначительные изменения. Просто потому что всё и так хорошо работает, а лучшее — враг хорошего. Были добавлены возможности, не затрагивающие ядро, но здесь об этом речи не пойдёт.
В качестве демонстрации начну с синтетических бенчмарков:
Здесь объявляется простой класс с тремя атрибутами, а затем в цикле создаётся 1000 объектов этого класса. Обратите внимание, как в этом примере используется память: при создании объекта класса Foo и переменной для его хранения выделяется 262 байт динамической памяти PHP.
Давайте заменим объект на эквивалентный массив:
Вот ещё один пример:
Теперь давайте разберём, как всё это устроено в недрах PHP, подкрепив теорией практические наблюдения.
Всё начинается с классов
Внутри PHP класс представляется с помощью структуры zend_class_entry:
Важно знать ещё об одном моменте, связанном с zend_class_entry — о PHP-комментариях. Они также известны как аннотации. Это строковые переменные (в языке С — буферы char* ), которые тоже надо разместить в памяти. Для языка С, не использующего Unicode, в отличие от PHP, правило очень простое: один символ = один байт. Чем больше у вас в классе аннотаций, тем больше памяти будет использовано после парсинга.
У zend_class_entry поле doc_comment содержит аннотации класса. У методов и атрибутов тоже есть такое поле.
Пользовательские и внутренние классы
Пользовательский класс — это класс, заданный с помощью PHP, а внутренний класс задаётся либо благодаря внедрению исходного кода в сам PHP, либо с помощью расширения. Самое большое различие между этими двумя видами классов заключается в том, что пользовательские классы оперируют памятью, выделяемой по запросу, а внутренние — «постоянной» памятью.
Это означает, что когда PHP заканчивает обработку текущего HTTP-запроса, он убирает из памяти и уничтожает все пользовательские классы, готовясь к обработке следующего запроса. Этот подход известен под названием «архитектура без разделения ресурсов» (the share nothing architecture). Так было заложено в PHP с самого начала, и изменять это пока не планируется.
Итак, каждый раз при формировании запроса и парсинге классов происходит выделение памяти для них. После использования класса уничтожается всё, что с ним связано. Так что обязательно используйте все объявленные классы, в противном случае будет теряться память. Применяйте автозагрузчики, они задерживают парсинг/объявление во время выполнения, когда PHP нужно задействовать класс. Несмотря на замедление выполнения, автозагрузчик позволяет грамотно использовать память, поскольку он не будет запущен, пока действительно не возникнет потребность в классе.
С внутренними классами всё иначе. Они размещаются в памяти постоянно, вне зависимости от того, использовали их или нет. То есть они уничтожаются только тогда, когда прекращается работа самого PHP — после завершения обработки всех запросов (подразумеваются веб SAPI, например, PHP-FPM). Поэтому внутренние классы более эффективны, чем пользовательские (в конце запроса уничтожаются только статические атрибуты, больше ничего).
Обратите внимание, что даже при кешировании опкодов, как OPCache, создание и уничтожение класса осуществляется при каждом запросе, как и в случае с пользовательскими классами. OPCache просто ускоряет оба этих процесса.
Как вы заметили, если активировать много PHP-расширений, каждое из которых объявляет много классов, но при этом использовать лишь небольшое их количество, то теряется память. Помните, что PHP-расширения объявляют классы во время запуска PHP, даже если в последующих запросах эти классы использоваться не будут. Поэтому не рекомендуется держать расширения активными, если они не применяются в данный момент, иначе вы будете терять память. Особенно если эти расширения объявляют много классов — хотя они могут забить память и чем-нибудь другим.
Классы, интерфейсы или трейты — без разницы
Не слишком хорошо, что здесь используется 912 байт всего лишь для декларирования интерфейса BarException.
Привязка класса
Многие разработчики не вспоминают о привязке класса, пока не начинают задавать вопросом, а как же всё устроено на самом деле. Привязку класса можно описать как «процесс, в ходе которого сам класс и все связанные с ним данные подготавливаются для полноценного использования разработчиком». Этот процесс очень прост и не требует много ресурсов, если речь идёт о каком-то одном классе, не дополняющем другой, не использующем трейты и не внедряющим интерфейс. Процесс привязки для таких классов полностью протекает во время компиляции, а в ходе выполнения ресурсы на это уже не тратятся. Обратите внимание, что речь шла привязке класса, задекларированного пользователем. Для внутренних классов тот же самый процесс выполняется, когда классы зарегистрированы ядром или расширениями PHP, как раз перед запуском пользовательских скриптов — и делается это лишь один раз за всё время работы PHP.
Всё сильно усложняется, если речь заходит о внедрении интерфейсов или наследовании классов. Тогда в ходе привязки класса у родительских и дочерних объектов (будь то классы или интерфейсы) копируется абсолютно все.
Тут добавить нечего, простой случай.
Для каждой из таблиц функций (методов) используется do_inherit_method :
Что касается наследования, то здесь, в принципе, всё то же самое, что и при внедрении интерфейса. Только вовлечено ещё больше «участников». Но хочу отметить, что если PHP уже знает о классе, то привязка осуществляется во время компилирования, а если не знает — то во время выполнения. Так что лучше объявлять так:
Кстати, рутинная процедура привязки класса может привести к очень странному поведению:
В первом варианте привязка класса В отложена на время выполнения, потому что когда компилятор доходит до объявления этого класса, он ещё ничего не знает о классе А. Когда начинается выполнение, то привязка класса А происходит без вопросов, потому что он уже скомпилирован, будучи одиночным классом. Во втором случае всё иначе. Привязка класса С отложена на время выполнения, потому что компилятор ещё ничего не знает о В, пытаясь скомпилировать его. Но когда во время выполнения начинается привязка класса С, то он ищет В, который не существует, поскольку не скомпилирован по причине того, что В является дополнением. Вылетает сообщение “Class B doesn’t exist”.
Объекты
Итак, теперь мы знаем, что:
Теперь поговорим об объектах. В первой главе показано, что создание «классического» объекта («классического» пользовательского класса) потребовало очень мало памяти, около 200 байт. Всё дело в классе. Дальнейшая компиляция класса тоже потребляет память, но это к лучшему, потому что для создания одиночного объекта требуется меньше байт. По сути, объект представляет собой крохотный набор из крохотных структур.
Управление методами объекта
Вы могли заметить интересную вещь, посмотрите на первые строки:
Во время компиляции функции/метода происходит немедленный перевод в нижний регистр. Вышеприведённая функция BAR() превращается в bar() компилятором при добавлении метода таблице классов и функций.
В приведённом примере первый вызов статический: компилятор вычислил key для строковой “bar”, а когда приходит время вызова метода, ему нужно делать меньше работы. Второй вызов уже динамический, компилятор ничего не знает о “$b”, не может вычислить key для вызова метода. Затем, во время выполнения, нам придётся перевести строковую в нижний регистр и вычислить её хеш ( zend_hash_func() ), что не лучшим образом сказывается на производительности.
Управление атрибутами объекта
Вот что происходит:
Так что, создавая объект, мы «всего лишь» создаём структуру zend_object весом 32 байта:
Далее движок создаёт вектор признаков нашего объекта:
Вероятно, у вас возникли два вопроса:
Пока вы не пишете в объект, его потребление памяти не меняется. После записи он занимает уже больше места (пока не будет уничтожен), поскольку содержит все записанные в него атрибуты.
Объекты, ведущие себя как ссылки благодаря хранилищу объектов
Объекты не являются ссылками. Это демонстрируется на маленьком скрипте:
Все сейчас скажут, что «в PHP 5 объекты являются ссылками», об этом упоминает даже официальный мануал. Технически это совершенно неверно. Тем не менее, объекты могут вести себя так же, как и ссылки. Например, когда вы передаёте переменную, являющуюся объектом функции, эта функция может модифицировать тот же объект.
object(MyClass)#1 (0) < >/* #1 is the object handle (number), it is unique */
Когда мы вызываем метод, движок изменяет область видимости:
Вот так можно получать доступ к приватным членам объектов, не принадлежащим вам, но являющимся дочерними по отношению к вашей текущей области видимости:
Эта особенность стала причиной большого количества баг-репортов от разработчиков. Но так устроена объектная модель в PHP — на самом деле, мы задаём область видимости на основе не объекта, а класса. В случае с нашим классом “Foo”, вы можете работать с любым приватным Foo любого другого Foo, как показано выше.
О деструкторе
Деструкторы опасны, не полагайтесь на них, поскольку PHP их не вызывает даже в случае фатальной ошибки:
А что насчёт порядка вызова деструкторов в том случае, если они всё-таки вызываются? Ответ хорошо виден в коде:
Здесь продемонстрированы три стадии вызова деструктора:
PHP не вызывает деструкторы в случае возникновения какой-либо фатальной ошибки. Дело в том, что в этом случае Zend работает нестабильно, а вызов деструкторов приводит к выполнению пользовательского кода, который может получить доступ к ошибочным указателям и, в результате, к падению PHP. Уж лучше сохранять стабильность системы — поэтому вызов деструкторов и блокируется. Возможно, в PHP 7 что-то и поменяется.
Суммируя вышесказанное: не доверяйте деструкторам критически важный код, например, управление механизмом блокировки (lock mechanism), поскольку PHP может и не вызвать деструктор или вызвать его в неконтролируемой последовательности. Если всё-таки важный код обрабатывается деструктором, то как минимум самостоятельно контролируйте жизненный цикл объектов. PHP вызовет деструктор, когда refcount вашего объекта упадёт до нуля, а это значит, что объект больше не используется и его можно безопасно уничтожить.