#Симптомы и условия возникновения
Кириллица и другие символы иногда превращаются в �, когда граница потока разрезает многобайтовый символ UTF-8, а код самостоятельно вызывает chunk.toString() для каждого отдельного Buffer. Для начала проверьте используемую версию среды и точный текст ошибки на копии проекта, не меняя рабочий сервер.
#Причины и как их отличить
| Возможная причина | Как узнать | Что делать |
|---|---|---|
| Порционная декодировкаВ этой ситуации наблюдается конкретный диагностический признак. Сопоставьте его с журналом ошибки и результатом соответствующей проверки, прежде чем выбирать исправление. | Искажение зависит от размера частей, в файле байты верные | Использовать потоковый декодер с сохранением неполных байтов. |
| Реальная кодировка не UTF-8В этой ситуации наблюдается конкретный диагностический признак. Сопоставьте его с журналом ошибки и результатом соответствующей проверки, прежде чем выбирать исправление. | Даже при чтении целого файла текст испорчен | Определить исходную кодировку и конвертировать отдельно. |
| Двойное декодированиеВ этой ситуации наблюдается конкретный диагностический признак. Сопоставьте его с журналом ошибки и результатом соответствующей проверки, прежде чем выбирать исправление. | В журнале видны последовательности вида Ð | Установить точное место ошибочной конвертации. |
#Пошаговая диагностика и решение
- 1. Проверьте один и тот же входной файлСделайте копию файла с кириллицей. Просмотрите оригинал в редакторе с явно выбранной UTF-8; если он уже неверен, потоковый декодер не восстановит утраченные символы.
- 2. Найдите опасное преобразованиеВ проекте найдите обработчики
stream.on("data", chunk => chunk.toString("utf8")). Отдельный Buffer может заканчиваться в середине символа, поэтому каждый chunk нельзя декодировать изолированно.
- 3. Используйте setEncoding для читаемого потокаЕсли вход действительно UTF-8, сразу после
createReadStreamвызовите.setEncoding("utf8"). Node сохранит неполные байты между порциями; в обработчик data начнут поступать строки.
- 4. При ручной декодировке используйте StringDecoderВ потоке Buffer создайте
new StringDecoder("utf8"), передавайте каждую порцию в decoder.write() и в конце вызовите decoder.end(). Не создавайте новый StringDecoder на каждой порции.
- 5. Проверьте границу символа на искусственном примереВ тестовом файле создайте пример с разделёнными байтами евро или кириллической буквы, сравните отдельный
Buffer.toStringи StringDecoder. Корректный декодер выдаёт единственный исходный символ.
- 6. Уточните HTTP-кодировку и выводЕсли проблема только при ответе браузеру, проверьте
Content-Type: text/plain; charset=utf-8и отсутствие второй перекодировки; настройка заголовка не исправляет неправильные байты.
- 7. Прогоните контрольные строкиПроверьте русские слова, emoji и латиницу, в том числе когда входные части имеют разные размеры. Если остаются
�, изучите реальные байты источника и его объявленную кодировку. - Самостоятельный тест в файле .mjs, запускаемый командой node test.mjsТекстСамостоятельный тест в файле .mjs, запускаемый командой node test.mjs.
import { StringDecoder } from 'node:string_decoder'; const decoder = new StringDecoder('utf8'); const a = Buffer.from([0xD0]); const b = Buffer.from([0x96]); // Вместе байты «Ж» console.log(decoder.write(a) + decoder.write(b) + decoder.end()); // Ж
#Как проверить результат и что делать, если не помогло
Текст совпадает с исходным побайтно после обратной кодировки в UTF-8, а кириллица и emoji не превращаются в символ замены при произвольных размерах частей.
Если неисправность повторилась, выпишите первый код ошибки и действие, на котором она возвращается. Сравните версию Node.js/npm, рабочий каталог, настройки процесса и журнал тестового запуска. Откатите только внесённые изменения из предварительно сохранённой копии, затем разберите причину на отдельной среде.