Java: Росіяни букви і не тільки ... h2>
Введення b>
p>
Деякі
проблеми настільки складні, що потрібно бути дуже розумним і дуже добре
поінформованим, щоб не бути впевненим у їх вирішенні. p>
Лоренс
Дж. Пітер p>
Peter's Almanac p>
Кодування h2>
Коли
я тільки починав програмувати на мові C, першою моєю програмою (не рахуючи
HelloWorld) була програма перекодування текстових файлів з основної кодування
ГОСТ-а (пам'ятаєте таку? :-) В альтернативне. Було це в далекому 1991-му році. З
тих пір багато чого змінилося, але за минулі 10 років подібні програмки свою
актуальність, на жаль, не втратили. Занадто багато вже накопичено даних у
різноманітних кодуваннях і занадто багато використовується програм, які вміють
працювати тільки з однією. Для російської мови існує не менше десятка
різних кодувань, що робить проблему ще більш заплутаною. p>
Звідки
ж узялися всі ці кодування і для чого вони потрібні? Комп'ютери за своєю природою
можуть працювати тільки з числами. Для того, щоб зберігати букви в пам'яті
комп'ютера треба поставити у відповідність кожній букві певне число (приблизно
такий же принцип використовувався і до появи комп'ютерів - згадайте про ту ж
азбуку Морзе). Причому число бажано трохи менше - чим менше двійкових розрядів
буде задіяно, тим ефективніше можна буде використовувати пам'ять. Ось це
відповідність набору символів та чисел власне і є кодування. Бажання будь-якої
ціною заощадити пам'ять, а так само роз'єднаність різних груп комп'ютерників і
призвела до нинішнього стану справ. Найпоширенішим способом кодування
зараз є використання для одного символу одного байти (8 біт), що
визначає загальну кількість символів в 256. Набір перші 128 символів
стандартизований (набір ASCII) і є однаковими у всіх поширених
кодуваннях (ті кодування, де це не так вже практично вийшли з
вжитку). Англіцкіе літери і символи пунктуації знаходяться в цьому
діапазоні, що й визначає їх вражаючу живучість в комп'ютерних системах
:-). Інші мови знаходяться не в настільки щасливому становищі - їм усім
доводиться тулитися в останніх 128 числах. p>
Unicode h2>
В
Наприкінці 80-х багато хто усвідомив необхідність створення єдиного стандарту на
кодування символів, що й призвело до появи Unicode. Unicode - це спроба
раз і назавжди зафіксувати конкретне число за конкретним символом. Зрозуміло,
що в 256 символів тут не укластися при всьому бажанні. Досить довгий час
здавалося, що вже 2-х то байт (65536 символів) повинно вистачити. Але ж ні --
остання версія стандарту Unicode (3.1) визначає вже 94140 символів. Для
такого кол-ва символів, напевно, вже доведеться використовувати 4 байти (4294967296
символів). Може бути і вистачить на деякий час ... :-) P>
В
набір символів Unicode входять всілякі літери з усякими рисками і
пріпендюлькамі, грецькі, математичні, ієрогліфи, символи псевдографіки і
пр. і пр. У тому числі і так улюблені нами символи кирилиці (діапазон значень
0x0400-0x04ff). Так що з цього боку ніякої дискримінації немає. p>
Якщо
Вам цікаві конкретні коду символів, для їх перегляду зручно використовувати
програму "Таблиця символів" з WinNT. Ось, наприклад, діапазон
кирилиці: p>
p>
Якщо
у Вас інша OS або Вас цікавить офіційне тлумачення, то повну розкладку
символів (charts) можна знайти на офіційному сайті Unicode
(http://www.unicode.org/charts/web.html). p>
Типи char і byte h2>
В
Java для символів виділений окремий тип даних char розміром у 2 байти. Це часто породжує плутанину в
умах початківців (особливо якщо вони раніше програмували на інших мовах,
наприклад на C/C + +). Справа в тому, що в більшості інших мов для обробки
символів використовуються типи даних розміром в 1 байт. Наприклад, в C/C + + тип char в більшості випадків використовується
як для обробки символів, так і для обробки байтів - там немає поділу. У
Java для байтів є свій тип - тип byte.
Таким чином C-ішному char
відповідає Java-київськи byte,
а Java-вскому char зі світу C
найближче тип wchar_t.
Треба чітко розділяти поняття символів і байтів - інакше нерозуміння і проблеми
гарантовані. p>
Java
практично з самого свого народження використовує для кодування символів
стандарт Unicode. Бібліотечні функції Java очікують побачити в змінних типу char символи, представлені кодами
Unicode. В принципі, Ви, звичайно, можете запхати туди що завгодно - цифри є
цифри, процесор все стерпить, але при будь-якій обробці бібліотечні функції будуть
діяти виходячи з припущення що їм передали кодування Unicode. Так що
можна спокійно вважати, що у типу char
кодування зафіксована. Але це всередині JVM. Коли дані читаються ззовні або
передаються назовні, то вони можуть бути представлені тільки одним типом - типом byte. Всі інші типи конструюються з
байтів в залежності від використовуваного формату даних. Ось тут на сцену і
виходять кодування - в Java це просто формат даних для передачі символів,
який використовується для формування даних типу char. Для кожної кодової сторінки в бібліотеці є по
2 класу перекодування (ByteToChar і CharToByte). Класи ці лежать в пакеті sun.io. Якщо, при перекодуванні з char в byte не було знайдено відповідного символу, він
замінюється на символ?. p>
До речі,
ці файли кодових сторінок в деяких ранніх версіях JDK 1.1 містять помилки,
викликають помилки перекодіровок, а то й взагалі виключення при виконанні.
Наприклад, це стосується кодування KOI8_R. Найкраще, що можна при цьому зробити --
змінити версію на більш пізню. Судячи з Sun-івської опису, більшість цих
проблем було вирішено у версії JDK 1.1.6. p>
До
появи версії JDK 1.4 набір доступних кодувань визначався тільки
виробником JDK. Починаючи з 1.4 з'явилося нове API (пакет java.nio.charset), за допомогою якого
Ви вже можете створити свою власну систему кодування (наприклад підтримати рідко
використовувану, але моторошно необхідну саме Вам). p>
Клас String h2>
В
більшості випадків для подання рядків в Java використовується об'єкт типу java.lang.String. Це звичайний клас,
який всередині себе зберігає масив символів (char []),
і який містить багато корисних методів для маніпуляції символами. Найкращі
цікаві - це конструктори, що мають перший параметром масив байтів (byte []) і методи getBytes (). За допомогою цих методів Ви
можете виконувати перетворення з масиву байтів в рядки і назад. Для того,
щоб вказати будь кодування при цьому використовувати у цих методів є
рядковий параметр, який задає її ім'я. Ось, наприклад, як можна виконати
перекодування байтів з КОИ-8 у Windows-1251: p>
//
Дані в кодуванні КОИ-8 p>
byte []
koi8Data = ...; p>
// Перетворимо з
КОИ-8 в Unicode p>
String string = new
String (koi8Data, "KOI8_R "); p>
//
Перетворимо з Unicode в Windows-1251 p>
byte [] winData =
string.getBytes ( "Cp1251 "); p>
Список
8-ми бітових кодувань, доступних в сучасних JDK і підтримують російські
букви Ви можете знайти нижче, в розділі "8-ми бітові кодування росіян
букв ". p>
Так
як кодування - це формат даних для символів, крім знайомих 8-ми бітових
кодувань в Java також на рівних присутні і багатобайтове кодування. До
таких відносяться UTF-8, UTF-16, Unicode і пр. Наприклад ось так можна отримати
байти у форматі UnicodeLittleUnmarked (16-ти бітове кодування Unicode,
молодший байт першим, без ознаки порядку байтів): p>
// Рядок Unicode p>
String string = "..."; p>
// Перетворимо з Unicode в
UnicodeLittleUnmarked p>
byte [] data =
string.getBytes ( "UnicodeLittleUnmarked "); p>
При
подібних перетвореннях легко помилитися - якщо кодування байтових даних не
відповідають вказаним параметром при перетворенні з byte в char, то перекодування буде виконано неправильно.
Іноді після цього можна витягнути правильні символи, але частіше за все частина
даних буде безповоротно втрачена. p>
В
реальної програмі явно вказувати кодову сторінку не завжди зручно (хоча більше
надійно). Для цього була введена кодування за замовчуванням. За умовчанням вона
залежить від системи і її установки (для російських віндів прийнята кодування Cp1251),
і в старих JDK її можна змінити установкою системного властивості file.encoding.
В JDK 1.3 зміна цієї налаштування іноді спрацьовує, іноді - ні. Викликано
це в такий: спочатку file.encoding ставиться з регіональних налаштувань
комп'ютера. Посилання на кодування за замовчуванням запам'ятовується в нутрії при першій
перетворення. При цьому використовується file.encoding, але це перетворення
відбувається ще до використання аргументів запуску JVM (собсно, при їх
розборі). Взагалі-то, як стверджують в Sun, ця властивість відображає системну
кодування, і вона не повинна змінюватися у командному рядку (див., наприклад,
коментарі до BugID 4163515) Тим не менше у JDK 1.4 Beta 2 зміна цієї налаштування
знову почала надавати ефект. Що це, свідома зміна або побічний
ефект, який може знову зникнути - Sun-вівці чіткої відповіді поки що не дали. p>
Ця
кодування використовується тоді, коли явно не вказано назву сторінки. Про це
треба завжди пам'ятати - Java не буде намагатися передбачити кодування байтів,
які ви передаєте для створення рядка String (так само вона не зможе прочитати
Ваші думки з цього приводу :-). Вона просто використовує поточну кодування за
замовчуванням. Оскільки ця установка один на всі перетворення, іноді можна
наразитися на неприємності. p>
Для
перетворення з байтів в символи і назад слід користуватися тільки цими
методами. Просте приведення типу використовувати в більшості випадків не можна --
кодування символів при цьому не буде враховуватися. Наприклад, однією з найбільш
поширених помилок є читання даних побайтно за допомогою методу
read () з InputStream, а потім приведення отриманого значення до типу char: p>
InputStream is = ..; p>
int b; p>
StringBuffer sb = new
StringBuffer (); p>
while ((b = is.read ())!=- 1) p>
( p>
sb.append ((char) b);// <- так робити не можна p>
) p>
String
s = sb.toString (); p>
Зверніть
увагу на приведення типу - "(char) b".
Значення байтів замість перекодування просто скопійовано в char (діапазон
значень 0-0xFF, а не той, де знаходиться кирилиця). Такому копіюванню
відповідає кодування ISO-8859-1 (яка один в один відповідає першій
256 значень Unicode), а отже, можна вважати, що цей код просто використовує
її (замість тієї, в якій реально закодовані символи в оригінальних даних).
Якщо Ви спробуєте відобразити отримане значення - на екрані буде або
Вопросики або кракозябли. Наприклад, при читанні рядка "АБВ" в
віндового кодуванні може запросто відобразитися щось на кшталт такого:
"