Оповідь про кодування та java

З кодуванням у java погано. Тобто, навпаки, все ідеально добре: внутрішнє уявлення рядків – Utf16-BE (і підтримка Unicode була з перших днів). Всі можливі функції вміють перетворювати рядок з маленького регістру на великий, перевіряти чи є цей символ буквою або цифрою, виконувати пошук у рядку (у тому числі з регулярними виразами) та інше. Для цих операцій не потрібно використовувати якісь сторонні бібліотеки на зразок звичних для php mbstring або iconv. Як то кажуть, підтримка багатомовних тестів є в коробці. Тож звідки беруться проблеми? Проблеми виникають, як тільки рядки тексту намагаються "вибратися" з jvm (операції виведення тексту різним споживачам) або навпаки намагаються в цю саму jvm "залізти" (операція читання даних від деякого постачальника).

Казка про капіталістів

Типові проблеми, з якими стикаються java-розробники

Т.к. java програми взаємодіють з різними підсистемами, то й виникаючі проблеми бувають різними. Хоча все, загалом, зводиться до однієї із двох проблем:

Треба сказати, що як параметр другого методу можна передати як параметр об'єкт Locale (відомості про географічне розташування сторінки, її мову, грошові одиниці ...). У цьому випадку будуть повернуті шрифти, локалізовані саме для цієї мови. Якщо ж ніякого параметра при виклику не вказати, то ви отримаєте список шрифтів, прив'язаних до поточної (за замовчуванням) локалі.

Щоб створити шрифт на основі деякого файлу ttf, необхідно викликати статичний метод createFont з класу Font. Як параметр для нього слід вказати файл, який містить визначення шрифту, а також вказати тип цього файлу (Font.TRUETYPE_FONT або Font.TYPE1_FONT). Створенийоб'єкт шрифту можна “налаштувати” вказавши йому розмір чи стиль (plain, italic, bold). Використовуйте для цього метод deriveFont.

кодування

final JFrame jf = new JFrame ("barra"); jf.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);

JPanel pa = new JPanel (new GridLayout (0, 1));

JLabel lab_1 = new JLabel ("Гравітаційні хвилі"); JLabel lab_2 = new JLabel ("Гравітаційні хвилі"); JLabel lab_3 = new JLabel ("Гравітаційні хвилі"); JLabel lab_4 = new JLabel ("Гравітаційні хвилі"); // використовуємо стандартні шрифти: у першому випадку логічний шрифт, а в // другий фізичний. // зверніть увагу, що на картинці вони виглядають однаково lab_1.setFont (new Font ("Serif", Font.PLAIN, 24)); lab_2.setFont (new Font ("Times New Roman", Font.PLAIN, 24));

// тепер пробуємо завантажити шрифт із зовнішнього файлу Font f_ye = Font.createFont (Font.TRUETYPE_FONT, new File ("yermak.ttf")); lab_3.setFont (f_ye.deriveFont (Font.PLAIN, 24.0f)); // і ще один шрифт із зовнішнього файлу Font f_inv = Font.createFont ( Font.TRUETYPE_FONT,new File ( "invest.ttf" )) ; lab_4.setFont (f_inv.deriveFont (Font.PLAIN, 24.0f));

// отримаємо і виведемо у вигляді JComboBox список всіх шрифтів pa.add (new JLabel ("getAllFonts")); Font [] allFonts = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment() .getAllFonts() ; pa.add (new JComboBox (allFonts)); pa.add (new JLabel ("count fonts =" + allFonts.length)); // список назв усіх шрифтів доступних для поточної локалі pa.add (new JLabel ("getAvailableFontFamilyNames")); String [] locFontNames = java.awt.GraphicsEnvironment. getLocalGraphicsEnvironment() .getAvailableFontFamilyNames() ; pa.add (new JComboBox (locFontNames)); pa.add (new JLabel ("count fonts =" + locFontNames.length));

pa.add (lab_1); pa.add (lab_2); pa.add (lab_3); pa.add (lab_4);

jf.setContentPane (pa); jf.pack ();

SwingUtilities.invokeLater ( new Runnable() public void run() jf.setVisible(true); > > );

Тепер повернемося до українських літер та java.

Друга найпоширеніша проблема- це неправильне перетворення кодування. Наприклад, ви хочете прочитати текстовий файл у кодуванні windows-1251. Але при створенні об'єкта InputStreamReader ви вказали неправильне кодування (або поклалися на значення за замовчуванням).

