Как посчитать английские буквы в файле java
Здесь я попытался собрать информацию по поводу написания русскоязычных программ на языке Java. Если Вам известно что-либо помимо того, что я тут понаписал, напишите мне, ваши добавления будут кстати.
Содержание:
Файлы данных, потоки, БД.
Итак, как все, надеюсь, знают, в языке Java для представления символов используется Unicode, т.е. по два байта на один символ (тип char размером в 16 бит). В набор символов входят всевозможные буквы со всякими чёрточками и припендюльками, греческие, математические и символы псевдографики. В том числе и так любимые нами символы кириллицы (диапазон значений 0x0400-0x04ff). Так что с этой стороны никакой дискриминации нет.
Если Вам интересны конкретные кода символов, для их просмотра удобно использовать программу "Таблица символов" из WinNT. Вот, например, диапазон кириллицы:
Кстати, эти файлы кодовых страниц в некоторых ранних версиях JDK 1.1 содержат ошибки, вызывающие ошибки перекодировок, а то и вообще исключения при выполнении. Например, это касается кодировки KOI8_R. Лучшее, что можно при этом сделать - сменить версию на более позднюю. Судя по Sun-овскому описанию, большинство этих проблем было решено в версии JDK 1.1.6.
Когда и как надлежит пользоваться этой перекодировкой? Когда пользоваться, в принципе, понятно - при любом преобразовании из byte в char и наоборот. В классе String в тех местах, где есть преобразование можно указать дополнительный параметр (String enc), задающий имя кодовой страницы. Это конструктор по массиву байтов и метод getBytes(). Однако, в реальной программе, явно указывать кодовую страницу не всегда удобно. Для этого была введена кодировка по умолчанию. По умолчанию она зависит от системы (для русских виндов принята кодировка Cp1251), и в старых JDK её можно изменить установкой системного свойства file.encoding. Вообще-то, как утверждают в Sun, это свойство отражает системную кодировку, и она не должна изменяться в командной строке (см., например, комментарии к BugID 4256423) Эта кодировка используется тогда, когда явно не указанно название страницы. Т.к. эта настройка одна на все преобразования, иногда можно наткнуться на неприятности. Например, эта же настройка используется для вывода на консольный экран, что, в случае виндов, как правило, неприемлемо - там нужно использовать страницу Cp866. Было бы здорово, если бы эти кодировки указывались независимо - например, console.encoding и т.п., но, думаю, Sun-овцам пока не до таких высоких материй.
- Использовать вместо System.out.println свой класс, а уже в нём делать преобразование. Например:
- Написать свою версию PrintStream, поддерживающую нужную кодировку, и подставить его через System.setOut() и System.setErr(). Вот, например, обычное начало в моих программах:
- Читать и записывать массивы байтов (byte[]), а для перекодировки использовать упомянутые методы класса String. Этот способ особенно удобен, когда в потоке могут присутствовать данные в разных кодировках.
- Использовать классы InputStreamReader и OutputStreamWriter из пакета java.io, специально предназначенные для этих целей.
- Сделать преобразование в нужную кодировку. Если вы всё сделаете корректно, то данные не потеряются, хотя, конечно, пользоваться этим желательно только в крайнем случае. Пример:
Например, один из самых часто используемых драйверов - мост JDBC-ODBC. В версиях JDK 1.1, этот мост просто игнорировал кодировки символов, из-за чего нужно было предпринять дополнительные ухищрения, типа описанных в предыдущем пункте (это также касается и последней ихней версии, 1.1.8).
Мост из комплекта Sun Java 2 теперь можно настроить на нужную кодировку. Это делается добавлением дополнительного свойства charSet в набор параметров, передаваемых для открытия соединения с базой. По умолчанию используется file.encoding. Делается это примерно так:
Другой пример - драйвер JDBC-OCI (не pure Java - тот называется thin) от Oracle 8.0.5 под Linux. При получении данных из БД, драйвер определяет "свою" кодировку при помощи переменной окружения NLS_LANG. Если эта переменная не найдена, то он считает что кодировка - ISO88591. Весь фокус в том, что NLS_LANG должна быть именно переменной окружения, а properties (типа file.encoding) здесь "не катят". В случае использования драйвера внутри servlet engine Apache+Jserv, переменную окружения можно задать в файле jserv.properties:
Если же Вы свободны в формировании формата - тогда всё проще. Используйте формат Unicode или UTF8 - и проблем не будет.
В случае с БД, можно, конечно, использовать и какой-нибудь 16-ричный формат, но это не всегда приемлемо, т.к. Вы получите 2-х - 4-х кратный рост места на диске и потеряете возможность использовать стандартные программы работы с БД, например генераторы отчётов.
Русские буквы в исходниках Java-программ.
Кроме использования этой настройки есть ещё один метод - указывать буквы в формате "\uxxxx", где указывается код символа. Этот метод работает со всеми версиями, а для получения этих кодов можно использовать стандартную утилиту native2ascii.
Русские буквы в файлах properties.
Для чтения файлов properties используются методы загрузки ресурсов, которые работают специфичным образом. Собственно для чтения используется метод Properties.load, который не использует file.encoding (там кодировка жёстко указана), поэтому единственный способ указать русские буквы - использовать формат "\uxxxx" и утилиту native2ascii.
Метод Properties.save работает по разному в версиях JDK 1.1 и 1.2. В версиях 1.1 он просто отбрасывал старший байт, поэтому правильно работал только с англицкими буквами. В 1.2 делается обратное преобразование в "\uxxxx", так что он работает зеркально к методу load.
Русские буквы в Servlet-ах.
Ну, для чего эти самые Servlet-ы нужны, я думаю, Вы в курсе. Если нет - то лучше сначала прочитать документацию. Здесь же рассказывается только об особенностях работы с русскими буквами.
Так в чём же особенности? Когда Servlet посылает ответ клиенту, есть два способа послать этот ответ - через OutputStream (getOutputStream()) или через PrintWriter (getWriter()). В первом случае Вы записываете массивы байтов, поэтому применимы вышеописанные методы записи в файл. В случае же PrintWriter, он использует установленную кодировку. В любом случае необходимо правильно указать используемую кодировку при вызове метода setContentType(), для того, чтобы было правильное преобразование символов на стороне сервера. Это указание должно быть сделано перед вызовом getWriter() или перед первой записью в OutputStream. Пример:
Пока же приходится обходиться своими средствами. Оригинальный способ работы с кодировками предлагает Russian Apache - здесь расписано, как именно. Судя по отзывам, не имеет проблем с русскими и система Resin.
Своё решение проблемы так же предложил Вячеслав Педак.
Ну а самый простейший вариант извлечь таки символы - передавать в комплекте параметров имя кодировки (или, если вы уверены в текущей кодировке броузера, использовать предопределённую кодировку) и использовать метод перекодировки символов:
В общем, опыт в написании Servlet-ов у меня небольшой, так что Ваши замечания будут приветствоваться.
GUI (AWT, Swing)
Многие связывают неправильный вывод русских букв с неправильной установкой шрифта. Мне кажется, это связанно с тяжким опытом программирования на Windows 3.x, где основная причина действительно была в этом. В Java всё сложнее и редко действительно связанно со шрифтами. Я не разбирался со специфическими настройками броузеров, т.к. ещё не писал апплетов, только приложения, но думаю в последних версиях в этом плане всё нормально.
Где же действительно лежат наибольшие подводные камни? В основном это связанно с неправильной перекодировкой символов. Часть этих проблем и методы их решения описаны выше. Если у Вас все преобразования выполняются корректно, и для вывода используется шрифт Unicode, то есть очень большой шанс, что Ваша программа будет работать правильно.
Если проблемы всё же остались, тут нужно выяснить, где они возникают. Попробуйте запустить приложение под разными JVM, под разными платформами, на разных броузерах.
Начиная с версии 1.3rc1 эта проблема уже исправлена, так что можно просто обновить JDK. Так же надо учесть, что с оригинальной версией Win95 поставляются шрифты, не поддерживающие Unicode - в этой ситуации можно просто скопировать шрифты из Win98 или WinNT.
Если же вам, кровь из носу, нужно использовать стандартные шрифты, работая в JDK 1.2, то можно компенсировать этот глюк, перекодировав строки текста непосредственно перед выводом. Сделать это можно, например, так:
Но только не забудьте - этот код будет работать только под Win9x и Sun JDK/JRE 1.2.
Аналогично на подобную компенсацию можно нарваться, если поменять региональные настройки с русских на буржуйские. При этом, кроме всего прочего, меняется и кодировка по умолчанию (file.encoding) - вместо 1251 становится 1252. Это приводит к тому, что, если при чтении файлов кодировка не была явно указана (и при компиляции не задавался ключик -encoding), то русские буквы переезжают в диапазон 0x80-0xff и создаётся впечатление нормальной работы. Разницу можно заметить на преобразованиях регистра и сортировках через java.text.Collator - они будут выполняться неверно. А если были использованы строковые константы - то на других платформах вы увидите только кракозяблы.
Ещё один способ - скачать версию Swing для JDK 1.1 и запускать приложение из под Microsoft JVM - там всё выводится корректно. Только не забудьте обновить MS JVM - те версии, что идут в комплекте с IE 4.x не совсем корректно работают. С сервера Microsoft можно скачать свежую версию, например 5.00.3240 - с ней всё ОК.
Кстати, по поводу MS JVM. Непонятно по каким соображениям, но в ней отсутствуют все файлы кодировок русских букв, акромя Cp1251 (наверное, они таким образом пытались уменьшить размер дистрибутива). Если Вам нужны другие кодировки, например, Cp866, то нужно добавить соответствующие классы в CLASSPATH. Причём классы от последних версий Sun JDK не подходят - у Sun-а уже давно изменилась их структура, поэтому последние версии классов с Microsoft-ом не стыкуются (у MS осталась структура от JDK 1.1.4). На сервере Microsoft, в принципе, лежит полный комплект дополнительных кодировок (страница "Miscellaneous Resources", ссылка "Additional I/O libraries"), но там файл размером около 3 метров, а их сервер докачку не поддерживает :-). Мне удалось таки выкачать этот файл, я его перепаковал jar-ом, можете взять его отсюда.
I18n (вывод чисел, дат и т.п.)
Загадочная комбинация i18n расшифровывается просто - это сокращение от могучего слова Internationalization. 18 - это кол-во букв между i и n. Означает оно, в контексте Java, возможность автоматической подстройки программы под текущий язык и специфику страны. Делается это через использование класса Locale, представляющего язык и конкретную страну, и классов, которые знают, что с этим Locale делать. Большинство этих классов находятся в пакете java.text.
Основной класс, которым пользуются все остальные, - это java.util.ResourceBundle, который позволяет загружать различные виды ресурсов. Причём имя загружаемого класса или файла properties зависит от указанного Locale (или Locale по умолчанию - если ничего не указанно).
- resfile_ru_RU.class
- resfile_ru_RU.properties
- resfile_ru.class
- resfile_ru.properties
- resfile.class
- resfile.properties
Это позволяет легко добавлять описания для новых языков и стран. Большинство классов сами заботятся обо всей этой внутренней кухне, так что Вам об этом знать часто и не нужно.
Что касается дат, то само форматирование выполняется классом DateFormat. Получить формат, уже настроенный на язык и страну можно при помощи методов getDateInstance(), getTimeInstance() и getDateTimeInstance(). В качестве аргумента можно указать одну из констант для задания необходимого стиля формата. По умолчанию будет использован предпочтительный стиль для данного Locale. Допустимые константы:
Константа | Описание | Пример |
SHORT | Полностью цифровой, короткий вывод | 25.01.99 или 17:40 |
MEDIUM | Вывод средней длины | 25.01.1999 |
LONG | Длинный вывод | 25 Январь 1999 г. или 17:23:32 |
FULL | Вся информация Понедельник, | 25 января 1999 г. или 17:23:32 GMT+03:00 |
Если же вы хотите сами контролировать набор выводимых полей и их разделители, то для этого можно использовать класс SimpleDateFormat.
Аналогичным образом делается и форматирование чисел. За это отвечает класс NumberFormat. Получить форматы можно при помощи методов getInstance(), getNumberInstance(), getCurrencyInstance() и getPercentInstance(). Свой формат можно сконструировать при помощи класса DecimalFormat.
Об утилите native2ascii
Эта утилита входит в состав Sun JDK и предназначена для преобразования исходных текстов к ASCII-виду. Эта утилита, при запуске без параметров, работает со стандартным входом (stdin) а не выводит подсказку по ключам, как остальные утилиты. Это приводит к тому, что многие и не догадываются о необходимости указания параметров (кроме, может быть, тех, кто нашёл в себе силы и мужество заглянуть таки в документацию :-). Между тем этой утилите для правильной работы необходимо, как минимум, указать используемую кодировку (ключик -encoding). Если этого не сделать, то будет использована кодировка по умолчанию (file.encoding), что может несколько расходится с ожидаемой. В результате, получив неверные кода букв (из-за неверной кодировки) можно потратить весьма много времени на поиск ошибок в абсолютно верном коде.
О методе перекодировки символов
Этот метод многие используют неправильно, наверное, не совсем понимая его суть и ограничения. Он предназначен для восстановления верных кодов букв, если они были неверно проинтерпретированны. Суть метода проста: из полученных неверных символов, используя соответствующую кодовую страницу, восстанавливается исходный массив байтов. Затем из этого массива байтов, используя уже корректную страницу, получаются нормальные кода символов. Пример:
Проблем в использовании этого приёма может быть несколько. Например, для восстановления используется неверная страница, или же она может измениться в некоторых ситуациях. Другая проблема может быть в том, что некоторые страницы выполняют неоднозначное преобразование byte <-> char. Смотрите, например, описание ошибки за номером 4296969.
Поэтому пользоваться этим методом стоит только в самом крайнем случае, когда уже ничто другое не помогает, и Вы чётко себе представляете, где именно происходит неверное преобразование символов.
Как видно обычный лист, заполненный значениями, в плане синтаксиса ошибок нет, но есть один минус, все значения были введены вручную. С одной стороны значений не много, всего 26, в принципе можно и ввести, но если, к примеру, понадобиться добавить ещё один такой же лист, который будет содержать все буквы русского алфавита или сразу два списка обоих алфавитов в нижнем регистре, как быть тогда, тоже все руками вводить? Конечно, можно найти и скопировать уже готовый список значений и ничего вводить вручную не придется, но есть ещё один способ, о нём я расскажу в этой статье.
Как получить все буквы английского алфавита
Для начала вспоминаем, что есть такая вещь, как Unicode. Юникод — это стандарт кодирования символов, который позволяет представить знаки практически всех письменных языков Википедия. В данный момент в нём зарезервировано 1.112.064 позиций символов, из которых сейчас используется чуть более 100 000. Кодовое пространство Unicode разделено на 17 плоскостей. Нас интересует нулевая (базовая многоязычная) плоскость, имеющая диапазон от U+0000 до U+FFFF. Нулевая плоскость, содержит символы, часто употребительных письменностей и так же разбита на определенные области (диапазоны).
Латиница
Все области нас, конечно же, не интересуют, будем работать лишь с некоторыми диапазонами значений. И так, например, в нашей программе требуется получить все буквы английского алфавита от A до Z. Сразу надо пояснить, что Юникод не содержит буквы английского алфавита, точно так же, как и не содержит буквы немецкого, русского алфавита. А всё потому, что хоть мы и говорим, например буквы английского алфавита, на самом деле все буквы немецкого, итальянского, французского и того же английского языка состоят из 26 букв латинского алфавита. Поэтому нам нужно искать область, выделенную под латиницу.
Основной диапазон, выделенный под латиницу от U+0020 до U+007F (Основная латиница), где U Unicode, а следом идут четыре шестнадцатеричных числа. Вся эта область нам, конечно же, не нужна, потому что кроме букв, в нёй так же содержится ещё много других знаков и чисел, которые нас пока что не интересуют.
Результаты те же.
Область, выделенная под буквы верхнего регистра: от \x41 до \x5А
Шестнадцатеричная система не очень удобна для восприятия, поэтому переведем шестнадцатеричные коды в десятичную систему.
Как перевести число из шестнадцатеричной системы в десятичную
Сначала добавим строку: using System.Globalization;
Затем, берём шестнадцатеричный набор символов \x41, убираем первые два символа (\x) оставляем только 41, после чего используя метод parse, преобразуем строку в число.
Как перевести число в шестнадцатиричную систему
Обратный перевод dec в hex.
В результате всех вычислений получаем следующую табличку:
Название | Символы | DEC | HEX |
Латиница | A..Z | 65..90 | 41..5A |
Латиница | a..z | 97..122 | 61..7A |
Теперь имея на руках данную таблицу, можно легко получить, к примеру, массив всех английских букв от A до Z в верхнем регистре.
А вот так, например можно вывести слово HELLO из полученного массива Как получить все буквы русского алфавита
С латиницей, надеюсь всё понятно, теперь рассмотрим, как получить буквы русского алфавита. В отличие от английского алфавита, в русском используется Кириллица (Cyrillic), под которую так же в нулевой плоскости выделен определенный диапазон: U+0400..U+04FF (Unicode 1.1).
Название | Символ | DEC | HEX | DEC код Ё |
Кириллица | А…Я | 1040..1071 | 0410..042F | 1025 |
Кириллица | а..я | 1072..1103 | 0430..044F | 1105 |
Как видно всё то же самое, но есть один нюанс, в диапазон значений не попадает буква Ё, как для нижнего, так и верхнего регистра.
В следующем примере показано, как получить все буквы русского алфавита от А до Я в верхнем регистре.
Краткое руководство по проверке того, содержит ли текст все буквы алфавита.
1. Обзор
В этом уроке мы увидим, как проверить, содержит ли строка все буквы алфавита или нет.
Мы обсудим три подхода.
Сначала мы смоделируем алгоритм, используя императивный подход. Затем будут использоваться регулярные выражения. И, наконец, мы воспользуемся более декларативным подходом с использованием Java 8.
Кроме того, мы обсудим Большую сложность принятых подходов.
2. Императивный алгоритм
Давайте реализуем императивный алгоритм. Для этого сначала создадим логический массив visited. Затем мы пройдем через входную строку символ за символом и пометим символ как посещенный.
Обратите внимание, что Верхний регистр и Нижний регистр считаются одинаковыми. Таким образом, индекс 0 представляет и А, и а, аналогично, индекс 25 представляет и Z, и z.
Наконец, мы проверим, все ли символы в посещенном массиве имеют значение true:
Обратите внимание, что существует множество способов оптимизации алгоритма, таких как удаление букв из набора и разрыв, как только Set пуст. Однако для целей упражнения этот алгоритм достаточно хорош.
3. Использование Регулярного Выражения
Используя регулярное выражение, мы можем легко получить те же результаты с помощью нескольких строк кода:
Здесь мы сначала исключаем все символы, кроме букв алфавита, из ввода . Затем мы удаляем дубликаты символов. Наконец, мы считаем буквы и убеждаемся, что у нас есть все, 26.
Несмотря на меньшую производительность, Большая сложность этого подхода также имеет тенденцию к O(n).
4. Поток Java 8
Используя функции Java 8, мы можем легко достичь того же результата более компактным и декларативным способом, используя методы Stream filter и distinct :
Большая-O-сложность этого подхода также будет O(n).
4. Тестирование
Давайте проверим счастливый путь для нашего алгоритма:
Здесь предложение содержит все буквы алфавита, следовательно, мы ожидаем true в результате.
5. Заключение
В этом уроке мы рассмотрели, как проверить, содержит ли строка все буквы алфавита .
Сначала мы увидели несколько способов реализовать это с помощью традиционного императивного программирования, регулярных выражений и потоков Java 8.
У меня есть текстовый файл, и мне было любопытно, какой символ появляется, как часто в тексте.
Любой отзыв оценен.
Выход будет, например:
Ресурсы:
Вы должны начать использовать try-with-resources . Этот статус выполняет некоторые работы для вас с ресурсами, которые реализуют AutoCloseable . Он закрывает эти ресурсы для вас, поэтому вам не нужно беспокоиться о блокировках файлов и остальных подключениях к базе данных:
Вы также не должны бросать Exception в основной метод вашей программы. Это может смутить пользователей. Вместо этого основной метод должен обрабатывать все исключения «изящно», будучи завернутым в блок try-catch .
Conditionals:
Это ранний оператор возврата для следующих условий, то есть вам не нужно писать else if в следующем состоянии .
Нейминг
hashMap не является хорошим именем. Используемая вами карта не является Hash-Map, а treeMap также не будет объяснять, что делает карта, что она содержит .
Возможно, вы захотите переименовать его в characterMap
все остальные равны, ваше именование является хорошим и последовательным, и точно определяет, что делают переменные. Вы прекрасно следуете camelCase -conventions. Продолжайте!
Резюме:
Ваш код читается красиво и легко понятен. Вы следуете соглашениям об именах и имеете описательные и понятные имена переменных. Вы должны работать над обработкой исключений и использованием ресурсов.
Мои заметки в коде:
Я бы переписал класс следующим образом:
Затем я сначала инициализировал отображение символами, такими как
Затем это поможет вам сократить лестницу if-else, например
Все точки в ответе Vogel612 должны быть приняты во внимание. Ваш отказ от закрытия ресурсов - это ваша самая большая проблема.
Моя главная цель с этим ответом - показать, как теперь следует выполнить с Java 8.
Ваш текущий метод использует очень традиционные циклы и условия Java. Вот как код должен выглядеть с API-интерфейсом Java 8:
Этот код имеет ту же функцию, что и ваш код, но значительно короче - он использует новый API Stream Java 8 в сочетании со всеми новые лямбды.
Мы используем метод filter Stream , чтобы вырезать вещи, которые не являются буквами.
Теперь мы используем новый метод Map.merge , который принимает ключ и значение и, кроме того, лямбда, которая принимает два значения. Если ключ не существует на карте, он просто добавляется с заданным значением. Если он существует на карте, то лямбда вызывается с существующим значением и новым значением; значение, возвращаемое из лямбда, затем помещается в карту.
Мы используем метод collect для Stream<Character> , чтобы «уменьшить» поток в изменяемую коллекцию, в этом случае TreeMap .
Наконец, мы используем новый метод forEach на Map , чтобы распечатать содержимое карты.
Как демонстрация возможностей Java 8, чтобы сортировать результат по счету, а не по символу (как и в вашем посте), просто измените печать на:
Читайте также: