Кодировки и веб-страницы

Возвращаясь к избитой проблеме с кодировками русских букв, хотелось бы иметь под рукой некий единый справочник или руководство, в котором можно найти решения различных сходных ситуаций. В своё время сам перелопатил множество статей и публикаций, чтобы находить причины ошибок. Задача этой публикации — сэкономить время и нервы читателя и собрать воедино различные причины ошибок с кодировками в разработке на Java и JSP и способы их устранения.

Варианты решения могут быть не единственными, охотно добавлю предложенные читателем, если они будут рабочими.

Итак, поехали.

1. Проблема: при получении разработанной мной страницы браузером весь русский текст идёт краказябрами, даже тот, который забит статически.
Причина: браузер неверно определяет кодировку текста, потому что нет явного указания.
Решение: явно указать кодировку:
a) HTML: добавляем тэг META в хидер страницы:
[\< meta http-equiv="Content-Type" content="text/html; charset=cp1251"\>]

б) XML: указываем кодировку в заголовке:
[<?xml version="1.0" encoding="cp1251"?>]

в) JSP — задаём тип контента в заголовке:
[<%@ page language="java" contentType="text/html;charset=cp1251"%>]

г) JSP — задаём кодировку возвращаемой страницы
[<%@ page pageEncoding="cp1251"%>]

д) Java — устанавливаем хидер ответа:
[response.setCharacterEncoding("cp1251");]
[response.setContentType("text/html;charset=cp1251");]


2. Проблема: написанный в JSP-странице статический русский текст почему-то идёт краказабрами, хотя кодировка страницы задана.
Причина: статический текст был написан в кодировке, отличной от заданного странице.
Решение: изменить кодировку в редакторе (например, для AkelPad нажимаем «Сохранить как» и выбираем нужную кодировку).

3. Проблема: получаемый из запроса текст идёт кракозябрами.
Причина: кодировка запроса отличается от используемой для его обработки кодировки.
Решение: установить кодировку запроса или перекодировать в нужную.
а) Java, со стороны отправителя не задана нужная кодировка — перекодируем в нужную:
[String MyParam= new String(request.getParameter("MyParam").getBytes("ISO-8859-1"),"cp1251");]

Примечание: кодировка ISO-8859-1 устанавливается по умолчанию, если не была задана другая.
б) Java, со стороны отправителя задана нужная кодировка — устанавливаем кодировку запроса:
[request.setCharacterEncoding("cp1251");]


4. Проблема: отправленный GET-параметром русский текст при редиректе приходит кракозябрами.
Причина: упаковка русского текста в URI по умолчанию идёт в ISO-8859-1.
Решение: упаковать текст в нужной кодировке вручную.
а) JSP, URLEncoder:
[<%@ page import="java.net.URLEncoder"%>
<%response.sendRedirect("targetPage.jsp?MyParam="+URLEncoder.encode("Русский текст","cp1251"));%>]


5. Проблема: текст из базы данных читается кракозябрами.
Причина: кодировка текста, прочитанного из базы данных, отличается от кодировки страницы.
Решение: установить соответствующую кодировку страницы, либо перекодировать полученные из базы данных значения.
а) Java, перекодирование считанной в db_string базы данных строки:
[String MyValue = new String(db_string.getBytes("utf-8"),"cp1251");]


6. Проблема: текст записывается в базу данных кракозябрами, хотя на странице отображается правильно.
Причина: кодировка записываемой строки отличается от кодировки сессии работы с базой данных, либо от кодировки базы данных (стоит помнить, что они не всегда совпадают).
Решение: установить необходимую кодировку сессии или перекодировать строку.
а) Java, перекодирование записываемой строки db_string в кодировку сессии или базы данных:
[String db_string = new String(MyValue.getBytes("cp1251"),"utf-8");]

б) Java, MySQL, настройка параметров подключения в строке dburl, передаваемой функции коннекта:
[dburl += "?characterEncoding=cp1251";]

в) MySQL, настройка параметров подключения в XML-описателе контекста, добавляем атрибут к тегу \<Resource\>:
[connectionProperties="useUnicode=no;characterEncoding=cp1251;"]

г) MySQL, прямая установка кодировки сессии вызовом SET NAMES (connect — объект подключения Connection):
[CallableStatement cs = connect.prepareCall("set names 'cp1251'");
cs.execute();]


7. Проблема: если ничего не помогло…
Решение: всегда остаётся самый «топорный» метод — прямое перекодирование.
а) Для известной кодировки источника:
[String MyValue = new String(source_string.getBytes("utf-8"),"cp1251");]

б) Для параметра запроса:
[String MyValue = new String(request.getParameter("MyParam").getBytes(request.getCharacterEncoding()),"cp1251");]


Дополнение, или что нужно знать:

1. Кодировки базы данных и сессии подключения могут различаться, в зависимости от конкретной СУБД и драйвера. К примеру, при подключении к MySQL стандартным драйвером com.mysql.jdbc.Driver без явного указания кодировка сессии устанавливалась в UTF-8, несмотря на другую кодировку схемы БД.
2. Кодировка упаковки строки запроса в URI по умолчанию устанавливается в ISO-8859-1. С подобным можно столкнуться, например, при передаче явно заданного текста в редиректе с одной страницы на другую.
3. Взаимоотношения кодировок страницы, базы данных, сессии, параметров запроса и ответа не зависят от языка разработки и описанные для Java функции имеют аналоги для PHP, Asp и других.