У результаті під час читання файлу символи будуть розглядатися як такі, що належать певній кодовій сторінці. Але зовсім не факт, що деякий код символу коректний для кодування A буде також коректний для кодування B. У разі коректності кодів, ми побачимо те, що деякі символи були замінені на якісь інші символи. А от якщо код є некоректним (наприклад, зарезервований на майбутнє), то такий символ буде замінений на знак “?”.

Для отримання списку всіх доступних кодувань можна використовувати наступний код (виклик статичного методу Charset.availableCharsets):

кодування

Тут "utf-8" правильне кодування, а "windows-1251" - неправильне.

Але попереджаю відразу - це поганий, дуже поганий спосіб "полагодити примус". Пам'ятайте, що під час перетворень можлива втрата символів (через несумісні кодування). Так що якщо ви прочитали дані з файлу в неправильному кодуванні, відсутні символи були замінені на значки питань. Отже, спроба відновити оригінальний масив байтів буде безуспішною.

Java та web.

Web – це те саме місце, де стикаються безліч людей, що працюють під різними версіями операційних систем, що використовують різні браузери і написаний нами сайт повинен працювати завжди і скрізь.

Давайте розглянемо, як дані надходять на вхід браузеру від веб-сервера і те, як браузер надсилає інформацію серверу (точніше веб-додатку, що виконується на ньому). Є два методи для надсилання запитів від браузера до веб-сервера: get та post. Інші методи, такі як put, delete, options, доступні при використанні ajax-дзвінків, а всередині компонента XmlHttpRequest виконується конвертація даних, що надсилаються в “utf-8”, що відразу вирішує ряд проблем з невідомими кодуваннями.

Найідеальніша ситуація – коли відправка йде за допомогою методу POST. У цьому випадку браузер кодує дані в такому самому кодуванні, як і в тому, що була сформована веб-сторінка. За кодування даних, що повертаються, відповідають або вказана вгорі jsp-файлу директива:

Перша з цих опцій (contentType) вказує на кодування вихідного документа, а друга (pageEncoding) на кодування власне файлу, в якому знаходиться код jsp-сторінки.

Або якщо ви створюєте сервлет, то першим кроком потрібно вказати вихідне кодування документа:

