Кодування кирилиці - російської мови
Повна підтримка діректів.htaccess додається ...
Пролонгації домену 199-00 руб
Кодування являє собою таблицю символів, де кожній букві алфавіту (а також цифрам і спеціальними знаками) Привласнений свій унікальний номер - код символу.
Стандартизована тільки половина таблиці, т.зв. ASCII-код - перші 128 символів, які включають в себе літери латинського алфавіту. І з ними ніколи не буває проблем. Друга ж половина таблиці (а всього в ній 256 символів - за кількістю станів, який може прийняти один байт) віддана під національні символи, і в кожній країні ця частина різна. Але тільки в Росії примудрилися придумати цілих 5 різних кодувань. Термін "різні" позначає те, що одному і тому самому символу відповідає різний цифровий код. Тобто якщо ми неправильно визначимо кодування тексту, то нашій увазі абсолютно нечитаний текст.
Кодування з'явилися історично. Перша широко використовувана російське кодування називалося KOI-8. Її придумали, коли адаптували до російської мови систему UNIX. Це було ще в сімдесятих - до появи персоналок. І до сих пір в UNIX це вважається основним кодуванням.
Потім з'явилися перші персональні комп'ютери, і почався переможний хід DOS. Замість того щоб скористатися вже придуманим кодуванням, Microsoft вирішила зробити свою, ні з чим не сумісну. так з'явилася DOS-кодування (або 866 кодова сторінка). У ній, до речі, були введені спецсимволи для малювання рамок, що широко використовувалося в програмах написаних під DOS. Наприклад, в тому ж Norton Commander-е.
Паралельно з IBM-сумісними розвивалися і Macintosh-комп'ютери. Незважаючи на те, що їх частка в Росії дуже мала, проте, потреба в русифікації існувала і, зрозуміло, була придумана ще одне кодування - MAC.
Час минав, і 1990 року Microsoft виявила на світло першу успішну версію Windows 3.0-3.11. А разом з нею і підтримку національних мов. І знову був пророблений такий же фокус, як і з DOS. З незрозумілих причин вони не підтримали жодну, із уже існуючих раніше (як це зробила OS / 2, яка прийняла за стандарт DOS-кодування), а запропонували нову Win-кодування (Або кодова сторінка 1251 ). Де-факто, вона стала найпоширенішою в Росії.
І, нарешті, п'ятий варіант кодування пов'язаний вже не з конкретною фірмою, а зі спробами стандартизації кодувань на рівні всієї планети. Займалася цим ISO - міжнародна організація по стандартам. І, здогадайтеся, що вони зробили з російською мовою? Замість того, щоб прийняти за "стандартну російську" котрусь із вищеописаних, вони придумали ще одну (!) І назвали її довгим нелегкотравним сполученням ISO-8859-5. Зрозуміло, вона теж виявилася ні з чим не сумісної. І в теперішній момент це кодування практично ніде не застосовується. Здається, її використовують тільки в базі даних Oracle. По крайней мере, я ні разу не бачив текст в цьому кодуванні. Проте, її підтримка присутня у всіх броузерах.
Зараз йде робота над створенням нового універсального кодування ( UNICODE), В якій передбачається в одну кодову таблицю запхнути всі мови світу. Тоді точно проблем не буде. Для цього на кожен символ відвели 2 байти. Таким чином, максимальна кількість знаків у таблиці розширилося до 65535. Але до моменту, коли всі перейдуть на UNICODE, залишається ще занадто багато часу.
Тут трохи відволікся і розглянемо для цілісного сприйняття мета тег - Content-Type.
Мета теги використовуються для опису властивостей HTML документа і повинні знаходиться в рамках тега HEAD. Мета теги типу NAME містять текстову інформацію про документ, його автора й деякі рекомендації для пошукових машин. Наприклад: Robots, Description, Keywords, Author, Copyright.
Мета теги типу HTTP-EQUIV впливають на формування заголовка документа і визначають режим його обробки.
Мета тег Content-Type - Відповідає за вказівку типу документа і кодування символів.
Використовувати мета тег Content-Type треба тільки з урахуванням деяких нюансів:
По - перше, кодування символів тексту повинне відповідати кодуванню, зазначеному в тезі.
По - друге, сервер не повинен міняти кодування тексту при обробці запиту браузера.
По - третє, якщо сервер змінює кодування тексту, він повинен скорегувати або видалити Meta-тег Content-Type.
Недотримання цих вимог може призвести до наступного: web-сервер автоматично визначить кодування запиту клієнта і віддасть сторіночку web-браузеру перекодованої. Броузер, в свою чергу, буде читати документ відповідно до мета тегом Content-Type. І якщо кодування не збіжаться, то прочитати документ можна буде тільки після ряду хитромудрих маніпуляцій. Особливо це характерно для старих браузерів.
Увага! Мета тег Content-Type дуже часто вставляється генераторами HTML коду.
Найбільш часто зустрічаються типи кодування:
Windows-1251 - Кирилиця (Windows).
KOI8-r - Кирилиця (КОІ8-Р)
cp866 - Кирилиця (DOS).
Windows -1252 - Західна Європа (Windows).
Windows-1250 - Центральна Європа (Windows).
Напевно всім відомий мета тег -
В даному матеріалі використовувалися уривки зі статті з сайту http://cherry-design.ru/
Нещодавно звільнені домени з PR та ТИЦ:
Сервіс http://reg.ru - найбільшого провайдера та реєстратора доменів дозволяє подати заявку на реєстрацію доменного імені, яке нещодавно було звільнено колишнім Адміністратором. Звільнені домени часто мають високі Показати ТИЦ і PR і можуть бути цікаві до придбання.
Звільнені домени.RU c ТИЦ: |
Вільні преміум-домени: |
Обсяг інформації: 7659 bytes
кодування
Коли я тільки починав програмувати на мові C, першою моєю програмою (не рахуючи HelloWorld) була програма перекодування текстових файлів з основної кодування ГОСТ-а (пам'ятаєте таку? :-) в альтернативну. Було це в далекому 1991-го року. З тих пір багато чого змінилося, але за минулі 10 років подібні програмки свою актуальність, на жаль, не втратили. Занадто багато вже накопичено даних в різноманітних кодуваннях і занадто багато використовується програм, які вміють працювати тільки з однією. Для російської мови існує не менше десятка різних кодувань, що робить проблему ще більш заплутаною.
Звідки ж узялися всі ці кодування і для чого вони потрібні? Комп'ютери за своєю природою можуть працювати тільки з числами. Для того щоб зберігати букви в пам'яті комп'ютера треба поставити у відповідність кожній букві якесь число (приблизно такий же принцип використовувався і до появи комп'ютерів - згадайте про ту ж азбуку Морзе). Причому число бажано поменше - чим менше двійкових розрядів буде задіяно, тим ефективніше можна буде використовувати пам'ять. Ось це відповідність набору символів і чисел власне і є кодування. Бажання будь-яку ціну зберегти пам'ять, а так само роз'єднаність різних груп комп'ютерників і призвела до нинішнього стану справ. Найпоширенішим способом кодування зараз є використання для одного символу одного байта (8 біт), що визначає загальна кількість символів в 256. Набір перших 128 символів стандартизований (набір ASCII) і є однаковими у всіх поширених кодуваннях (ті кодування, де це не так вже практично вийшли з ужитку). Англицкий букви і розділові знаки розташовуються знаходяться в цьому діапазоні, що і визначає їх вражаючу живучість в комп'ютерних системах :-). Інші мови знаходяться не в настільки щасливому становищі - їм усім доводиться тулитися в останніх 128 числах.
Unicode
В кінці 80-х багато хто усвідомив необхідність створення єдиного стандарту на кодування символів, що і призвело до появи Unicode. Unicode - це спроба раз і назавжди зафіксувати конкретне число за конкретним символом. Зрозуміло, що в 256 символів тут не вкластися при всьому бажанні. Досить довгий час здавалося, що вже 2-х то байт (65536 символів) повинно вистачити. Але ж ні - остання версія стандарту Unicode (3.1) визначає вже 94140 символів. Для такого кол-ва символів, напевно, вже доведеться використовувати 4 байти (4294967296 символів). Може бути і вистачить на деякий час ... :-)
У набір символів Unicode входять всілякі літери з усякими рисками і пріпендюлькамі, грецькі, математичні, ієрогліфи, символи псевдографіки і пр. і пр. У тому числі і так улюблені нами символи кирилиці (діапазон значень 0x0400-0x04ff). Так що з цього боку ніякої дискримінації немає.
Якщо Вам цікаві конкретні коду символів, для їх перегляду зручно використовувати програму "Таблиця символів" з WinNT. Ось, наприклад, діапазон кирилиці:
Якщо у Вас інша OS або Вас цікавить офіційне тлумачення, то повну розкладку символів (charts) можна знайти на офіційному сайті Unicode (http://www.unicode.org/charts/web.html).
Типи char і byte
В Java для символів виділений окремий тип даних char розміром у 2 байти. Це часто породжує плутанину в умах початківців (особливо якщо вони раніше програмували на інших мовах, наприклад на C / C ++). Справа в тому, що в більшості інших мов для обробки символів використовуються типи даних розміром в 1 байт. Наприклад, в C / C ++ тип char в більшості випадків використовується як для обробки символів, так і для обробки байтів - там немає поділу. В Java для байтів є свій тип - тип byte. Таким чином C-ішному char відповідає Java-вський byte, а Java-чортківському char зі світу C найближче тип wchar_t. Треба чітко розділяти поняття символів і байтів - інакше нерозуміння і проблеми гарантовані.
Java практично з самого свого народження використовує для кодування символів стандарт Unicode. Бібліотечні функції Java очікують побачити в змінних типу char символи, представлені кодами Unicode. В принципі, Ви, звичайно, можете запхати туди що завгодно - цифри є цифри, процесор все стерпить, але при будь-якій обробці бібліотечні функції будуть діяти виходячи з припущення що їм передали кодування Unicode. Так що можна спокійно думати, що у типу char кодування зафіксована. Але це всередині JVM. Коли дані читаються ззовні або передаються назовні, то вони можуть бути представлені тільки одним типом - типом byte. Всі інші типи конструюються з байтів в залежності від використовуваного формату даних. Ось тут то на сцену і виходять кодування - в Java це просто формат даних для передачі символів, який використовується для формування даних типу char. Для кожної кодової сторінки в бібліотеці є по 2 класу перекодування (ByteToChar і CharToByte). Класи ці лежать в пакеті sun.io. Якщо, при перекодуванні з char в byte не було знайдено відповідного символу, він замінюється на символ ?.
До речі, ці файли кодових сторінок в деяких ранніх версіях JDK 1.1 містять помилки, що викликають помилки перекодіровок, а то і взагалі виключення при виконанні. Наприклад, це стосується кодування KOI8_R. Найкраще, що можна при цьому зробити - змінити версію на більш пізню. Судячи з Sun-івської опису, більшість цих проблем було вирішено у версії JDK 1.1.6.
До появи версії JDK 1.4 набір доступних кодувань визначався тільки виробником JDK. Починаючи з 1.4 з'явилося нове API (пакет java.nio.charset), за допомогою якого Ви вже можете створити свою власну систему кодування (наприклад підтримати рідко використовувану, але моторошно необхідну саме Вам).
клас String
У більшості випадків для подання рядків в Java використовується об'єкт типу java.lang.String. Це звичайний клас, який всередині себе зберігає масив символів (char), і який містить багато корисних методів для маніпуляції символами. Найцікавіші - це конструктори, що мають першим параметром масив байтів (byte) і методи getBytes (). За допомогою цих методів Ви можете виконувати перетворення з масиву байтів в рядки і назад. Для того, щоб вказати яке кодування при цьому використовувати у цих методів є строковий параметр, який задає її ім'я. Ось, наприклад, як можна виконати перекодування байтів з КОИ-8 в Windows-1251:
// Дані в кодуванні ЯКІ-8 byte koi8Data = ...; // Перетворимо з ЯКІ-8 в Unicode String string = new String (koi8Data, "KOI8_R"); // Перетворимо з Unicode в Windows-1251 byte winData = string.getBytes ( "Cp1251");
Список 8-ми бітових кодувань, доступних в сучасних JDK і підтримують російські букви Ви можете знайти нижче, в розділі.
Так як кодування - це формат даних для символів, крім знайомих 8-ми бітових кодувань в Java також на рівних присутні і багатобайтові кодування. До таких відносяться UTF-8, UTF-16, Unicode і ін. Наприклад ось так можна отримати байти в форматі UnicodeLittleUnmarked (16-ти бітове кодування Unicode, молодший байт перший, без ознаки порядку байтів):
// Перетворимо з Unicode в UnicodeLittleUnmarked byte data = string.getBytes ( "UnicodeLittleUnmarked");
При подібних перетвореннях легко помилитися - якщо кодування байтових даних не соотвествуют вказаною параметру при перетворенні з byte в char, то перекодування буде виконано неправильно. Іноді після цього можна витягнути правильні символи, але частіше за все частина даних буде безповоротно втрачена.
У реальній програмі явно вказувати кодову сторінку не завжди зручно (хоча більше надійно). Для цього була введена кодування за замовчуванням. За умовчанням вона залежить від системи і її налаштувань (для російських виндов прийнята кодування Cp1251), і в старих JDK її можна змінити установкою системного властивості file.encoding. В JDK 1.3 зміна цієї налаштування іноді спрацьовує, іноді - ні. Викликано це наступним: спочатку file.encoding ставиться з регіональних налаштувань комп'ютера. Посилання на кодування за замовчуванням запам'ятовується в нутрії при першому перетворенні. При цьому використовується file.encoding, але це перетворення відбувається ще до використання аргументів запуску JVM (собсно, при їх розборі). Взагалі-то, як стверджують в Sun, ця властивість відображає системну кодування, і вона не повинна змінюватися в командному рядку (див., Наприклад, коментарі до BugID) Проте в JDK 1.4 Beta 2 зміна цієї настройки знову почала надавати ефект. Що це, свідома зміна або побічний ефект, який може знову зникнути - Sun-вівці чіткої відповіді поки не дали.
Ця кодування використовується тоді, коли явно не вказано назву сторінки. Про це треба завжди пам'ятати - Java не буде намагатися передбачити кодування байтів, які Ви передаєте для створення рядка String (так само вона не зможе прочитати Ваші думки з цього приводу :-). Вона просто використовує поточну кодування за замовчуванням. Оскільки ця настройка одна на все перетворення, іноді можна наштовхнутися на неприємності.
Для перетворення з байтів в символи і назад слід користуватися тільки цими методами. Просте приведення типу використовувати в більшості випадків не можна - кодування символів при цьому не буде враховуватися. Наприклад, однією з найпоширеніших помилок є читання даних побайтно за допомогою методу read () з InputStream, а потім приведення отриманого значення до типу char:
InputStream is = ..; int b; StringBuffer sb = new StringBuffer (); while ((b = is.read ())! = - 1) (sb.append ((char) b); // <- так делать нельзя ) String s = sb.toString ();
Зверніть увагу на приведення типу - "(char) b". Значення байтів замість перекодування просто скопійовано в char (діапазон значень 0-0xFF, а не той, де знаходиться кирилиця). Такому копіювання відповідає кодування ISO-8859-1 (яка один в один відповідає першим 256 значень Unicode), а значить, можна вважати, що цей код просто використовує її (замість тієї, в якій реально закодовані символи в оригінальних даних). Якщо Ви спробуєте відобразити отримане значення - на екрані будуть або вопросики або кракозябли. Наприклад, при читанні рядка "АБВ" в віндового кодуванні може запросто відобразитися щось на кшталт такого: "ÀÁÂ". Подібного роду код часто пишуть програмісти на заході - з англійськими літерами працює, і ладно. Виправити такий код легко - треба просто замінити StringBuffer на ByteArrayOutputStream:
InputStream is = ..; int b; ByteArrayOutputStream baos = new ByteArrayOutputStream (); while ((b = is.read ())! = - 1) (baos.write (b);) // Перекодування байтів на рядок з використанням кодування за замовчуванням String s = baos.toString (); // Якщо потрібна конкретна кодування - просто вкажіть її при виклику toString (): // // s = baos.toString ( "Cp1251");Більш докладно про поширені помилки дивіться розділ.
8-ми бітові кодування російських букв
Ось основні 8-ми бітові кодування російських букв, які поширені:
Крім основної назви можна використовувати синоніми. Набір їх може відрізнятися в різних версіях JDK. Ось список від JDK 1.3.1:
- Cp1251:
- Windows-1251
- Cp866:
- IBM866
- IBM-866
- CP866
- CSIBM866
- KOI8_R:
- KOI8-R
- CSKOI8R
- ISO8859_5:
- ISO8859-5
- ISO-8859-5
- ISO_8859-5
- ISO_8859-5: 1988
- ISO-IR-144
- 8859_5
- Cyrillic
- CSISOLatinCyrillic
- IBM915
- IBM-915
- Cp915
Причому синоніми, на відміну від основного імені нечутливі до регістру символів - така особливість реалізації.
Варто відзначити, що ці кодування на деяких JVM можуть бути відсутні. Наприклад, з сайту Sun можна завантажити дві різні версії JRE - US і International. У US версії присутня тільки мінімум - ISO-8859-1, ASCII, Cp1252, UTF8, UTF16 і кілька варіацій двухбайтового Unicode. Все інше є тільки в International варіанті. Іноді через це можна нарватися на граблі з запуском програми, навіть якщо їй не потрібні російські літери. Типова помилка, що виникає при цьому:
Error occurred during initialization of VM java / lang / ClassNotFoundException: sun / io / ByteToCharCp1251
Виникає вона, як не важко здогадатися, через те, що JVM, виходячи з російських регіональних налаштувань намагається встановити кодування за замовчуванням в Cp1251, але, тому що клас підтримки такий відсутній в US версії, закономірно обламується.
Файли і потоки даних
Так само як і байти концептуально відокремлені від символів, в Java розрізняються потоки байтів і потоки символів. Роботу з байтами представляють класи, які прямо або побічно успадковують класи InputStream або OutputStream (плюс клас-унікум RandomAccessFile). Роботу з символами представляє солодка парочка класів Reader / Writer (і їх спадкоємці, зрозуміло).
Для читання / записі не перетворених байтів використовуються потоки байтів. Якщо відомо, що байти представляють собою тільки символи в деякій кодуванні, можна використовувати спеціальні класи-перетворювачі InputStreamReader і OutputStreamWriter, щоб отримати потік символів і працювати безпосередньо з ним. Зазвичай це зручно в разі звичайних текстових файлів або при роботі з багатьма мережевими протоколами Internet. Кодування символів при цьому вказується в конструкторі класу-перетворювача. приклад:
// Рядок Unicode String string = "..."; // Записуємо рядок в текстовий файл в кодуванні Cp866 PrintWriter pw = new PrintWriter // клас з методами записи рядків (New OutputStreamWriter // клас-перетворювач (New FileOutputStream ( "file.txt"), "Cp866"); pw.println (string); // записуємо рядок в файл pw.close ();
Якщо в потоці можуть бути присутніми дані в різних кодуваннях або ж символи перемішані з іншими двійковими даними, то краще читати і записувати масиви байтів (byte), а для перекодування використовувати вже згадані методи класу String. приклад:
// Рядок Unicode String string = "..."; // Записуємо рядок в текстовий файл в двох кодуваннях (Cp866 і Cp1251) OutputStream os = new FileOutputStream ( "file.txt"); // клас записи байтів в файл // Записуємо рядок в кодуванні Cp866 os.write (string.getBytes ( "Cp866")); // Записуємо рядок в кодуванні Cp1251 os.write (string.getBytes ( "Cp1251")); os.close ();
Консоль в Java традиційно представлена потоками, але, на жаль, не символів, а байтів. Справа в тому, що потоки символів з'явилися тільки в JDK 1.1 (разом з усім механізмом кодувань), а доступ до консольного вводу / виводу проектувався ще в JDK 1.0, що і призвело до появи виродка у вигляді класу PrintStream. Цей клас використовується в змінних System.out і System.err, які власне і дають доступ до висновку на консоль. За всіма ознаками це потік байтів, але з купою методів запису рядків. Коли Ви записуєте в нього рядок, всередині відбувається конвертація в байти з використанням кодування за замовчуванням, що в разі виндов, як правило, є неприйнятним - кодування за замовчуванням буде Cp1251 (Ansi), а для консольного вікна зазвичай потрібно використовувати Cp866 (OEM). Ця помилка була зареєстрована ще в 97-му році () але Sun-вівці виправляти її начебто не поспішають. Так як методу установки кодування в PrintStream немає, для вирішення цієї проблеми можна підмінити стандартний клас на власний за допомогою методів System.setOut () і System.setErr (). Ось, наприклад, звичайне початок в моїх програмах:
... public static void main (String args) ( // Установка виведення консольних повідомлень із належним кодуванням try (String consoleEnc = System.getProperty ( "console.encoding", "Cp866"); System.setOut (new CodepagePrintStream (System.out, consoleEnc)); System.setErr (new CodepagePrintStream (System.err, consoleEnc)); ) catch (UnsupportedEncodingException e) (System.out.println ( "Unable to setup console codepage:" + e);) ...
Вихідні тексти класу CodepagePrintStream Ви можете знайти на даному сайті: CodepagePrintStream.java.
Якщо Ви самі конструюєте формат даних, я рекомендую Вам використовувати одну з мультибайтних символів. Найзручніше зазвичай формат UTF8 - перші 128 значень (ASCII) в ньому кодуються одним байтом, що часто може значно зменшити загальний обсяг даних (не дарма це кодування прийнята за основу в світі XML). Але у UTF8 є один недолік - кількість необхідних байтів залежить від коду символів. Там, де це критично можна використовувати один з двобайтових форматів Unicode (UnicodeBig або UnicodeLittle).
Бази даних
Для того, щоб прочитати коректно символи з БД, зазвичай досить вказати JDBC-драйвера на потрібне кодування символів в БД. Як саме - це залежить від конкретного драйвера. Зараз вже багато драйвера підтримують подібну настройку, на відміну від недавнього минулого. Далі наведено кілька відомих мені прикладів.
Міст JDBC-ODBC
Це один з найбільш часто використовуваних драйверів. Міст з JDK 1.2 і старше можна легко налаштувати на потрібну кодування. Це робиться додаванням додаткового властивості charSet в набір параметрів, що передаються для відкриття з'єднання з базою. За замовчуванням використовується file.encoding. Робиться це приблизно так:
// Встановлюємо з'єднання
Драйвер JDBC-OCI від Oracle 8.0.5 під Linux
При отриманні даних з БД, даний драйвер визначає "свою" кодування за допомогою змінної оточення NLS_LANG. Якщо ця змінна не знайдена, то він вважає що кодування - ISO-8859-1. Весь фокус у тому, що NLS_LANG повинна бути саме змінної оточення (яка встановлюється командою set), а не системна властивість Java (типу file.encoding). У разі використання драйвера всередині servlet engine Apache + Jserv, змінну оточення можна задати у файлі jserv.properties:
wrapper.env = NLS_LANG = American_America.CL8KOI8RІнформацію про це надіслав Сергій Безруков, за що йому окреме спасибі.
Драйвер JDBC для роботи з DBF (zyh.sql.dbf.DBFDriver)
Драйвер тільки недавно навчився працювати з російськими літерами. Хоч він і повідомляє по getPropertyInfo () що він розуміє властивість charSet - це фікція (принаймні у версії від 30.07.2001). Реально ж налаштувати кодування можна установкою властивості CODEPAGEID. Для російських символів там доступні два значення - "66" для Cp866 і "C9" для Cp1251. приклад:
// Параметри з'єднання з базою Properties connInfo = new Properties (); connInfo.put ( "CODEPAGEID", "66"); // Кодування Cp866 // Встановлюємо з'єднання Connection db = DriverManager.getConnection ( "jdbc: DBF: / C: / MyDBFFiles", connInfo);Якщо у Вас DBF-файли формату FoxPro, то у них своя специфіка. Справа в тому, що FoxPro зберігає в заголовку файлу ID кодової сторінки (байт зі зміщенням 0x1D), яка використовувалася при створенні DBF-ки. При відкритті таблиці драйвер використовує значення з заголовка, а не параметр "CODEPAGEID" (параметр в цьому випадку використовується тільки при створенні нових таблиць). Відповідно, щоб все працювало нормально, DBF-файл повинен бути створений з правильним кодуванням - інакше будуть проблеми.
MySQL (org.gjt.mm.mysql.Driver)
З цим драйвером теж все досить просто:
// Параметри з'єднання з базою Properties connInfo = new Poperties (); connInfo.put ( "user", user); connInfo.put ( "password", pass); connInfo.put ( "useUnicode", "true"); connInfo.put ( "characterEncoding", "KOI8_R"); Connection conn = DriverManager.getConnection (dbURL, props);
InterBase (interbase.interclient.Driver)
Для цього драйвера працює параметр "charSet":// Параметри з'єднання з базою Properties connInfo = new Properties (); connInfo.put ( "user", username); connInfo.put ( "password", password); connInfo.put ( "charSet", "Cp1251"); // Встановлюємо з'єднання Connection db = DriverManager.getConnection (dataurl, connInfo);
Однак не забудьте при створенні БД і таблиць вказати кодування символів. Для російської мови можна використовувати значення "UNICODE_FSS" або "WIN1251". приклад:
CREATE DATABASE 'E: ProjectHoldingDataBaseHOLDING.GDB' PAGE_SIZE 4096 DEFAULT CHARACTER SET UNICODE_FSS; CREATE TABLE RUSSIAN_WORD ( "NAME1" VARCHAR (40) CHARACTER SET UNICODE_FSS NOT NULL, "NAME2" VARCHAR (40) CHARACTER SET WIN1251 NOT NULL, PRIMARY KEY ( "NAME1"));
У версії 2.01 InterClient присутній помилка - класи ресурсів з повідомленнями для російської мови там неправильно скомпільовані. Швидше за все розробники просто забули вказати кодування початкових кодів при компіляції. Є два шляхи виправлення цієї помилки:
- Використовувати interclient-core.jar замість interclient.jar. При цьому російських ресурсів просто не буде, і автоматом підхопили англійські.
- Перекомпіліровать файли в нормальний Unicode. Розбір class-файлів - справа невдячна, тому краще скористатися JAD-му. На жаль JAD, якщо зустрічає символи з набору ISO-8859-1, виводить їх в 8-ірічной кодуванні, так що скористатися стандартним перекодировщик native2ascii не вдасться - доведеться написати свій (програма Decode). Якщо Вам не хочеться морочитися з цими проблемами - можете просто взяти готовий файл з ресурсами (пропатченний jar з драйвером - interclient.jar, окремі класи ресурсів - interclient-rus.jar).
Але навіть налаштувавши JDBC-драйвер на потрібне кодування в деяких випадках можна нарватися на неприємності. Наприклад, при спробі використання нових чудових скроліруемих курсорів стандарту JDBC 2 в мосту JDBC-ODBC з JDK 1.3.x досить швидко можна виявити, що російські букви там просто не працюють (метод updateString ()).
З цією помилкою пов'язана невелика історія. Коли я вперше її побачив (під JDK 1.3 rc2), я зарегистр її на BugParade (). Коли вийшла перша бета JDK 1.3.1, цю помилку позначили як пофіксеную. Зраділий, я скачав цю бету, запустив тест - не працює. Написав Sun-вівцям про це - у відповідь мені написали, що фікс буде включений в наступні релізи. Ну ладно, подумав я, почекаємо. Час минав, вийшов реліз 1.3.1, а потім і бета 1.4. Нарешті я знайшов час перевірити - знову не працює. Мати, мати, мати ... - звично відгукнулося відлуння. Після гнівного листа в Sun вони завели нову помилку (), яку віддали на розтерзання індійському філії. Індуси пошаманити над кодом, і заявили, що в 1.4 beta3 все виправлено. Скачав я цю версію, запустив під ним тестовий приклад, ось результат -. Як виявилося, та beta3, яку роздають на сайті (build 84) це не та beta3, куди був включений остаточний фікс (build 87). Тепер обіцяють, що виправлення увійде в 1.4 rc1 ... Ну, загалом, Ви розумієте :-)
Російські літери в исходниках Java-програм
Як уже згадувалося, при виконанні програми використовується Unicode. Вихідні ж файли пишуться в звичайних редакторах. Я користуюся Far-му, у Вас, напевно, є свій улюблений редактор. Ці редактори зберігають файли в 8-бітовому форматі, а значить, що до цих файлів також застосовні міркування, аналогічні наведеним вище. Різні версії компіляторів трохи по різному виконують перетворення символів. У ранніх версіях JDK 1.1.x використовується настройка file.encoding, яку можна поміняти за допомогою нестандартної опції -J. У новіших (як повідомив Денис Кокарев - починаючи з 1.1.4) був введений додатковий параметр -encoding, за допомогою якого можна вказати використовувану кодування. У скомпільованих класах рядки представлені у вигляді Unicode (точніше в модифікованому варіанті формату UTF8), так що найцікавіше відбувається при компіляції. Тому, найголовніше - з'ясувати, в якому кодуванні у Вас вихідні коди і вказати правильне значення при компіляції. За замовчуванням буде використаний все той же горезвісний file.encoding. Приклад виклику компілятора:
Крім використання цієї настройки є ще один метод - вказувати букви в форматі "uXXXX", де вказується код символу. Цей метод працює з усіма версіями, а для отримання цих кодів можна використовувати стандартну утиліту.
Якщо Ви користуєтеся яким-небудь IDE, то у нього можуть бути свої глюки. Найчастіше ці IDE користуються для читання / збереження початкових кодів кодування за замовчуванням - так що звертайте увагу на регіональні настройки своєї ОС. Крім цього можуть бути і явні помилки - наприклад досить непогана IDE-шка CodeGuide погано перетравлює заголовну російську літеру "Т". Вбудований аналізатор коду приймає цю букву за прямі подвійні лапки, що призводить до того, що коректний код сприймається як помилковий. Боротися з цим можна (заміною літери "Т" на її код "u0422"), але неприємно. Судячи з усього десь всередині парсеру застосовується явне перетворення символів в байти (типу: byte b = (byte) c), тому замість коду 0x0422 (код букви "Т") виходить код 0x22 (код подвійної лапки).
Інша проблема зустрічається у JBuilder, але вона більше пов'язана з ергономікою. Справа в тому, що в JDK 1.3.0, під яким за замовчуванням працює JBuilder, є бага (), через яку новостворювані вікна GUI при активізації автоматично включають розкладку клавіатури в залежності від регіональних налаштувань ОС. Тобто якщо у Вас російські регіональні настройки, то він буде постійно намагатися переключитися на російську розкладку, що при написанні програм моторошно заважає. На сайті JBuilder.ru є парочка патчіков які змінюють поточну локаль в JVM на Locale.US, але найкращий спосіб - перейти на JDK 1.3.1, в якому дана бага пофиксил.
Початківці користувачі JBuilder можуть також зустрітися з такою проблемою - російські літери зберігаються у вигляді кодів "uXXXX". Щоб цього уникнути, треба в діалозі Default Project Properties, закладка General, в поле Encoding поміняти Default на Cp1251.
Якщо Ви використовуєте для компіляції не стандартний javac, а інший компілятор - зверніть увагу на те, як він виконує перетворення символів. Наприклад, деякі версії IBM-івського компілятора jikes не розуміють, що бувають кодування, відмінні від ISO-8859-1 :-). Існують версії, пропатченний на цей рахунок, але часто там теж зашивається деяка кодування - немає такого зручності, як в javac.
JavaDoc
Для генерації HTML-документації по ісходникам використовується утиліта javadoc, що входить в стандартну поставку JDK. Для вказівки кодувань у неї є аж 3 параметра:
- -encoding - Ця установка задає кодування початкових кодів. За замовчуванням використовується file.encoding.
- -docencoding - Ця установка задає кодування генеруються HTML-файлів. За замовчуванням використовується file.encoding.
- -charset - Ця установка задає кодування, яка буде записуватися в заголовки генеруються HTML-файлів ( ). Очевидно, що вона повинна збігатися з попередньою настройкою. Якщо цей параметр опущена, то тег meta додаватися не буде.
Російські літери в файлах properties
Для читання файлів properties використовуються методи завантаження ресурсів, які працюють специфічним чином. Власне для читання використовується метод Properties.load, який не використовує file.encoding (там в исходниках жорстко вказано кодування ISO-8859-1), тому єдиний спосіб вказати російські літери - використовувати формат "uXXXX" і утиліту.
Метод Properties.save працює по різному в версіях JDK 1.1 і 1.2. У версіях 1.1 він просто відкидав старший байт, тому правильно працював тільки з англицкий буквами. В 1.2 робиться зворотне перетворення в "uXXXX", так що він працює дзеркально до методу load.
Якщо файли properties у Вас завантажуються не як ресурси, а як звичайні файли конфігурації, і Вас не влаштовує така поведінка - вихід один, написати власний завантажувач.
Російські літери в Servlet-ах.
Ну, для чого ці самі Servlet-и потрібні, я думаю, Ви в курсі. Якщо ні - то краще спочатку прочитати документацію. Тут же розповідається тільки про особливості роботи з російськими буквами.
Так в чому ж особливості? Коли Servlet посилає відповідь клієнту, є два способи послати цю відповідь - через OutputStream (метод getOutputStream ()) або через PrintWriter (метод getWriter ()). У першому випадку Ви записуєте масиви байтів, тому застосовні вищеописані методи записи в потоки. У разі ж PrintWriter, він використовує встановлену кодування. У будь-якому випадку необхідно правильно вказати використовувану кодування при виклику методу setContentType (), для того, щоб було правильне перетворення символів на стороні сервера. Ця вказівка має бути зроблено перед викликом getWriter () або перед першим записом в OutputStream. приклад:
// Установка кодування для результатів запиту // Врахуйте, що деякі engine не допускають // наявність прогалини між ';' і 'charset' response.setContentType ( "text / html; charset = UTF-8"); PrintWriter out = response.getWriter (); // Налагоджувальний висновок назви кодування для перевірки out.println ( "Encoding:" + response.getCharacterEncoding ()); ... out.close (); )
Це з приводу віддачі відповідей клієнту. З вхідними параметрами, на жаль не так просто. Вхідні параметри кодуються оглядачем побайтно відповідно до MIME-типом "application / x-www-form-urlencoded". Як розповів Олексій Мендель російські літери браузери кодують, використовуючи поточну встановлену кодування. Ну і, зрозуміло, нічого про неї не повідомляють. Відповідно, наприклад, в JSDK версій від 2.0 до 2.2 це ніяк не перевіряється, а то, що за кодування буде використана для перетворення - залежить від використовуваного engine. Починаючи з специфікації 2.3 з'явилася можливість встановлювати кодування для javax.servlet.ServletRequest - метод setCharacterEncoding (). Цю специфікацію вже підтримують останні версії Resin і Tomcat.
Таким чином, якщо Вам пощастило, і у Вас стоїть сервер з підтримкою Servlet 2.3, то все досить просто:
public void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException ( // Кодування повідомлень request.setCharacterEncoding ( "Cp1251"); String value = request.getParameter ( "value"); ...
У застосуванні методу request.setCharacterEncoding () є одна істотна тонкість - він повинен бути застосований до першого звернення до запиту за даними (наприклад request.getParameter ()). Якщо Ви використовуєте фільтри, які обробляють запит до того як він приходить в сервлет, є ненульова ймовірність того, що в одному з фільтрів може статися читання якого-небудь параметра із запиту (наприклад для авторизації) і request.setCharacterEncoding () в сервлет не спрацює .
Тому ідеологічно більш правильно написати фільтр, який встановлює кодування запиту. При цьому він повинен стояти першим в ланцюжку фільтрів в web.xml.
Приклад такого фільтра:
import java.io. *; import java.util. *; import javax.servlet. *; import javax.servlet.http. *; public class CharsetFilter implements Filter (// кодування private String encoding; public void init (FilterConfig config) throws ServletException ( // читаємо з конфігурації encoding = config.getInitParameter ( "requestEncoding"); // якщо не встановлена - встановлюємо Cp1251 if (encoding == null) encoding = "Cp1251"; ) Public void doFilter (ServletRequest request, ServletResponse response, FilterChain next) throws IOException, ServletException (request.setCharacterEncoding (encoding); next.doFilter (request, response);) public void destroy () ())
І його конфігурації в web.xml:
Charset Filter CharsetFilter Charset Filter /*
Якщо ж Вам не пощастило, і у Вас більше стара версія - для досягнення результату доведеться поізвращаться:
-
FOP
Пакет FOP призначений для обробки документів за стандартом XSL FO (Formating Objects). Зокрема він дозволяє створювати PDF-документи на базі документів XML. Для перетворення з вихідного XML в FO пакет FOP за замовчуванням використовує XSLT-процесор Xalan в парі з Xerces. Для створення підсумкового зображення в FOP необхідно підключити шрифти, що підтримують російські букви. Ось як можна зробити це для версії 0.20.1:
- В підкаталог conffonts (наприклад, в c: fop-0.20.1conffonts) скопіювати файли ttf з системного каталогу Windows. Для Arial normal / normal, normal / bold, italic / normal і italic / bold потрібні файли arial.ttf, arialbd.ttf, ariali.ttf і arialbi.ttf.
- Згенерувати файли описів шрифтів (типу arial.xml). Для цього для кожного шрифту потрібно виконати команду (це для Arial normal / normal, все в один рядок):
java -cp.; c: fop-0.20.1uildfop.jar; c: fop-0.20.1libatik.jar; c: fop-0.20.1libxalan-2.0.0.jar; c: fop-0.20.1libxerces.jar; c: fop-0.20.1libjimi-1.0.jar org.apache.fop.fonts.apps.TTFReader fontsarial.ttf fontsarial.xml
- У FOP додати в conf / userconfig.xml опис шрифту з російськими буквами, типу:
- При виклику FOP з командного рядка після org.apache.fop.apps.Fop писати -c c: fop-0.20.1confuserconfig.xml Якщо потрібно використовувати FOP з сервлета, то потрібно в сервлет після рядка
Driver driver = new Driver ();
додати рядки:// Каталог fonts (c: weblogicfonts) був створений виключно для зручності. String userConfig = "fonts / userconfig.xml"; File userConfigFile = new File (userConfig); Options options = new Options (userConfigFile);
Тоді розташування файлів ttf у файлі userconfig.xml можна вказати щодо кореня сервера додатки, без вказівки абсолютного шляху: - У файлі FO (або XML і XSL) перед використанням шрифту писати:
font-family = "Arial" font-weight = "bold" (Якщо використовується Arial bold) font-style = "italic" (Якщо використовується Arial italic)
Даний алгоритм надіслав Олексій Тюрін, за що йому окреме спасибі.
Якщо Ви використовуєте вбудований в FOP переглядач, то необхідно врахувати його особливості. Зокрема, хоча передбачається, що написи в ньому русифіковані, насправді зроблено це з помилкою (у версії 0.19.0). Для завантаження написів з файлів ресурсів в пакеті org.apache.fop.viewer.resources використовується власний завантажувач (клас org.apache.fop.viewer.LoadableProperties). Кодування читання там жорстко зафіксована (8859_1, як і в разі Properties.load ()), проте підтримка запису виду "uXXXX" не реалізована. Я повідомив про цю помилку розробникам, вони включили її виправлення в свої плани.
Крім усього іншого існує сайт присвячений русифікації FOP (http://www.openmechanics.org/rusfop/) Там Ви зможете знайти дистрибутив FOP з уже виправленими помилками і підключеними русскмі шрифтами.
CORBA
У стандарті CORBA передбачений тип, відповідний Java-івської типу String. Це тип wstring. Все б добре, але деякі CORBA-серверу не підтр
Оригінальний спосіб роботи з кодуваннями пропонує Russian Apache - розписано, як саме.
кодування
Коли я тільки починав програмувати на мові C, першою моєю програмою (не рахуючи HelloWorld) була програма перекодування текстових файлів з основної кодування ГОСТ-а (пам'ятаєте таку? :-) в альтернативну. Було це в далекому 1991-го року. З тих пір багато чого змінилося, але за минулі 10 років подібні програмки свою актуальність, на жаль, не втратили. Занадто багато вже накопичено даних в різноманітних кодуваннях і занадто багато використовується програм, які вміють працювати тільки з однією. Для російської мови існує не менше десятка різних кодувань, що робить проблему ще більш заплутаною.
Звідки ж узялися всі ці кодування і для чого вони потрібні? Комп'ютери за своєю природою можуть працювати тільки з числами. Для того щоб зберігати букви в пам'яті комп'ютера треба поставити у відповідність кожній букві якесь число (приблизно такий же принцип використовувався і до появи комп'ютерів - згадайте про ту ж азбуку Морзе). Причому число бажано поменше - чим менше двійкових розрядів буде задіяно, тим ефективніше можна буде використовувати пам'ять. Ось це відповідність набору символів і чисел власне і є кодування. Бажання будь-яку ціну зберегти пам'ять, а так само роз'єднаність різних груп комп'ютерників і призвела до нинішнього стану справ. Найпоширенішим способом кодування зараз є використання для одного символу одного байта (8 біт), що визначає загальна кількість символів в 256. Набір перших 128 символів стандартизований (набір ASCII) і є однаковими у всіх поширених кодуваннях (ті кодування, де це не так вже практично вийшли з ужитку). Англицкий букви і розділові знаки розташовуються знаходяться в цьому діапазоні, що і визначає їх вражаючу живучість в комп'ютерних системах :-). Інші мови знаходяться не в настільки щасливому становищі - їм усім доводиться тулитися в останніх 128 числах.
Unicode
В кінці 80-х багато хто усвідомив необхідність створення єдиного стандарту на кодування символів, що і призвело до появи Unicode. Unicode - це спроба раз і назавжди зафіксувати конкретне число за конкретним символом. Зрозуміло, що в 256 символів тут не вкластися при всьому бажанні. Досить довгий час здавалося, що вже 2-х то байт (65536 символів) повинно вистачити. Але ж ні - остання версія стандарту Unicode (3.1) визначає вже 94140 символів. Для такого кол-ва символів, напевно, вже доведеться використовувати 4 байти (4294967296 символів). Може бути і вистачить на деякий час ... :-)
У набір символів Unicode входять всілякі літери з усякими рисками і пріпендюлькамі, грецькі, математичні, ієрогліфи, символи псевдографіки і пр. І пр. У тому числі і так улюблені нами символи кирилиці (діапазон значень 0x0400-0x04ff). Так що з цього боку ніякої дискримінації немає.
Якщо Вам цікаві конкретні коду символів, для їх перегляду зручно використовувати програму "Таблиця символів" з WinNT. Ось, наприклад, діапазон кирилиці:
Якщо у Вас інша OS або Вас цікавить офіційне тлумачення, то повну розкладку символів (charts) можна знайти на офіційному сайті Unicode (http://www.unicode.org/charts/web.html).
Типи char і byte
В Java для символів виділений окремий тип даних char розміром у 2 байти. Це часто породжує плутанину в умах початківців (особливо якщо вони раніше програмували на інших мовах, наприклад на C / C ++). Справа в тому, що в більшості інших мов для обробки символів використовуються типи даних розміром в 1 байт. Наприклад, в C / C ++ тип char в більшості випадків використовується як для обробки символів, так і для обробки байтів - там немає поділу. В Java для байтів є свій тип - тип byte. Таким чином C-ішному char відповідає Java-вський byte, а Java-чортківському char зі світу C найближче тип wchar_t. Треба чітко розділяти поняття символів і байтів - інакше нерозуміння і проблеми гарантовані.
Java практично з самого свого народження використовує для кодування символів стандарт Unicode. Бібліотечні функції Java очікують побачити в змінних типу char символи, представлені кодами Unicode. В принципі, Ви, звичайно, можете запхати туди що завгодно - цифри є цифри, процесор все стерпить, але при будь-якій обробці бібліотечні функції будуть діяти виходячи з припущення що їм передали кодування Unicode. Так що можна спокійно думати, що у типу char кодування зафіксована. Але це всередині JVM. Коли дані читаються ззовні або передаються назовні, то вони можуть бути представлені тільки одним типом - типом byte. Всі інші типи конструюються з байтів в залежності від використовуваного формату даних. Ось тут то на сцену і виходять кодування - в Java це просто формат даних для передачі символів, який використовується для формування даних типу char. Для кожної кодової сторінки в бібліотеці є по 2 класу перекодування (ByteToChar і CharToByte). Класи ці лежать в пакеті sun.io. Якщо, при перекодуванні з char в byte не було знайдено відповідного символу, він замінюється на символ ?.
До речі, ці файли кодових сторінок в деяких ранніх версіях JDK 1.1 містять помилки, що викликають помилки перекодіровок, а то і взагалі виключення при виконанні. Наприклад, це стосується кодування KOI8_R. Найкраще, що можна при цьому зробити - змінити версію на більш пізню. Судячи з Sun-івської опису, більшість цих проблем було вирішено у версії JDK 1.1.6.
До появи версії JDK 1.4 набір доступних кодувань визначався тільки виробником JDK. Починаючи з 1.4 з'явилося нове API (пакет java.nio.charset), за допомогою якого Ви вже можете створити свою власну систему кодування (наприклад підтримати рідко використовувану, але моторошно необхідну саме Вам).
клас String
У більшості випадків для подання рядків в Java використовується об'єкт типу java.lang.String. Це звичайний клас, який всередині себе зберігає масив символів (char), і який містить багато корисних методів для маніпуляції символами. Найцікавіші - це конструктори, що мають першим параметром масив байтів (byte) і методи getBytes (). За допомогою цих методів Ви можете виконувати перетворення з масиву байтів в рядки і назад. Для того, щоб вказати яке кодування при цьому використовувати у цих методів є строковий параметр, який задає її ім'я. Ось, наприклад, як можна виконати перекодування байтів з КОИ-8 в Windows-1251:
// Дані в кодуванні ЯКІ-8 byte koi8Data = ...; // Перетворимо з ЯКІ-8 в Unicode String string = new String (koi8Data, "KOI8_R"); // Перетворимо з Unicode в Windows-1251 byte winData = string.getBytes ( "Cp1251");
Список 8-ми бітових кодувань, доступних в сучасних JDK і підтримують російські букви Ви можете знайти нижче, в розділі.
Так як кодування - це формат даних для символів, крім знайомих 8-ми бітових кодувань в Java також на рівних присутні і багатобайтові кодування. До таких відносяться UTF-8, UTF-16, Unicode і ін. Наприклад ось так можна отримати байти в форматі UnicodeLittleUnmarked (16-ти бітове кодування Unicode, молодший байт перший, без ознаки порядку байтів):
// Перетворимо з Unicode в UnicodeLittleUnmarked byte data = string.getBytes ( "UnicodeLittleUnmarked");
При подібних перетвореннях легко помилитися - якщо кодування байтових даних не соотвествуют вказаною параметру при перетворенні з byte в char, то перекодування буде виконано неправильно. Іноді після цього можна витягнути правильні символи, але частіше за все частина даних буде безповоротно втрачена.
У реальній програмі явно вказувати кодову сторінку не завжди зручно (хоча більше надійно). Для цього була введена кодування за замовчуванням. За умовчанням вона залежить від системи і її налаштувань (для російських виндов прийнята кодування Cp1251), і в старих JDK її можна змінити установкою системного властивості file.encoding. В JDK 1.3 зміна цієї налаштування іноді спрацьовує, іноді - ні. Викликано це наступним: спочатку file.encoding ставиться з регіональних налаштувань комп'ютера. Посилання на кодування за замовчуванням запам'ятовується в нутрії при першому перетворенні. При цьому використовується file.encoding, але це перетворення відбувається ще до використання аргументів запуску JVM (собсно, при їх розборі). Взагалі-то, як стверджують в Sun, ця властивість відображає системну кодування, і вона не повинна змінюватися в командному рядку (див., Наприклад, коментарі до BugID) Проте в JDK 1.4 Beta 2 зміна цієї настройки знову почала надавати ефект. Що це, свідома зміна або побічний ефект, який може знову зникнути - Sun-вівці чіткої відповіді поки не дали.
Ця кодування використовується тоді, коли явно не вказано назву сторінки. Про це треба завжди пам'ятати - Java не буде намагатися передбачити кодування байтів, які Ви передаєте для створення рядка String (так само вона не зможе прочитати Ваші думки з цього приводу :-). Вона просто використовує поточну кодування за замовчуванням. Оскільки ця настройка одна на все перетворення, іноді можна наштовхнутися на неприємності.
Для перетворення з байтів в символи і назад слід користуватися тільки цими методами. Просте приведення типу використовувати в більшості випадків не можна - кодування символів при цьому не буде враховуватися. Наприклад, однією з найпоширеніших помилок є читання даних побайтно за допомогою методу read () з InputStream, а потім приведення отриманого значення до типу char:
InputStream is = ..; int b; StringBuffer sb = new StringBuffer (); while ((b = is.read ())! = - 1) (sb.append ((char) b); // <- так делать нельзя ) String s = sb.toString ();
Зверніть увагу на приведення типу - "(char) b". Значення байтів замість перекодування просто скопійовано в char (діапазон значень 0-0xFF, а не той, де знаходиться кирилиця). Такому копіювання відповідає кодування ISO-8859-1 (яка один в один відповідає першим 256 значень Unicode), а значить, можна вважати, що цей код просто використовує її (замість тієї, в якій реально закодовані символи в оригінальних даних). Якщо Ви спробуєте відобразити отримане значення - на екрані будуть або вопросики або кракозябли. Наприклад, при читанні рядка "АБВ" в віндового кодуванні може запросто відобразитися щось на кшталт такого: "ÀÁÂ". Подібного роду код часто пишуть програмісти на заході - з англійськими літерами працює, і ладно. Виправити такий код легко - треба просто замінити StringBuffer на ByteArrayOutputStream:
InputStream is = ..; int b; ByteArrayOutputStream baos = new ByteArrayOutputStream (); while ((b = is.read ())! = - 1) (baos.write (b);) // Перекодування байтів на рядок з використанням кодування за замовчуванням String s = baos.toString (); // Якщо потрібна конкретна кодування - просто вкажіть її при виклику toString (): // // s = baos.toString ( "Cp1251");Більш докладно про поширені помилки дивіться розділ.
8-ми бітові кодування російських букв
Ось основні 8-ми бітові кодування російських букв, які поширені:
Крім основної назви можна використовувати синоніми. Набір їх може відрізнятися в різних версіях JDK. Ось список від JDK 1.3.1:
- Cp1251:
- Windows-1251
- Cp866:
- IBM866
- IBM-866
- CP866
- CSIBM866
- KOI8_R:
- KOI8-R
- CSKOI8R
- ISO8859_5:
- ISO8859-5
- ISO-8859-5
- ISO_8859-5
- ISO_8859-5: 1988
- ISO-IR-144
- 8859_5
- Cyrillic
- CSISOLatinCyrillic
- IBM915
- IBM-915
- Cp915
Причому синоніми, на відміну від основного імені нечутливі до регістру символів - така особливість реалізації.
Варто відзначити, що ці кодування на деяких JVM можуть бути відсутні. Наприклад, з сайту Sun можна завантажити дві різні версії JRE - US і International. У US версії присутня тільки мінімум - ISO-8859-1, ASCII, Cp1252, UTF8, UTF16 і кілька варіацій двухбайтового Unicode. Все інше є тільки в International варіанті. Іноді через це можна нарватися на граблі з запуском програми, навіть якщо їй не потрібні російські літери. Типова помилка, що виникає при цьому:
Error occurred during initialization of VM java / lang / ClassNotFoundException: sun / io / ByteToCharCp1251
Виникає вона, як не важко здогадатися, через те, що JVM, виходячи з російських регіональних налаштувань намагається встановити кодування за замовчуванням в Cp1251, але, тому що клас підтримки такий відсутній в US версії, закономірно обламується.
Файли і потоки даних
Так само як і байти концептуально відокремлені від символів, в Java розрізняються потоки байтів і потоки символів. Роботу з байтами представляють класи, які прямо або побічно успадковують класи InputStream або OutputStream (плюс клас-унікум RandomAccessFile). Роботу з символами представляє солодка парочка класів Reader / Writer (і їх спадкоємці, зрозуміло).
Для читання / записі не перетворених байтів використовуються потоки байтів. Якщо відомо, що байти представляють собою тільки символи в деякій кодуванні, можна використовувати спеціальні класи-перетворювачі InputStreamReader і OutputStreamWriter, щоб отримати потік символів і працювати безпосередньо з ним. Зазвичай це зручно в разі звичайних текстових файлів або при роботі з багатьма мережевими протоколами Internet. Кодування символів при цьому вказується в конструкторі класу-перетворювача. приклад:
// Рядок Unicode String string = "..."; // Записуємо рядок в текстовий файл в кодуванні Cp866 PrintWriter pw = new PrintWriter // клас з методами записи рядків (New OutputStreamWriter // клас-перетворювач (New FileOutputStream ( "file.txt"), "Cp866"); pw.println (string); // записуємо рядок в файл pw.close ();
Якщо в потоці можуть бути присутніми дані в різних кодуваннях або ж символи перемішані з іншими двійковими даними, то краще читати і записувати масиви байтів (byte), а для перекодування використовувати вже згадані методи класу String. приклад:
// Рядок Unicode String string = "..."; // Записуємо рядок в текстовий файл в двох кодуваннях (Cp866 і Cp1251) OutputStream os = new FileOutputStream ( "file.txt"); // клас записи байтів в файл // Записуємо рядок в кодуванні Cp866 os.write (string.getBytes ( "Cp866")); // Записуємо рядок в кодуванні Cp1251 os.write (string.getBytes ( "Cp1251")); os.close ();
Консоль в Java традиційно представлена потоками, але, на жаль, не символів, а байтів. Справа в тому, що потоки символів з'явилися тільки в JDK 1.1 (разом з усім механізмом кодувань), а доступ до консольного вводу / виводу проектувався ще в JDK 1.0, що і призвело до появи виродка у вигляді класу PrintStream. Цей клас використовується в змінних System.out і System.err, які власне і дають доступ до висновку на консоль. За всіма ознаками це потік байтів, але з купою методів запису рядків. Коли Ви записуєте в нього рядок, всередині відбувається конвертація в байти з використанням кодування за замовчуванням, що в разі виндов, як правило, є неприйнятним - кодування за замовчуванням буде Cp1251 (Ansi), а для консольного вікна зазвичай потрібно використовувати Cp866 (OEM). Ця помилка була зареєстрована ще в 97-му році () але Sun-вівці виправляти її начебто не поспішають. Так як методу установки кодування в PrintStream немає, для вирішення цієї проблеми можна підмінити стандартний клас на власний за допомогою методів System.setOut () і System.setErr (). Ось, наприклад, звичайне початок в моїх програмах:
... public static void main (String args) ( // Установка виведення консольних повідомлень із належним кодуванням try (String consoleEnc = System.getProperty ( "console.encoding", "Cp866"); System.setOut (new CodepagePrintStream (System.out, consoleEnc)); System.setErr (new CodepagePrintStream (System.err, consoleEnc)); ) catch (UnsupportedEncodingException e) (System.out.println ( "Unable to setup console codepage:" + e);) ...
Вихідні тексти класу CodepagePrintStream Ви можете знайти на даному сайті: CodepagePrintStream.java.
Якщо Ви самі конструюєте формат даних, я рекомендую Вам використовувати одну з мультибайтних символів. Найзручніше зазвичай формат UTF8 - перші 128 значень (ASCII) в ньому кодуються одним байтом, що часто може значно зменшити загальний обсяг даних (не дарма це кодування прийнята за основу в світі XML). Але у UTF8 є один недолік - кількість необхідних байтів залежить від коду символів. Там, де це критично можна використовувати один з двобайтових форматів Unicode (UnicodeBig або UnicodeLittle).
Бази даних
Для того, щоб прочитати коректно символи з БД, зазвичай досить вказати JDBC-драйвера на потрібне кодування символів в БД. Як саме - це залежить від конкретного драйвера. Зараз вже багато драйвера підтримують подібну настройку, на відміну від недавнього минулого. Далі наведено кілька відомих мені прикладів.
Міст JDBC-ODBC
Це один з найбільш часто використовуваних драйверів. Міст з JDK 1.2 і старше можна легко налаштувати на потрібну кодування. Це робиться додаванням додаткового властивості charSet в набір параметрів, що передаються для відкриття з'єднання з базою. За замовчуванням використовується file.encoding. Робиться це приблизно так:
// Встановлюємо з'єднання
Драйвер JDBC-OCI від Oracle 8.0.5 під Linux
При отриманні даних з БД, даний драйвер визначає "свою" кодування за допомогою змінної оточення NLS_LANG. Якщо ця змінна не знайдена, то він вважає що кодування - ISO-8859-1. Весь фокус у тому, що NLS_LANG повинна бути саме змінної оточення (яка встановлюється командою set), а не системна властивість Java (типу file.encoding). У разі використання драйвера всередині servlet engine Apache + Jserv, змінну оточення можна задати у файлі jserv.properties:
wrapper.env = NLS_LANG = American_America.CL8KOI8RІнформацію про це надіслав Сергій Безруков, за що йому окреме спасибі.
Драйвер JDBC для роботи з DBF (zyh.sql.dbf.DBFDriver)
Драйвер тільки недавно навчився працювати з російськими буквами. Хоч він і повідомляє по getPropertyInfo () що він розуміє властивість charSet - це фікція (принаймні у версії від 30.07.2001). Реально ж налаштувати кодування можна установкою властивості CODEPAGEID. Для російських символів там доступні два значення - "66" для Cp866 і "C9" для Cp1251. приклад:
// Параметри з'єднання з базою Properties connInfo = new Properties (); connInfo.put ( "CODEPAGEID", "66"); // Кодування Cp866 // Встановлюємо з'єднання Connection db = DriverManager.getConnection ( "jdbc: DBF: / C: / MyDBFFiles", connInfo);Якщо у Вас DBF-файли формату FoxPro, то у них своя специфіка. Справа в тому, що FoxPro зберігає в заголовку файлу ID кодової сторінки (байт зі зміщенням 0x1D), яка використовувалася при створенні DBF-ки. При відкритті таблиці драйвер використовує значення з заголовка, а не параметр "CODEPAGEID" (параметр в цьому випадку використовується тільки при створенні нових таблиць). Відповідно, щоб все працювало нормально, DBF-файл повинен бути створений з правильним кодуванням - інакше будуть проблеми.
MySQL (org.gjt.mm.mysql.Driver)
З цим драйвером теж все досить просто:
// Параметри з'єднання з базою Properties connInfo = new Poperties (); connInfo.put ( "user", user); connInfo.put ( "password", pass); connInfo.put ( "useUnicode", "true"); connInfo.put ( "characterEncoding", "KOI8_R"); Connection conn = DriverManager.getConnection (dbURL, props);
InterBase (interbase.interclient.Driver)
Для цього драйвера працює параметр "charSet":// Параметри з'єднання з базою Properties connInfo = new Properties (); connInfo.put ( "user", username); connInfo.put ( "password", password); connInfo.put ( "charSet", "Cp1251"); // Встановлюємо з'єднання Connection db = DriverManager.getConnection (dataurl, connInfo);
Однак не забудьте при створенні БД і таблиць вказати кодування символів. Для російської мови можна використовувати значення "UNICODE_FSS" або "WIN1251". приклад:
CREATE DATABASE "E: \\ ProjectHolding \\ DataBase \\ HOLDING.GDB" PAGE_SIZE 4096 DEFAULT CHARACTER SET UNICODE_FSS; CREATE TABLE RUSSIAN_WORD ( "NAME1" VARCHAR (40) CHARACTER SET UNICODE_FSS NOT NULL, "NAME2" VARCHAR (40) CHARACTER SET WIN1251 NOT NULL, PRIMARY KEY ( "NAME1"));
У версії 2.01 InterClient присутній помилка - класи ресурсів з повідомленнями для російської мови там неправильно скомпільовані. Швидше за все розробники просто забули вказати кодування початкових кодів при компіляції. Є два шляхи виправлення цієї помилки:
- Використовувати interclient-core.jar замість interclient.jar. При цьому російських ресурсів просто не буде, і автоматом підхопили англійські.
- Перекомпіліровать файли в нормальний Unicode. Розбір class-файлів - справа невдячна, тому краще скористатися JAD-му. На жаль JAD, якщо зустрічає символи з набору ISO-8859-1, виводить їх в 8-ірічной кодуванні, так що скористатися стандартним перекодировщик native2ascii не вдасться - доведеться написати свій (програма Decode). Якщо Вам не хочеться морочитися з цими проблемами - можете просто взяти готовий файл з ресурсами (пропатченний jar з драйвером - interclient.jar, окремі класи ресурсів - interclient-rus.jar).
Але навіть налаштувавши JDBC-драйвер на потрібне кодування в деяких випадках можна нарватися на неприємності. Наприклад, при спробі використання нових чудових скроліруемих курсорів стандарту JDBC 2 в мосту JDBC-ODBC з JDK 1.3.x досить швидко можна виявити, що російські букви там просто не працюють (метод updateString ()).
З цією помилкою пов'язана невелика історія. Коли я вперше її побачив (під JDK 1.3 rc2), я зарегистр її на BugParade (). Коли вийшла перша бета JDK 1.3.1, цю помилку позначили як пофіксеную. Зраділий, я скачав цю бету, запустив тест - не працює. Написав Sun-вівцям про це - у відповідь мені написали, що фікс буде включений в наступні релізи. Ну ладно, подумав я, почекаємо. Час минав, вийшов реліз 1.3.1, а потім і бета 1.4. Нарешті я знайшов час перевірити - знову не працює. Мати, мати, мати ... - звично відгукнулося відлуння. Після гнівного листа в Sun вони завели нову помилку (), яку віддали на розтерзання індійському філії. Індуси пошаманити над кодом, і заявили, що в 1.4 beta3 все виправлено. Скачав я цю версію, запустив під ним тестовий приклад, ось результат -. Як виявилося, та beta3, яку роздають на сайті (build 84) це не та beta3, куди був включений остаточний фікс (build 87). Тепер обіцяють, що виправлення увійде в 1.4 rc1 ... Ну, загалом, Ви розумієте :-)
Російські літери в исходниках Java-програм
Як уже згадувалося, при виконанні програми використовується Unicode. Вихідні ж файли пишуться в звичайних редакторах. Я користуюся Far-му, у Вас, напевно, є свій улюблений редактор. Ці редактори зберігають файли в 8-бітовому форматі, а значить, що до цих файлів також застосовні міркування, аналогічні наведеним вище. Різні версії компіляторів трохи по різному виконують перетворення символів. У ранніх версіях JDK 1.1.x використовується настройка file.encoding, яку можна поміняти за допомогою нестандартної опції -J. У новіших (як повідомив Денис Кокарев - починаючи з 1.1.4) був введений додатковий параметр -encoding, за допомогою якого можна вказати використовувану кодування. У скомпільованих класах рядки представлені у вигляді Unicode (точніше в модифікованому варіанті формату UTF8), так що найцікавіше відбувається при компіляції. Тому, найголовніше - з'ясувати, в якому кодуванні у Вас вихідні коди і вказати правильне значення при компіляції. За замовчуванням буде використаний все той же горезвісний file.encoding. Приклад виклику компілятора:
Крім використання цієї настройки є ще один метод - вказувати букви в форматі "\\ uXXXX", де вказується код символу. Цей метод працює з усіма версіями, а для отримання цих кодів можна використовувати стандартну утиліту.
Якщо Ви користуєтеся яким-небудь IDE, то у нього можуть бути свої глюки. Найчастіше ці IDE користуються для читання / збереження початкових кодів кодування за замовчуванням - так що звертайте увагу на регіональні настройки своєї ОС. Крім цього можуть бути і явні помилки - наприклад досить непогана IDE-шка CodeGuide погано перетравлює заголовну російську літеру "Т". Вбудований аналізатор коду приймає цю букву за прямі подвійні лапки, що призводить до того, що коректний код сприймається як помилковий. Боротися з цим можна (заміною літери "Т" на її код "\\ u0422"), але неприємно. Судячи з усього десь всередині парсеру застосовується явне перетворення символів в байти (типу: byte b = (byte) c), тому замість коду 0x0422 (код букви "Т") виходить код 0x22 (код подвійної лапки).
Інша проблема зустрічається у JBuilder, але вона більше пов'язана з ергономікою. Справа в тому, що в JDK 1.3.0, під яким за замовчуванням працює JBuilder, є бага (), через яку новостворювані вікна GUI при активізації автоматично включають розкладку клавіатури в залежності від регіональних налаштувань ОС. Тобто якщо у Вас російські регіональні настройки, то він буде постійно намагатися переключитися на російську розкладку, що при написанні програм моторошно заважає. На сайті JBuilder.ru є парочка патчіков які змінюють поточну локаль в JVM на Locale.US, але найкращий спосіб - перейти на JDK 1.3.1, в якому дана бага пофиксил.
Початківці користувачі JBuilder можуть також зустрітися з такою проблемою - російські літери зберігаються у вигляді кодів "\\ uXXXX". Щоб цього уникнути, треба в діалозі Default Project Properties, закладка General, в поле Encoding поміняти Default на Cp1251.
Якщо Ви використовуєте для компіляції не стандартний javac, а інший компілятор - зверніть увагу на те, як він виконує перетворення символів. Наприклад, деякі версії IBM-івського компілятора jikes не розуміють, що бувають кодування, відмінні від ISO-8859-1 :-). Існують версії, пропатченний на цей рахунок, але часто там теж зашивається деяка кодування - немає такого зручності, як в javac.
JavaDoc
Для генерації HTML-документації по ісходникам використовується утиліта javadoc, що входить в стандартну поставку JDK. Для вказівки кодувань у неї є аж 3 параметра:
- -encoding - Ця установка задає кодування початкових кодів. За замовчуванням використовується file.encoding.
- -docencoding - Ця установка задає кодування генеруються HTML-файлів. За замовчуванням використовується file.encoding.
- -charset - Ця установка задає кодування, яка буде записуватися в заголовки генеруються HTML-файлів ( ). Очевидно, що вона повинна збігатися з попередньою настройкою. Якщо цей параметр опущена, то тег meta додаватися не буде.
Російські літери в файлах properties
Для читання файлів properties використовуються методи завантаження ресурсів, які працюють специфічним чином. Власне для читання використовується метод Properties.load, який не використовує file.encoding (там в исходниках жорстко вказано кодування ISO-8859-1), тому єдиний спосіб вказати російські літери - використовувати формат "\\ uXXXX" і утиліту.
Метод Properties.save працює по різному в версіях JDK 1.1 і 1.2. У версіях 1.1 він просто відкидав старший байт, тому правильно працював тільки з англицкий буквами. В 1.2 робиться зворотне перетворення в "\\ uXXXX", так що він працює дзеркально до методу load.
Якщо файли properties у Вас завантажуються не як ресурси, а як звичайні файли конфігурації, і Вас не влаштовує така поведінка - вихід один, написати власний завантажувач.
Російські літери в Servlet-ах.
Ну, для чого ці самі Servlet-и потрібні, я думаю, Ви в курсі. Якщо ні - то краще спочатку прочитати документацію. Тут же розповідається тільки про особливості роботи з російськими буквами.
Так в чому ж особливості? Коли Servlet посилає відповідь клієнту, є два способи послати цю відповідь - через OutputStream (метод getOutputStream ()) або через PrintWriter (метод getWriter ()). У першому випадку Ви записуєте масиви байтів, тому застосовні вищеописані методи записи в потоки. У разі ж PrintWriter, він використовує встановлену кодування. У будь-якому випадку необхідно правильно вказати використовувану кодування при виклику методу setContentType (), для того, щоб було правильне перетворення символів на стороні сервера. Ця вказівка має бути зроблено перед викликом getWriter () або перед першим записом в OutputStream. приклад:
// Установка кодування для результатів запиту // Врахуйте, що деякі engine не допускають // наявність прогалини між ";" і "charset" response.setContentType ( "text / html; charset = UTF-8"); PrintWriter out = response.getWriter (); // Налагоджувальний висновок назви кодування для перевірки out.println ( "Encoding:" + response.getCharacterEncoding ()); ... out.close (); )
Це з приводу віддачі відповідей клієнту. З вхідними параметрами, на жаль не так просто. Вхідні параметри кодуються оглядачем побайтно відповідно до MIME-типом "application / x-www-form-urlencoded". Як розповів Олексій Мендель російські літери браузери кодують, використовуючи поточну встановлену кодування. Ну і, зрозуміло, нічого про неї не повідомляють. Відповідно, наприклад, в JSDK версій від 2.0 до 2.2 це ніяк не перевіряється, а то, що за кодування буде використана для перетворення - залежить від використовуваного engine. Починаючи з специфікації 2.3 з'явилася можливість встановлювати кодування для javax.servlet.ServletRequest - метод setCharacterEncoding (). Цю специфікацію вже підтримують останні версії Resin і Tomcat.
Таким чином, якщо Вам пощастило, і у Вас стоїть сервер з підтримкою Servlet 2.3, то все досить просто:
public void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException ( // Кодування повідомлень request.setCharacterEncoding ( "Cp1251"); String value = request.getParameter ( "value"); ...
У застосуванні методу request.setCharacterEncoding () є одна істотна тонкість - він повинен бути застосований до першого звернення до запиту за даними (наприклад request.getParameter ()). Якщо Ви використовуєте фільтри, які обробляють запит до того як він приходить в сервлет, є ненульова ймовірність того, що в одному з фільтрів може статися читання якого-небудь параметра із запиту (наприклад для авторизації) і request.setCharacterEncoding () в сервлет не спрацює .
Тому ідеологічно більш правильно написати фільтр, який встановлює кодування запиту. При цьому він повинен стояти першим в ланцюжку фільтрів в web.xml.
Приклад такого фільтра:
import java.io. *; import java.util. *; import javax.servlet. *; import javax.servlet.http. *; public class CharsetFilter implements Filter (// кодування private String encoding; public void init (FilterConfig config) throws ServletException ( // читаємо з конфігурації encoding = config.getInitParameter ( "requestEncoding"); // якщо не встановлена - встановлюємо Cp1251 if (encoding == null) encoding = "Cp1251"; ) Public void doFilter (ServletRequest request, ServletResponse response, FilterChain next) throws IOException, ServletException (request.setCharacterEncoding (encoding); next.doFilter (request, response);) public void destroy () ())
І його конфігурації в web.xml:
Charset Filter CharsetFilter Charset Filter /*
Якщо ж Вам не пощастило, і у Вас старіша версія - для досягнення результату доведеться поізвращаться:
-
FOP
Якщо програма не працює ніде - значить проблема тільки в ній і в Ваших руках. Уважно перечитайте все, що було написано вище, і шукайте. Якщо ж проблема проявляється тільки в конкретному оточенні - значить справа, можливо в налаштуваннях. Де саме - залежить від того, який графічною бібліотекою Ви користуєтеся. Якщо AWT - допомогти може правильна настройка файлу font.properties.ru. Приклад коректного файлу можна взяти з Java 2. Якщо у Вас немає цієї версії, можете завантажити його з даного сайту: версія для Windows, версія для Linux (див. Також нижче). Цей файл задає використовувані шрифти і кодові сторінки. Якщо у Вас встановлена російська версія OS - просто додайте цей файл туди, де лежить файл font.properties. Якщо ж це англицкий версія, то потрібно, або переписати цей файл замість font.properties або додатково змінити поточні регіональні настройки на російські. Іноді може спрацювати настройка -Duser.language = ru, але частіше - ні. Тут приблизно ті ж проблеми, що і з file.encoding - спрацює чи ні, залежить від JDK (див. Помилку за номером).
З бібліотекою Swing все простіше - в ній все малюється через підсистему Java2D. Написи в стандартних діалогах (JOptionPane, JFileChooser, JColorChooser) переробити на російський дуже просто - достатньо лише створити кілька файлів ресурсів. Я це вже зробив, так що можете просто взяти готовий файл і додати його в lib \\ ext або в CLASSPATH. Єдина проблема, з якою я зіткнувся - в версіях JDK починаючи з 1.2 rc1 і по 1.3 beta, російські літери не виводяться під Win9x при використанні стандартних шрифтів (Arial, Courier New, Times New Roman, etc.) через помилки в Java2D. Помилка досить своєрідний - зі стандартними шрифтами зображення букв відображаються не у відповідності з кодами Unicode, а по таблиці Cp1251 (кодування Ansi). Ця помилка зареєстрована в BugParade під номером. За замовчуванням в Swing використовуються шрифти, що задаються у файлі font.properties.ru, так що досить замінити їх іншими - і російські літери з'являються. На жаль, набір робочих шрифтів невеликий - це шрифти Tahoma, Tahoma Bold і два набори шрифтів з дистрибутива JDK - Lucida Sans * і Lucida Typewriter * (приклад файлу font.properties.ru). Чим ці шрифти відрізняються від стандартних - мені незрозуміло.
Починаючи з версії 1.3rc1 ця проблема вже виправлена, так що потрібно просто оновити JDK. JDK 1.2 вже сильно застарів, так що я не рекомендую ним користуватися. Так само треба врахувати, що з оригінальною версією Win95 поставляються шрифти, які не підтримують Unicode - в цій ситуації можна просто скопіювати шрифти з Win98 або WinNT.
Типові помилки, або "куди поділася буква Ш?"
Буква Ш.
Це питання ( "куди поділася буква Ш?") Досить часто виникає у початківців програмістів на Java. Давайте розберемося, куди ж вона дійсно найчастіше дівається. :-)
Ось типова програма а-ля HelloWorld:
public class Test (public static void main (String args) (System.out.println ( "ЙЦУКЕНГШЩЗХ'");))
в Far-е зберігаємо даний код в файл Test.java, компіляем ...C: \\\u003e javac Test.java
і запускаємо ...C: \\\u003e java Test ЙЦУКЕНГ? ЩЗХ'
Що ж сталося? Куди поділася буква Ш? Весь фокус тут в тому, що сталася взаємокомпенсації двох помилок. Текстовий редактор в Far за замовчуванням створює файл в DOS-кодуванні (Cp866). Компілятор ж javac для читання исходника використовує file.encoding (якщо не вказано інше ключиком -encoding). А в середовищі Windows з російськими регіональними настройками кодуванням за замовчуванням є Cp1251. Це перша помилка. В результаті, в скомпільованому файлі Test.class символи мають невірні коду. Друга помилка полягає в тому, що для виведення використовується стандартний PrintStream, який теж використовує настройку з file.encoding, однак консольне вікно в Windows відображає символи, використовуючи кодування DOS. Якби кодування Cp1251 була взаімоодназначной, то втрати даних б не було. Але символ Ш в Cp866 має код 152, який в Cp1251 не визначений, і тому відображається на Unicode-символ 0xFFFD. Коли відбувається зворотне перетворення з char в byte, замість нього підставляється символ "?".
На аналогічну компенсацію можна нарватися, якщо прочитати символи з текстового файлу за допомогою java.io.FileReader, а потім вивести їх на екран через System.out.println (). Якщо файл був записаний в кодуванні Cp866, то висновок буде йти правильно, за винятком знову ж букви Ш.
Пряма конверсія byte<->char.
Ця помилка є улюбленою у зарубіжних програмістів на Java. Вона досить докладно розглянута на початку опису. Якщо Ви коли-небудь будете дивитися чужі вихідні, то завжди звертайте увагу на явну конверсію типів - (byte) або (char). Досить часто в таких місцях закопані граблі.
Алгоритм пошуку проблем з російськими буквами
Якщо Ви не уявляєте собі де у Вашій програмі може відбуватися втрата російських букв, то можна спробувати наступний тест. Будь-яку програму можна розглядати як обробник вхідних даних. Російські літери - це такі ж дані, вони проходять в загальному випадку три стадії обробки: вони звідкись читаються в пам'ять програми (вхід), обробляються всередині програми і виводяться користувачеві (вихід). Для того, щоб визначити місце проблем, треба спробувати замість даних зашити в исходник таку тестовий рядок: "АБВ \\ u0410 \\ u0411 \\ u0412", і спробувати її вивести. Після цього дивіться, що у Вас вивелося:
- Якщо Ви побачите "АБВАБВ", значить компіляція початкових кодів і висновок у Вас працюють правильно.
- Якщо Ви побачите "??? АБВ" (або будь-які інші символи крім "АБВ" на місці перших трьох букв), значить висновок працює правильно, але ось компіляція початкових кодів відбувається невірно - скоріше за все не вказано ключик -encoding.
- Якщо Ви побачите "??????" (Або будь-які інші символи крім "АБВ" на місці другої трійки букв), значить висновок у Вас не працює належним чином.
Налаштувавши висновок і компіляцію вже можна легко розібратися і зі входом. Після настройки всього ланцюжка проблеми повинні зникнути.
Про утиліту native2ascii
Ця утиліта входить до складу Sun JDK і призначена для перетворення вихідних текстів до ASCII-виду. Вона читає вхідний файл, використовуючи зазначену кодування, а на виході записує символи в форматі "\\ uXXXX". Якщо вказати ключик -reverse, то виконується зворотна конвертація. Ця програма дуже корисна для конвертації файлів ресурсів (.properties) або для обробки вихідного, якщо Ви припускаєте, що вони можуть компіляться на комп'ютерах з відмінними від російських регіональними настройками.
Якщо запустити програму без параметрів, вона працює зі стандартним входом (stdin), а не виводить підказку по ключам, як інші утиліти. Це призводить до того, що багато хто і не здогадуються про необхідність вказівки параметрів (крім, може бути, тих, хто знайшов в собі сили і мужність зазирнути таки в документацію :-). Тим часом цю утиліту для правильної роботи необхідно, як мінімум, вказати використовувану кодування (ключик -encoding). Якщо цього не зробити, то буде використана кодування за замовчуванням (file.encoding), що може дещо розходиться з очікуваною. В результаті, отримавши невірні коду букв (через невірну кодування) можна витратити дуже багато часу на пошук помилок в абсолютно правильному коді.
Про метод перекодування символів
Цей метод багато хто використовує неправильно, напевно, не зовсім розуміючи його суть і обмеження. Він призначений для відновлення вірних кодів букв, якщо вони були невірно проінтерпретовані. Суть методу проста: з отриманих невірних символів, використовуючи відповідну кодову сторінку, відновлюється вихідний масив байтів. Потім з цього масиву байтів, використовуючи вже коректну сторінку, виходять нормальні коду символів. приклад:
String res = new String (src.getBytes ( "ISO-8859-1"), "Cp1251");
Проблем у використанні цього прийому може бути кілька. Наприклад, для відновлення використовується невірна сторінка, або ж вона може змінитися в деяких ситуаціях. Інша проблема може бути в тому, що деякі сторінки виконують неоднозначне перетворення byte<-> char. Дивіться, наприклад, опис помилки за номером.
Тому користуватися цим методом варто тільки в самому крайньому випадку, коли вже ніщо інше не допомагає, і Ви чітко собі уявляєте, де саме відбувається невірне перетворення символів.
Російські літери і MS JVM
Незрозуміло з яких міркувань, але в ній відсутні всі файли кодувань російських букв, акромя Cp1251 (напевно, вони таким чином намагалися зменшити розмір дистрибутива). Якщо Вам потрібні інші кодування, наприклад, Cp866, то потрібно додати відповідні класи в CLASSPATH. Причому класи від останніх версій Sun JDK не підходять - у Sun-а вже давно змінилася їх структура, тому останні версії класів з Microsoft-ом не стикуються (у MS залишилася структура від JDK 1.1.4). На сервері Microsoft, в принципі, лежить повний комплект додаткових кодувань, але там файл розміром близько 3 метрів, а їх сервер докачку не підтримує :-). Мені вдалося таки викачати цей файл, я його переупакованої jar-му, можете взяти його звідси.
Якщо ж Ви пишете аплет, який повинен працювати під MS JVM і Вам потрібно прочитати звідкись (наприклад з файлу на сервері) байти в російській кодуванні, відмінною від Cp1251 (наприклад, в Cp866), то стандартний механізм кодувань Ви вже не зможете використовувати - апплетам заборонено додавати класи в системні пакети, яким в даному випадку є пакет sun.io. Виходу тут два - або перекодувати файл на сервері в Cp1251 (або в UTF-8) або перед перетворенням з байтів в Unicode виконувати конвертацію байтів з потрібної кодування в Cp1251.
Русифікація Java під Linux
Скажу відразу - я з Linux не працюю, а наведена тут інформація отримана від читачів даного опису. Якщо Ви знайдете неточність або захочете доповнити - напишіть мені.
При кирилізації JVM в Linux існує дві паралельні проблеми:
- Проблема виведення кирилиці в GUI-компонентах
- Проблема введення кирилиці з клавіатури (в X11)
Проблему виведення можна вирішити таким способом (даний алгоритм надіслав Artemy E. Kapitula):
- Встановити в X11 нормальні шрифти ttf з Windows NT / 200. Я б рекомендував Arial, Times New Roman, Courier New, Verdana і Tahoma - причому підключати їх краще не через сервер шрифтів, а як каталог з файлами.
- Додати наступний файл font.properties.ru в каталог $ JAVA_HOME / jre / lib
Проблема введення вирішується приблизно таким способом (даний алгоритм надіслав Михайло Іванов):
Налаштування введення російських букв в такій конфігурації:
- Mandrake Linux 7.1
- XFree86 3.3.6
- IBM Java 1.3.0 (релізний)
проблема:
IBM Java 1.3 не дає вводити російські букви (видно як крокозябри) при тому що на лейблах і в менюхах їх видно.
використання XIM (-\u003e xkb) в AWT. (Це не є погано саме по собі, просто з такими штуками потрібно звертатися обережно + деякі приладнати xkb не люблять).
Налаштувати xkb (і локаль (xkb без локалі НЕ ПРАЦЮЄ))
процедура:
- виставляється локаль (де-небудь типу в / etc / profile або в ~ / .bash_profile)
export LANG = ru_RU.KOI8-R export LC_ALL = ru_RU.KOI8-R
- правиться (якщо це ще не зроблено) / etc / X11 / XF86Config. У секції Keyboard має бути приблизно наступне:
XkbKeycodes "xfree86" XkbTypes "default" XkbCompat "default" XkbSymbols "ru" XkbGeometry "pc" XkbRules "xfree86" XkbModel "pc101" XkbLayout "ru" XkbOptions "grp: shift_toggle" # перемикання 2-мя шифт #XkbOptions "grp: caps_toggle "# перемикання caps-lock" ом
примітка: така настройка xkb не сумісна з xrus (і йому подібними типу kikbd) а тому з ними доведеться розпрощатися. - перезапускати X-и. Потрібно перевірити щоб все працювало (типу російські літери в терміналі і додатках)
- font.properties.ru -\u003e $ JAVA_HOME / jre / lib
- fonts.dir -\u003e $ JAVA_HOME / jre / lib / fonts
- cd $ JAVA_HOME / jre / lib / fonts; rm fonts.scale; ln -s fonts.dir fonts.scale
Тепер російські букви повинні вводитися і виводитися в свінгу без проблем.
Оригінальний спосіб роботи з кодуваннями пропонує Russian Apache - розписано, як саме.