Варианты решения могут быть не единственными, охотно добавлю предложенные читателем, если они будут рабочими.
Итак, поехали.
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)
webkumo
31.08.2015 14:35+21. Все исходники перетаскиваем на UTF (имейте же совесть! зачем заниматься чепухой с cp1251 на java? Это вам не php!)
2. Все подключения/сессии db переключаем на utf
3. Настраиваем контейнер приложения, опять же на utf
И получаем редкие проблемы отсутствующих в utf символов, которые не касаются ни символов русского языка, ни символов как минимум большинства европейских языков. При полной нативной поддержке со стороны java.Stalker_RED
31.08.2015 17:42А если был бы php, то есть смысл связываться с cp1251? Не расскажете подробнее, зачем?
dMetrius
31.08.2015 16:01+3У кого-то в 2015 году ещё открыт вопрос с кодировками и есть с ними проблемы?
sergey-b
01.09.2015 00:23Вот вы смеетесь, а в SQL Server-е до сих пор надо перед каждой строкой ставить буковку N, например: N'Cъешь еще этих мягких булок'. Иначе строка будет храниться в кодировке Cp1251.
grossws
01.09.2015 01:39Да, когда данных много (хотя бы единицы терабайт), экономия в 2 раза становится довольно интересно. И тут однобайтные кодировки и BOCU-1 выходят на сцену. В случае явы всё не так радужно, если держать в памяти явовские строки (они в utf16), но это уже обмен mem/cpu. Аналогично можно использовать какой-нибудь snappy/lz4.
sergey-b
01.09.2015 00:16Вот это шедевр
String db_string = new String(MyValue.getBytes(«cp1251»),«utf-8»);
Благодаря таким вот «костылям», когда вы наконец обновите настройки своего ПО, и у вас строка MyValue станет содержать нормальный текст, то вся система посыпется.
sergey-b
01.09.2015 00:18восстановить ссылки на источники нет возможности
Естественно, потому что так уже давно никто не делает.
grossws
01.09.2015 01:35За
encoding="cp1251"
в xml надо бить линейкой по пальцам.
Насчёт utf8 в базе дело спорное. Иногда лучше однобайтную (если данные позволяют) или BOCU-1 (в среднем близко к 1 байту на то, что можно представить в однобайтной кодировке), но с её поддержкой есть некоторая проблема, имя которой — IBM.webkumo
01.09.2015 04:17+2Хм… так utf8 тоже однобайтовая (правда расширяемая до 4х байтов в зависимости от конкретного символа), при этом в однобайтовом представлении включает весь нижний ряд ASCII-таблицы (первые 128 символов), если я правильно помню… Какие проблемы-то? а вот cp1251 на немецком сломается.
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 байта, что уже не такой серьёзный оверхед.webkumo
01.09.2015 10:26На всякий случай, обращаю ваше внимание — статья про Java, а она, опять же насколько я помню, в принципе хранит _все_ строки в памяти в своём UTF16. Т.е. выигрыша по памяти так не достичь.
Да и по дисковой подсистеме — не очень убедительный довод. Бинарные данные обычно куда больше место съедают, нежели строковые…grossws
01.09.2015 23:45На всякий случай, обращаю ваше внимание — статья про Java, а она, опять же насколько я помню, в принципе хранит _все_ строки в памяти в своём UTF16.
Я в курсе, см. комментарий выше. Не обязательно держать все данные в виде строк. Ява вполне позволяет держать byte[] и ByteBuffer в памяти. А распаковывать в utf-16 строки только по необходимости. Это и есть memory/cpu trade-off, о котором я говорил выше.
Да и по дисковой подсистеме — не очень убедительный довод. Бинарные данные обычно куда больше место съедают, нежели строковые…
Это очень сильно зависит от специфики данных и области деятельности.webkumo
02.09.2015 11:07-1И заниматься n раз перекодированием байт-буфер<->строка? Поясните свою мысль, пожалуйста, пока она кажется как минимум сомнительной.
Devid_Nezdeshniy
01.09.2015 09:00Вопрос не в перегоне именно в cp1251, она как пример взята. Вместо неё можно подставить любую нужную кодировку.
lorc
Мне кажется что самый правильный способ сократить время отладки и сберечь нервы — хранить всё в utf8, как пытается делать весь современный софт.
Я бы хотел посмотреть, как вы в cp1251 запишите имя одного известного физика — Erwin Rudolf Josef Alexander Schrodinger, или известнгого теоретика марксизма Mao Zedong (он же ???).
Evengard
Тоже удивился. cp1251 в 2015 году отдаёт архаизмом.