public class BlaBlaServlet extends HttpServlet protected void doPost ( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException response.setCharacterEncoding ( "utf-8" ) ; // ну а тепер давайте працювати …. >

protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException doPost (request, response); > >

Класно, значить, якщо ми сформували сторінку в кодуванні utf-8, то дані зформи прийдуть до нас у форматі utf-8. Класно, то класно, але хто сказав, що ваш веб-сервер правильно ці дані зможе розкодувати? Теоретично, коли браузер робить запит до сервера, то відправляється не тільки відомості про те який документ хоче бачити клієнт, не тільки дані з форми, а й відомості про браузер, про кодування, що підтримуються, про мови мов документа, і інше і інше і інше. Може там знайдеться відомості про кодування? Давайте перевіримо. При створенні тега form ви повинні вказати значення не тільки методу відправки (GET або POST), але й атрибута enctype. Його можливі значення: "multipart/form-data" або "application/x-www-form-urlencoded". У першому випадку форма буде здатна надсилати не тільки текстові дані, а й, наприклад, файли (хто б мені сказав, чому sun-вівці не могли реалізувати парсинг подібного запиту самостійно або внести до стандарту для будь-якого servlet-контейнера, а віддали на відкуп стороннім ?). Розглянемо як кодуються дані у разі "multipart/form-data"? Нижче приклад такого запиту:

Як бачите, запит розбито на секції за допомогою деякої унікальної комбінації символів. Отже, де в цьому запиті є вказівка ​​на те, в якому кодуванні надішли дані від браузера? Ніде немає їх. Можливо, кодування вказується при запиті "application/x-www-form-urlencoded"? Гаразд, ось приклад ще одного запиту:

Те, залежно від кодування, дані будуть відправлені або так:

%FF%F9%E8%EA%E8_%F1_%EF%E8%E2%EE%EC- так виглядає слово“ящики з пивом”у кодуванніwindows- 1251

%D1%8F%D1%89%D0%B8%D0%BA%D0%B8_%D1%81_%D0%BF%D0%B8%D0%B2%D0%BE%D0%BC– а так виглядає це слово у кодуванніutf-8.

Повертаючись до аналізу двох прикладів запиту даних, ми нідене бачимо вказівки на те, яке кодування використовується для надсилання даних. Може, у мене не правильний браузер, і якісь інші, правильні, браузери вказують кодування даних, що відправляються? На жаль, ні Internet Explorer 6,7 ні Firefox 2,3 ні Opera 9.5 не вказують відомостей про кодування.

Автоматично визначити кодування tomcat не може, а якщо не може, то буде виконувати перетворення даних з кодування (за замовчуванням) ISO8859-1. Кілька разів мені зустрічалися в мережі рекомендації робити щось на зразок:

Це дуже погана порада. Пам'ятайте, що неправильні операції перетворення можуть призвести до втрати даних і, до того ж, необоротного. Значить, потрібно підказати tomcat-у, як правильно виконати декодування (а інакше, без нашої вказівки, він такого не розкодує... не виправити).

Коли викликається ваш сервлет (або jsp, що суть те саме). То ви можете дізнатися в якому кодуванні до вас прийшли дані, наприклад, так:

Якщо значення кодування null (а воно дорівнює цій величині майже завжди), тоді tomcat вирішує, що вхідні дані були у форматі ISO8859-1 і намагається саме так виконати парсинг рядка. Існує народне повір'я, що якщо створити спеціальний сервлет-фільтр, який буде викликатися до того, як буде виконано перше звернення до списку параметрів, що передаються, і встановить значення правильного кодування, то все запрацює без проблем, наприклад:

Тепер при першому ж зверненні до якогось із вхідних параметрів:

Так само як і для jstl:

Буде виконано розкодування вхідних даних з урахуванням зазначеного кодування.

Можна обійтися і меншою кров'ю, виконавши цю команду всередині вашого сервлета найпершим рядком коду (потрібно тільки бути впевненим у тому, що ніякий інший код не намагався отриматизначення змінних до вас):

Або якщо ви створюєте jsp-файл з використанням jstl-тегів, то таку команду:

Однак для того, щоб вказане значення кодування було застосовано до параметрів, переданих методом GET (стосовно tomcat), потрібно виконати виправлення конфігураційного файлу server.xml і додати для елемента Connector атрибут useBodyEncodingForURI рівний значенню “true”. У цьому випадку розбір параметрів буде виконаний з таким кодуванням, яке ви встановили за допомогою дзвінка request.setCharacterEncoding("utf-8").

Простіше кажучи, або ви вказуєте явно значення кодування для всіх вхідних запитів за допомогою параметраURIEncoding(а-га, ось як завжди і для всіх додатків на цьому хостингу тільки таке кодування є допустимим). Або встановлюєте другу перміннуuseBodyEncodingForURI, що дорівнює значенню true (за замовчуванням її значення false).

Єдина проблема в тому, що ми виконуємо виправлення файлу server.xml ми можемо лише, якщо маємо прямий доступ до каталогу, де встановлений tomcat. Погодьтеся, що у випадку типового віртуального хостингу ми можемо керувати програмою лише за допомогою файлів web.xml та ще META-INF/context.xml – а це не те. Також, якщо ваша програма запущена під іншим веб-сервером, вам потрібно буде розбиратися з його специфічними налаштуваннями.

Деякий час тому я намагався розібратися з налаштуваннями для resin. У FAQ написано, що на аналіз даних впливають такі значення:

Тег character-encoding, може бути дочірнім по відношенню до наступних рівнів налаштування: resin, server, host-default, host, web-app-default, web-app (на рівні програми, а значить ми можемо налаштувати свою програму навіть на звичайному віртуальному хостингу).

Зверніть увагуна схему, яка регламентує вміст web.xml у наступному прикладі (традиційна )

То такий приклад нічого очікувати працювати: т.к. тег character-encoding є специфічним саме для resin.

Якщо значення кодування явно не вказано, то для читання даних використовується кодування за замовчуванням для jvm (file.encoding). На жаль, мої спроби запустити resin із зазначенням вхідного кодування нічим хорошим не закінчилися (після встановлення значення кодування в web.xml передані російськомовні символи перетворювалися на рис його знає що). Так що довелося обходитися звичним request.set CharacterEncoding ('utf-8'); Тоді у мене було мало часу розбиратися в особливостях поведінки resin, також я не виключаю, що це був хитрий баг, тому якщо у когось є нотатки з цього приводу, то прошу поділитися з громадськістю.