Возвращаясь к избитой проблеме с кодировками русских букв, хотелось бы иметь под рукой некий единый справочник или руководство, в котором можно найти решения различных сходных ситуаций. В своё время сам перелопатил множество статей и публикаций, чтобы находить причины ошибок. Задача этой публикации — сэкономить время и нервы читателя и собрать воедино различные причины ошибок с кодировками в разработке на 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 и других.

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

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

Комментарии (17)


  1. lorc
    31.08.2015 14:06
    +12

    Мне кажется что самый правильный способ сократить время отладки и сберечь нервы — хранить всё в utf8, как пытается делать весь современный софт.
    Я бы хотел посмотреть, как вы в cp1251 запишите имя одного известного физика — Erwin Rudolf Josef Alexander Schrodinger, или известнгого теоретика марксизма Mao Zedong (он же ???).


    1. Evengard
      31.08.2015 14:27
      +9

      Тоже удивился. cp1251 в 2015 году отдаёт архаизмом.


  1. webkumo
    31.08.2015 14:35
    +2

    1. Все исходники перетаскиваем на UTF (имейте же совесть! зачем заниматься чепухой с cp1251 на java? Это вам не php!)
    2. Все подключения/сессии db переключаем на utf
    3. Настраиваем контейнер приложения, опять же на utf
    И получаем редкие проблемы отсутствующих в utf символов, которые не касаются ни символов русского языка, ни символов как минимум большинства европейских языков. При полной нативной поддержке со стороны java.


    1. Stalker_RED
      31.08.2015 17:42

      А если был бы php, то есть смысл связываться с cp1251? Не расскажете подробнее, зачем?


      1. Borz
        31.08.2015 17:56

        у строковых методов проблемы с многобайтовыми строками — надо использовать mb_* аналоги, что делают не все.


  1. dMetrius
    31.08.2015 16:01
    +3

    У кого-то в 2015 году ещё открыт вопрос с кодировками и есть с ними проблемы?


    1. sergey-b
      01.09.2015 00:23

      Вот вы смеетесь, а в SQL Server-е до сих пор надо перед каждой строкой ставить буковку N, например: N'Cъешь еще этих мягких булок'. Иначе строка будет храниться в кодировке Cp1251.


    1. grossws
      01.09.2015 01:39

      Да, когда данных много (хотя бы единицы терабайт), экономия в 2 раза становится довольно интересно. И тут однобайтные кодировки и BOCU-1 выходят на сцену. В случае явы всё не так радужно, если держать в памяти явовские строки (они в utf16), но это уже обмен mem/cpu. Аналогично можно использовать какой-нибудь snappy/lz4.


  1. sergey-b
    01.09.2015 00:16

    Вот это шедевр

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

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


  1. sergey-b
    01.09.2015 00:18

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

    Естественно, потому что так уже давно никто не делает.


  1. grossws
    01.09.2015 01:35

    За encoding="cp1251" в xml надо бить линейкой по пальцам.

    Насчёт utf8 в базе дело спорное. Иногда лучше однобайтную (если данные позволяют) или BOCU-1 (в среднем близко к 1 байту на то, что можно представить в однобайтной кодировке), но с её поддержкой есть некоторая проблема, имя которой — IBM.


    1. webkumo
      01.09.2015 04:17
      +2

      Хм… так utf8 тоже однобайтовая (правда расширяемая до 4х байтов в зависимости от конкретного символа), при этом в однобайтовом представлении включает весь нижний ряд ASCII-таблицы (первые 128 символов), если я правильно помню… Какие проблемы-то? а вот cp1251 на немецком сломается.


      1. grossws
        01.09.2015 09:58

        В той части, где 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 байта, что уже не такой серьёзный оверхед.


        1. webkumo
          01.09.2015 10:26

          На всякий случай, обращаю ваше внимание — статья про Java, а она, опять же насколько я помню, в принципе хранит _все_ строки в памяти в своём UTF16. Т.е. выигрыша по памяти так не достичь.
          Да и по дисковой подсистеме — не очень убедительный довод. Бинарные данные обычно куда больше место съедают, нежели строковые…


          1. grossws
            01.09.2015 23:45

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

            Да и по дисковой подсистеме — не очень убедительный довод. Бинарные данные обычно куда больше место съедают, нежели строковые…
            Это очень сильно зависит от специфики данных и области деятельности.


            1. webkumo
              02.09.2015 11:07
              -1

              И заниматься n раз перекодированием байт-буфер<->строка? Поясните свою мысль, пожалуйста, пока она кажется как минимум сомнительной.


  1. Devid_Nezdeshniy
    01.09.2015 09:00

    Вопрос не в перегоне именно в cp1251, она как пример взята. Вместо неё можно подставить любую нужную кодировку.