Неэффективный ввод и вывод
Мое частое замечание к коду — неэффективный ввод-вывод. Примеры:
-
чтобы пройтись по строкам файла, человек читает его в память целиком и разбивает символами
\r\n
. Рано или поздно прилетает CSV на 5 гигабайт, и машине становится плохо. -
То же самое с джейсоном: есть стрим, но разработчик читает его в гигантскую строку, а потом парсит ее.
-
Нужно записать в файл 100 тысяч строк? Человек джойнит их разделителем, получает километровую строку и пишет в файл.
-
Различные кодирования — base64, gzip и другие — делаются также: данные читаются в память целиком, из них получается результат тоже в памяти.
-
При загрузке файла в S3 он целиком читается в байтовый массив, затем массив передается в запрос.
При этом разработчик обмазывает код вызовами gc в надежде, что это поможет.
Сколько подобных ошибок я исправил — не перечесть. В числе прочего был сервис, который падал от недостатка памяти, хотя ее было выделено запредельное количество. Оказалось, разработчик делал все из списка выше. Он получал огромные файлы, читал их в память, парсил, кодировал в JSON и gzip, используя строки и массивы. Когда код падал от OOM, он поднимал лимиты в облаке.
Это лишний раз подтверждает: сколько памяти ни дай, плохой код сожрет ее всю.
А решение простое — байтовые и символьные потоки. Ту же Джаву можно ругать за
многое, но в ней очень хорошие потоки (абстрактные классы Input-
и
OutputStream
, Reader
и Writer
). У них много наследников, каждый из которых
делает свою работу. Например, буферизирующий поток, который сглаживает
неравномерность сети и файлов. Потоки для сжатия, когда пишешь в него, а данные
сжимаются в полете. Потоки, связанные с файлами, сокетами или
устройствами. Потоки с подсчетом текущей строки и символа, потоки-пайпы (piped)
для “переливания” данных между тредами — всего этого навалом.
Легко найти сторонние потоки для подсчета MD5 и других хешей. Например, пишешь в
условный MD5OutputStream
, и хеш считается в полете. В конце вызываешь
.getHash, и готово.
Часто задача решается тем, что нужно построить стек потоков и скормить ему данные. Это труднее, чем прочитать файл в память и разбить на строки. Но не придется чинить в пятницу вечером.
Уделите время потокам ввода-вывода. Это прям очень полезная вещь.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter
Дядя Джон, 7th Jul 2025, link
Это что-то сродни советам в книгах начала 90-х по разработке: “Предусмотрите все возможные пути развития кода и добавьте код, подготавливающийся к данным ситуациям”.
Что-то вроде преждевременной оптимизации.
По-моему, хороший разработчик как раз и отличается тем, что он поймёт, где никогда не может прилететь 500 Мб JSON, и не тратит в этом месте время на поддержку файлов произвольного размера. И сразу понимает, где надо предусмотреть поддержку файлов произвольного размера.
А усложнять код и задерживать выполнение задач за счёт того, чтобы сразу в коде предусматривать все возможные ситуации:
С другой стороны, я работал, например, с индийскими разработчиками - им хоть кол на голове теши. Будешь говорить: “в этом месте в проде будет проходить 100500 Мб, сразу пиши без загрузки всего в память, иначе сломается”, в ответ скажет: “понял, босс”, и всё равно напишет свой корявый код, читающий всё в массив. После того, как ситуация повторилась десятки раз, подумал, что они в принципе далее определённого уровня необучаемые.
Мимокрокодил, 15th Jul 2025, link