Примечание: восстановить ссылки на источники нет возможности, все примеры взяты из собственного кода, хотя когда-то так же выискивал их по многочисленным форумам.

Надеюсь, этот небольшой обзор поможет начинающим веб-программистам сократить время отладки и сберечь нервы.
Share post

Comments 17

    +12
    Мне кажется что самый правильный способ сократить время отладки и сберечь нервы — хранить всё в utf8, как пытается делать весь современный софт.
    Я бы хотел посмотреть, как вы в cp1251 запишите имя одного известного физика — Erwin Rudolf Josef Alexander Schrödinger, или известнгого теоретика марксизма Máo Zédōng (он же 毛澤東).
      +9
      Тоже удивился. cp1251 в 2015 году отдаёт архаизмом.
      +2
      1. Все исходники перетаскиваем на UTF (имейте же совесть! зачем заниматься чепухой с cp1251 на java? Это вам не php!)
      2. Все подключения/сессии db переключаем на utf
      3. Настраиваем контейнер приложения, опять же на utf
      И получаем редкие проблемы отсутствующих в utf символов, которые не касаются ни символов русского языка, ни символов как минимум большинства европейских языков. При полной нативной поддержке со стороны java.
        0
        А если был бы php, то есть смысл связываться с cp1251? Не расскажете подробнее, зачем?
          0
          у строковых методов проблемы с многобайтовыми строками — надо использовать mb_* аналоги, что делают не все.
        +3
        У кого-то в 2015 году ещё открыт вопрос с кодировками и есть с ними проблемы?
          0
          Вот вы смеетесь, а в SQL Server-е до сих пор надо перед каждой строкой ставить буковку N, например: N'Cъешь еще этих мягких булок'. Иначе строка будет храниться в кодировке Cp1251.
            0
            Да, когда данных много (хотя бы единицы терабайт), экономия в 2 раза становится довольно интересно. И тут однобайтные кодировки и BOCU-1 выходят на сцену. В случае явы всё не так радужно, если держать в памяти явовские строки (они в utf16), но это уже обмен mem/cpu. Аналогично можно использовать какой-нибудь snappy/lz4.
            0
            Вот это шедевр

            String db_string = new String(MyValue.getBytes(«cp1251»),«utf-8»);

            Благодаря таким вот «костылям», когда вы наконец обновите настройки своего ПО, и у вас строка MyValue станет содержать нормальный текст, то вся система посыпется.

              0
              восстановить ссылки на источники нет возможности

              Естественно, потому что так уже давно никто не делает.
                0
                За encoding="cp1251" в xml надо бить линейкой по пальцам.

                Насчёт utf8 в базе дело спорное. Иногда лучше однобайтную (если данные позволяют) или BOCU-1 (в среднем близко к 1 байту на то, что можно представить в однобайтной кодировке), но с её поддержкой есть некоторая проблема, имя которой — IBM.
                  +2
                  Хм… так utf8 тоже однобайтовая (правда расширяемая до 4х байтов в зависимости от конкретного символа), при этом в однобайтовом представлении включает весь нижний ряд ASCII-таблицы (первые 128 символов), если я правильно помню… Какие проблемы-то? а вот cp1251 на немецком сломается.
                    0
                    В той части, где utf-8 кодируется в один байт (0x00-0x7f, но это не делает её однобайтной), она неотличима от cp1251, latin-1 и т. п.

                    Когда я говорю «данные позволяют», я и имею ввиду, что данные в рамках одной группы (например, чисто европейские языки в latin-1, тогда можно и французкий, и английский, и немецкий кодировать в 1 байт).

                    Если у вас данные исключительно на русском, то в utf-8 каждый символ будет занимать 2 байта, и вы от этого оверхеда нукуда не уйдёте. Пока данных мало — всё хорошо, а когда разговор о том, чтобы влезть в 256 GiB RAM против, грубо, 512 GiB или на один 800 GB SSD против двух, то этот вопрос встаёт в полный рост.

                    BOCU-1 же позволяет использовать всю мощь юникода, при этом давая компактый выхлоп при использовании относительно длинных последовательностей символов одной группы Юникода. Т. е. если у вас 100 символов кириллицы, то вы получите, например, 103 байта, что уже не такой серьёзный оверхед.
                      0
                      На всякий случай, обращаю ваше внимание — статья про Java, а она, опять же насколько я помню, в принципе хранит _все_ строки в памяти в своём UTF16. Т.е. выигрыша по памяти так не достичь.
                      Да и по дисковой подсистеме — не очень убедительный довод. Бинарные данные обычно куда больше место съедают, нежели строковые…
                        0
                        На всякий случай, обращаю ваше внимание — статья про Java, а она, опять же насколько я помню, в принципе хранит _все_ строки в памяти в своём UTF16.
                        Я в курсе, см. комментарий выше. Не обязательно держать все данные в виде строк. Ява вполне позволяет держать byte[] и ByteBuffer в памяти. А распаковывать в utf-16 строки только по необходимости. Это и есть memory/cpu trade-off, о котором я говорил выше.

                        Да и по дисковой подсистеме — не очень убедительный довод. Бинарные данные обычно куда больше место съедают, нежели строковые…
                        Это очень сильно зависит от специфики данных и области деятельности.
                          –1
                          И заниматься n раз перекодированием байт-буфер<->строка? Поясните свою мысль, пожалуйста, пока она кажется как минимум сомнительной.
                  0
                  Вопрос не в перегоне именно в cp1251, она как пример взята. Вместо неё можно подставить любую нужную кодировку.

                  Only users with full accounts can post comments. Log in, please.