Показаны сообщения с ярлыком Строки. Показать все сообщения
Показаны сообщения с ярлыком Строки. Показать все сообщения

29 июн. 2015 г.

Классы. Практика: строки и массивы как аргументы.

Теперь немного попрактикуемся чтобы закрепить прошлый материал. Так же что бы хорошо понять эти упражнения рекомендую освежить в памяти эту статью, особенно ее начальную часть где рассказывается о том как строки располагаются в памяти.

По существу это практика на понимание ссылочных типов данных и нацелена она на то чтобы понять разницу между объектом и ссылкой на объект. Поехали!

И теперь два маленьких класса используемых в этой программе.

Далее вывод данной программы:

CP0001

Кратенько поясню данный вывод. В строке 8 основной программы создается объект str1, который содержит строку Str1. В 9 строке создается строковая переменная str2 которая так же содержит строку Str1. НО! Хотя эти строки и создаются по разному, это один и тот же объект, что видно из вывода программы. С выводом str3 и str4 все должно уже быть более мнее вам понятно. А затем при помощи метода intern() в строке 20 мы заставляем str4 указывать на тот же объект (строку) что и str1 с str2. С массивами еще интереснее. Мы создаем класс dim1, внутри которого уже формируется массив из переданных аргументов. И далее на эти аргументы мы воздействуем из Class013 через массив strArray. А затем делаем наоборот. Это возможно поскольку мы работаем со ссылочными типами.

На эти грабли частенько наступают начинающие программисты. Если что-то не понятно, то пишите комменты.

11 июн. 2015 г.

Строки. Часть 9 – форматирование строк (практика).

Str00049

Разомнёмся на небольшом и простом примере слева.

Первый printf() выводит число 108 в десятичном и шестнадцатеричном виде.

Второй printf() выводит переменные i2 и i3 в различных вариантах со знаком и без него, а так же при разной ширине полей и если эта ширина больше количества символов (учитывая скобки и знаки + и ), то дополнительные поля заполняются пробелами.

Вариант printf() для double делает почти все то же самое, что и для int, но стоит обратить внимание на округление.

Строку s1 printf() выводит с различной максимальной шириной поля, что может ее обрезать в некоторых случая, а в некоторых, наоборот, добавить пробелов. Вывод этой программы следующий:

Str00050

Третий printf(), выводящий строки a, b, c и d в обратном порядке тоже интересный. Он использует индексы параметров (строк), чтобы вывести и их в противоположном порядке. Обратите внимание что индексы (перед знаком доллара - $) идут от 4 до 1.

И еще один пример простой программы, которая заполняет массив случайными числами типа double и сперва выводит массив в неформатированном виде, а затем в форматированном (без сортировки).

Str00051

Вывод у программы следующий:

Str00052

Если хочется увидеть отсортированный массив, то допишите программу сами :)

Строки. Часть 8 – форматирование строк (теория).

В JDK 5 был введен механизм форматирования строк, подобный тому, что есть в операторе printf языка C. Этот механизм реализует метод format() класса String и многие другие методы, например System.out.printf().

Чтобы проще было понять о чем речь, перейдем сразу к практике и рассмотрим простой пример:

Str00044Число x можно вывести на консоль с помощью выражения System.out.println(x). В результате на экране отобразится число с максимальным количеством значащих цифр, допустимых для данного типа. Например, в результате выполнения приведенного ниже фрагмента кода на экран будет выведено число 3333.3333333333335.

В ряде случаев это создает проблемы. Так, например, если вы хотите вывести на экран сумму в долларах и центах, большое количество цифр затруднит восприятие.


Форматирование строк делает их восприятие более приятным и понятным. Посмотрим какой вывод генерирует данная программа:

Str00045Как видите такой вывод более приятен и понятен. Например, первый оператор printf форматирует число x таким образом (%.2f), что длина общего поля равна размеру всего числа, а десятичная часть занимает от этого только два числа (поля).

Второй оператор printf уже форматирует значение x в виде числа, размер поля которого составляет 8 цифр, а дробная часть равна двум цифрам (%8.2f). Можете посчитать и убедиться. Кроме того обратите внимание что между символом равно (=) и числом появился пробел, поскольку для всего поля числа отведено 8 символов и все не занятые поля заполняются пробелами и происходит выравнивание по правому краю. Следующий printf уже отвел под поле числа 16 символов (%16.2f), что тоже видно на выводе программы. И последним я продемонстрировал работу метода format() класса String. Они имеют абсолютно одинаковый синтаксис, так как их функционал основан на классе Formatter.

Теперь будем разбираться более подробно с синтаксисом форматирования строк.

В методах printf() и format() можно задавать произвольное количество параметров. Пример вызова с несколькими параметрами приведен ниже.

System.out.printf("%s, в следующем году вам будет %d", name, age);

В данном случае значение переменной (параметра) name будет подставлено вместо спецификатора формата %s, а значение переменной (параметра) age – вместо спецификатора формата %d.

Каждый спецификатор формата, начинающийся с символа %, заменяется соответствующим параметром. Символ преобразования, которым завершается спецификатор формата, задает тип форматируемого значения: f — число с плавающей точкой; s — строка; d — десятичное число; и т.п. Символы преобразования описаны в таблице ниже:

Str00046
На заметку! Преобразование s можно использовать для форматирования любого объекта. Если этот объект реализует интерфейс Formattable, вызывается метод formatTo(). В противном случае для преобразования объекта в строку применяется метод toString().

Для даты и времени параметр х может быть представлен следующими символами преобразования:

DATE2
В составе спецификатора формата могут присутствовать флаги, управляющие форматом выходных данных. Назначение всех флагов описано в таблице ниже:

Str00047
Например, запятая, используемая в качестве флага, формирует разделитель групп, который зависит от локали (формата) установленной в вашей ОС. Так, в результате выполнения на Windwos 8.1 в русской локали, приведенного ниже оператора на экран будет выведена строка 3 333,33.

System.out.printf("%,.2f", 10000.0 / 3.0);

То есть в качестве разделителя разрядов используется пробел, заданный в настройках локали ОС.

На заметку! Ряд правил форматирования зависит от специфических условий конкретной страны или региона.

В одном спецификаторе формата можно использовать несколько флагов, например последовательность символов "%, (.2f" указывает на то, что при выводе будут использованы разделители групп, а отрицательные числа будут помещены в скобки). Например строка:


System.out.printf("%, (.2f", -10000.0 / 3.0);

Сгенерирует вот такой вывод: (3 333,33)

Для создания форматированной строки без вывода ее можно использовать статический метод String.format(). Например:


String message = String.format("%s, в следующем году вам будет %d", name, age);

В строке, определяющей формат, может задаваться индекс форматируемого параметра. Индекс должен следовать непосредственно за символом % и завершаться знаком $. Пример использования индекса приведен ниже.


System.out.printf("%1$s %2$tB %2$te, %2$tY", "Дата:", new Date());

Данный код сформирует такой вывод: Дата: июня 11, 2015

Внимание! Индексы начинаются с единицы.

Вы также можете использовать флаг <, который означает, что форматированию подлежит тот же параметр, который был сформатирован последним. Так, приведенный ниже оператор дает тот же результат, что и рассмотренное ранее.


System.out.printf("%s %tB %<te, %<tY", "Дата:", new Date());

Чтобы все стало чуть более понятно, то, кратко, общий синтаксис можно описать так:

%[аргумент_индекс][флаги][ширина][.точность]символ_преобразования

Спецификатор ширина управляет минимальным размером поля, при необходимости пустые поля заполняются пробелами. По умолчанию данные выравниваются по правому краю, но тип выравнивания можно переопределить, включив символ в секцию флаги.
В отличие от ширины поле точность задает минимальное значение. И если ширина относится ко всем типам преобразований данных и работает одинаково для всех типов, то точность имеет разный смысл для разных типов. Для объектов String точность задет максимальное количество выводимых символов. Для вещественных чисел точность задает количество выводимых знаков после запятой (шесть по умолчанию), с округлением или добавлением завершающих нулей в случае необходимости. Так как целые числа не имеют дробной части, точность на них не распространяется, и при попытке применения этого спецификатора с целым числом будет вызвано исключение.

В заключение теории приведу диаграмму которая показывает синтаксис спецификатора формата:

Str00048
P.S. В следующем разделе будет больше практики. Я разделил теорию и практику чтобы было удобней смотреть на таблицы символов преобразования и примеры на разных вкладках, а не мотать одну страницу вверх и вниз.

10 июн. 2015 г.

Строки. Часть 7 – регулярные выражения в классе String.

toni-stark_reg_exp_smallО регулярных выражениях пишут целые книги, поскольку это достаточно сложная тема. Поэтому, тут мы просто вкратце легонько затронем ее, чтобы вы знали о такой возможности обработки строк в классе String и вообще в Java.

Регулярные выражения поддерживаются в стандартной библиотеке Java. Это означает, что ни вам ни пользователю не надо скачивать на каких дополнительных библиотек, чтобы регулярные выражения заработали в ваших программах.

Ну, понеслась!

Str00038Начнем с простого примера, разбиения предложения записанного в строке на отдельные слова, в качестве разделителя слов будет использоваться естественный разделитель – пробел. Для этого в классе String есть метод split() – разделение строки на подстроки с использование регулярного выражения.

Регулярным выражением является строка – \\s, которая означает разделитель (пробел, табуляцию, новую строку, перевод страницы и возврат курсора).

Вывод программы следующий:

Str00039

То есть в массиве строк у нас оказались слова (без пробелов между ними) из строки strTst1.

Теперь добавим еще несколько строчек и пару методов к данному коду:

Str00040

Даже самое простое регулярное выражение (-|\\+)?\\d+ выглядит как магическое заклинание :), хотя этот шаблон выбирает в тексте целые числа, которые могут быть как со знаками + или , так и вовсе без них.

Этот фрагмент кода генерирует следующий вывод:

Str00041

Метод replaceAll(), заменяет все последовательности символов в строке попадающие под шаблон регулярного выражения на строку *NUMBER*. Метод replaceFirst() делает то же самое, но только для первого вхождения строки подпадающей под шаблон регулярного выражения. Как видите числа были заменены везде, где бы они не стояли, отдельно, в середине слова или в его начале. Что согласитесь очень удобно, так как не надо парсить (разбирать) строку посимвольно и сравнивать каждый символ на то является он цифрой или нет, а так же есть ли перед ней знак или нет.

Синтаксис регулярных выражений мы изучим чуть позже. Здесь у нас только знакомство с ними.

Добавим еще немножко кода:

Str00042

И запустим программу передав ей некоторые аргументы в командной строке.

Запуск и вывод могут выглядеть так:

Str00043

Здесь желтым подсвечен вывод вышеприведенного фрагмента кода.

Метод matches(), проверяет удовлетворяет ли вся строка целиком заданному регулярному выражению и выводит сообщение о том, подходит оно под этот шаблон или нет, что собственно и видно из вывода программы.

На этом, пока, с регулярными выражениями закончим знакомство, но мы его обязательно продолжим.

9 июн. 2015 г.

Строки. Часть 6 – методы класса String.

Часть методов класса String мы уже рассмотрели в предыдущих постах. Некоторые методы класса String такие же как и у классов StringBuilder и StringBuffer, поэтому здесь их рассматривать уже не будем. Сейчас кратко рассмотрим некоторые оставшиеся на примере простой программы:

Данная программа генерирует следующий вывод:

Str00037

Метод isEmpty() проверяет является ли строка пустой, то есть не содержащей ни одного символа. Не стоит это путать если переменная типа String содержит null ссылку, то есть не ссылается ни на какой объект.

Выбрать символ с индексом ind можно методом charAt(int ind). Если индекс ind отрицателен или больше, чем длина строки, возникает исключительная ситуация.

Все символы строки в виде массива символов можно получить методом toCharArray(), но с ним мы уже не раз сталкивались поэтому его нет в этом примере.

Если же надо включить в массив символов dst, начиная с индекса ind массива, подстроку от индекса begin включительно до индекса end исключительно, то используйте метод типа void:

getChars(int begin, int end, char[] dst, int ind)

В массив будет записано end-begin символов, которые займут элементы массива, начиная с индекса ind до индекса ind+(end-begin)-1.

 

Этот метод создает исключительную ситуацию в следующих случаях:

  • ссылка dst == null;
  • индекс begin отрицателен;
  • индекс begin больше индекса end;
  • индекс end больше длины строки;
  • индекс ind отрицателен;
  • ind+(end-begin) больше dst.length.

Если надо получить массив байтов, содержащий все символы строки в байтовой кодировке ASCII, то используйте метод getBytes(). Этот метод при переводе символов из Unicode в ASCII использует локальную кодовую таблицу. Если же надо получить массив байтов не в локальной кодировке, а в какой-то другой, применяйте метод getBytes(String encoding) или метод getBytes(Charset encoding).

Кода мы преобразовали строку str5 в массив byte, то получили отрицательные значения потому что коды латиницы лежат за пределами максимального положительного значения для byte, но если посмотреть на hex код этих отрицательных значений для byte, то к примеру –48 это шестнадцатеричное D0, что соответствует большой русской букве Р в кодировке CP1251.

Метод regionMatches()  сравнивает указанную часть строки с другой частью строки. Существует также перегруженная форма, которая игнорирует регистр символов при сравнении. Оба метода возвращают результат boolean. Вот общая форма этих двух методов:

regionMatches(int startIndex, String str2,  int str2StartIndex, int numChars)
regionMatches(boolean
ignoreCase, int startIndex, String str2,  int str2StartIndex, int numChars)

В обеих версиях startIndex задает индекс начала диапазона строки вызывающего объекта String. Строка, подлежащая сравнению, передается в str2. Индекс символа, начиная с которого нужно выполнять сравнение в str2, передается в str2StartIndex, а длина сравниваемой подстроки — в numChars. Во второй версии, если ignoreCase равно true, регистр символов игнорируется. В противном случае регистр учитывается.

В классе String определены два метода, представляющие собой более или менее специализированные формы regionMatches(). Метод startWith()  определяет, начинается ли заданный объект String с указанной строки. В дополнение endsWith() определяет, завершается ли объект String заданным фрагментом. Эти методы возвращают результат типа boolean. Так как методы достаточно просты, то я думаю достаточно примера в коде, чтобы понять как они работают.

Класс String предлагает два метода, которые позволяют вам выполнять поиск в строке определенного символа или подстроки.

indexOf()  — ищет первое вхождение символа или подстроки.
lastIndexOf()  — ищет последнее вхождение символа или подстроки.

Эти два метода перегружены несколькими разными способами. Во всех случаях эти методы возвращают позицию в строке (индекс), где символ или подстрока была найдена, либо -1 в случае неудачи. Поскольку вариантов перегрузки этих методов достаточно много, то описывать их все тут достаточно долго и нудно, поэтому, опять же, предлагаю посмотреть на примере приведенном выше как они работают.

Метод replace()  имеет две формы. Первая заменяет в исходной строке все вхождения одного символа другим. Вторая форма replace() заменяет одну последовательность символов на другую.

Метод contains() проверяет содержит ли проверяемая строка, строку переданную как аргумент метода и если содержит возвращает true, если нет – false.

Метод join() объединяет строки переданные как параметры и разделяет их разделителем, переданным в качестве первого параметра. В общем смотрите пример.

Метод toLowerCase() преобразует все символы строки из верхнего регистра в нижний. Метод toUpperCase() преобразует все символы строки из нижнего регистра в верхний. Небуквенные символы, такие как десятичные цифры, остаются неизменными.

Метод trim() возвращает новую строку, в которой удалены начальные и конечные символы с кодами, не превышающими ' \u0020'.

У класса String существует еще несколько методов для работы с регулярными выражениями, которые мы рассмотрим чуть позже.

Так же хочу отметить, что в примере приведенном выше рассмотрены не все ипостаси методов которые есть у класса String, поэтому, опять же, рекомендую смотреть исходники класса String в JDK, дабы обрести просветление по данной теме.

8 июн. 2015 г.

Строки. Часть 5 – сравнение строк.

Еще раз напомню, что в Java, все кроме примитивных типов являются ссылочными типами. Сравнение строк рассмотрим на уже более или менее знакомом нам примере когда мы рассматривали создание строк класса String.

Str00033

Следует обратить особое внимание на сравнение строк s4 и s5, хотя их содержимое равно, но при сравнении s4==s5 мы получим false, так как это разные объекты и при использовании оператора сравнения (==) для строк, равно как и для любых других объектов, происходит сравнение ссылок, а данном случае они разные. Поэтому при сравнении объектов оператором == можно получить граблями по морде неожиданный эффект :)

Если же для сравнения использовать метод equals() класса String, то происходит сравнение содержимого объектов (строк). Так же надо учитывать что метод сравнения строк equals() регистро-зависимый, то есть строки "Hello" и "HELLO" для него будут разными.

Данная программа генерирует следующий вывод:

Str00034

Для того чтобы сравнивать строки не зависимо от  регистра символов существует метод equalsIgnoreCase().

Для сравнения строк существуют еще методы compareTo(String str)  и compareToIgnoreCase(String str). Данные методы сравнивают лексическое значение строки со строкой заданной в параметре str, определяя сортируются ли она в алфавитном порядке раньше или позже строки в параметре str. Методы возвращают целое число типа int, которое может быть меньше, равно или больше нуля. Данное число определяется по следующим правилам:

  1. Сравниваются символы данной строки this и строки str с одинаковым индексом (т.е. посимвольное сравнение строк), пока не встретятся различные символы с индексом, допустим, k или пока одна из строк не закончится.
  2. В первом случае возвращается значение this.charAt(k) — str.charAt(k) , т. е. разность кодировок Unicode первых несовпадающих символов.
  3. Во втором случае возвращается значение this.length() — str.length() , т. е. разность длин строк.
  4. Если строки совпадают, возвращается 0, т.е. в той же ситуации, в которой метод equals() возвращает true.
  5. Если значение str равно null, возникает исключительная ситуация.

Сравнение символов происходит строго по их расположению в таблице кодировки стандарта Unicode, то есть эти методы не учитывают алфавитное расположение символов в локальной кодировке.

Русские буквы расположены в Unicode по алфавиту, за исключением одной буквы. Заглавная буква Ё находится перед всеми кириллическими буквами, ее код ' \u0401' , а строчная буква ё — после всех русских букв, ее код '\u0451' .

Если вас такое расположение не устраивает, задайте свое размещение букв с помощью класса RuleBasedCollator из пакета java.text.

Теперь небольшой пример:

Str00035

Вывод этой программы следующий:

Str00036

В первом случае –3 получилось поскольку код символа а равен 97, а код символа d равен 100, 97-100=-3. После этого мы просто сравнили одинаковые строки и поэтому результат равен нулю. Затем случай противоположный первому 100-97=3. И последний случай это когда вычлась длина строки abc0 из длины строки abc, что равно –1.

 

Здесь мы рассмотрели основные принципы сравнения строк целиком и граблей которые при этом могут быть. В классе String существует еще несколько методов сравнения частей строк, но их мы рассмотрим чуть позже.

7 июн. 2015 г.

Строки. Часть 4 – конкатенация строк.

Str001

Сразу же начнем с примера чуть модифицированной программы, которую мы рассматривали тут. В этом варианте мы оставили только тестирование производительности и добавили еще тест на скорость создания строки объекта StringBuilder.

Вывод у этой программы такой:

Str002Я специально вывел в конце длину строки sb, так как иногда результат был и 0мс. То есть конкатенация строк в классе StringBuilder в десятки раз выше.

Теперь разберемся почему же так происходит.

Я уже писал, что в случае использования объектов класса String в цикле конкатенации строки, на каждой итерации создается новый объект класса String, что влечет за собой достаточно большие накладные расходы на выделение памяти для объекта и переопределение ссылок на него.

В случае же объекта класса StringBuilder такого не происходит и как видите скорость работы в десятки раз выше.

Кроме всего прочего, конкатенация строк класса String при помощи перегруженного оператора плюс (+) не явно использует класс StringBuilder, а это означает, что в цикле, каждый раз создается новый объект не только класса String, но еще и класса StringBuilder. Вот от сюда и такие накладные расходы.

Чтобы понять еще глубже, что происходит в данной программе, можно ее декомпилировать программой javap, входящей в состав JDK.

Это можно сделать так: javap -c String012 > String012.txt

В данном случае мы перенаправили вывод работы javap в текстовый файл String012.txt, иначе бы вывод произошел на экран, что не очень удобно для анализа.

Файл получается достаточно многострочный, поэтому тут буду приводить его частями. И так! Первый цикл в студию!

Str003 

Как видите, хотя в цикле создания строки sl, класс StringBuilder у нас вообще не упоминается, но компилятор решил все равно его использовать, поскольку он работает намного эффективнее. И как я и говорил каждый раз в цикле создаются объекты классов String и StringBulder. Ровно тоже самое происходит и для строки so, поэтом отдельно ее рассматривать не будем. А вот создание строки класса StringBuilder рассмотреть интересно:

Str004

Тут, как говорится, совсем другой кордебалет. Что называется почувствуйте разницу :) В данном случае объект каждый раз не создается в цикле. Просто к одной и той же строке (объекту) в цикле добавляется "1".

Grabli

Это мы рассмотрели самые большие грабли, которые могут быть при конкатенации строк. Но есть еще и поменьше :)

Они даже рассмотрены в оригинальной документации Oracle.

Мы их быстренько рассмотрим на таких же простых примерах и двинемся дальше.

 

Str00031

Эта простая программа генерирует следующий вывод:

Str00032

Собственно на примере все понятно, что в первом случае сперва произошло вычисление операции 2+3, а затем преобразование всего выражения к строке, в соответствии с порядком проведения вычислений. Во втором случае же, сперва произошло приведение всего выражения к строке.

4 июн. 2015 г.

Строки. Часть 3 – создание строк, классы StringBuilder и StringBuffer.

В JDK 5 в дополнение к существующим богатым возможностям обработки строк Java появился новый строковый класс. Этот новый класс называется StringBuilder. Он идентичен StringBuffer за исключением одного важного отличия: он не синхронизирован, что означает, что он не является безопасным в отношении потоков. Выгода от применения StringBuilder связана с более высокой производительностью. Однако в случае разработки многопоточных программ вы должны использовать StringBuffer, а не StringBuilder.

Класс StringBuilder

Объекты класса StringBuilder — это строки переменной длины. Только что созданный объект имеет буфер определенной емкости (capacity), по умолчанию достаточной для хранения 16 символов. Емкость можно задать в конструкторе объекта.

Как только буфер начинает переполняться, его емкость автоматически увеличивается, чтобы вместить новые символы.

В любое время емкость буфера можно увеличить, обратившись к методу

ensureCapacity(int minCapacity);

Этот метод изменит емкость, только если minCapacity будет больше длины хранящейся в объекте строки. Емкость будет увеличена по следующему правилу. Пусть емкость буфера равна N. Тогда новая емкость будет равна

Max(2 * N + 2, minCapacity)

Таким образом, емкость буфера нельзя увеличить менее чем вдвое.

Методом setLength(int newLength) можно установить любую длину строки. Если она окажется больше текущей длины, то дополнительные символы будут равны ' \u0000'. Если она будет меньше текущей длины, то строка окажется обрезанной, последние символы потеряются, точнее, будут заменены символом ' \u0000' . Емкость при этом не изменится.

Если число newLength окажется отрицательным, возникнет исключительная ситуация. Поэтому будьте осторожны, устанавливая новую длину строки.

Количество символов в строке можно узнать так же, как и для объекта класса String, методом length() , а емкость — методом capacity() .

Создать объект класса StringBuilder можно только конструкторами. То есть вы не можете создать строку класса StringBuilder операцией присваивания как мы это делали для класса String.

В классе StringBuilder определены четыре конструктора:

  • StringBuilder() — создает пустой объект с емкостью 16 символов;
  • StringBuilder(int capacity) — создает пустой объект заданной емкости capacity;
  • StringBuilder(String str) — создает объект емкостью str.length() + 16, содержащий строку str;
  • StringBuilder(CharSequence str) — создает объект, содержащий строку str.

Чтобы понять как создавать строки класса StringBuilder рассмотрим простой пример:

Str00012

Данная программа генерирует следующий вывод:

Str00013

Тут все очень просто, strBld1 и strBld2 это пустые строки, которые я не стал выводить на консоль, а вывел только их длину и емкость.

Строка strBld3 хоть и имеет длину 5 символов, но емкость объекта составляет 21 символ.

 

Совет: Начиная с этого момента я настоятельно рекомендую заглядывать в исходники JDK и смотреть код изучаемых классов, так как это весьма просветляет. Кроме того, я не смогу описать все методы всех классов, для этого есть сами классы и документация к ним, кою я вам и рекомендую регулярно читать.

Добавление подстроки – метод append()

Str00014

В классе StringBuilder есть более десяти методов append(), добавляющих подстроку в конец строки. Они не создают новый экземпляр строки, а возвращают ссылку на ту же самую, но измененную строку.
Основной метод append(String) присоединяет строку в конец данной строки. Если присоединяемая строка равна null, то добавляется строка "null".
Два аналогичных метода работают с параметром типа StringBuffer и CharSequence.
Шесть методов append(type) добавляют примитивные типы boolean, char, int, long, float, double, преобразованные в строку.
Два метода присоединяют к строке массив char и подмассив символов, преобразованные в строку: append(char[]) и append(char[], int, int).
Есть метод добавляющий к строке кодовую точку (code point) – appendCodePoint(int).

Метод, append(Object obj), добавляет просто объект. Перед этим объект obj преобразуется в строку своим методом toString().

Ну и как всегда немного практики:

Str00015

Собственно этот пример тоже очень простой и особо пояснять тут почти нечего.

Единственное что стоит отметить, так это использование собственного класса String0009, код которого я приведу чуть ниже.

Добавление даты, и данных класса String009 я сделал, чтобы продемонстрировать, что на объектах при их добавлении вызывается их метод toString().

И кстати сказать, добавление кодовой точки в строку при помощи appendCodePoint() куда проще чем то-же самое мы делали в классе String.

Код класса String009 очень простой и не замысловатый, хотя сейчас он может показаться вам не понятным, но потерпите немного, скоро мы доберемся и до классов.

Str00016

Два этих класса генерируют следующий вывод:

Str00017

 

 

 

Вставка подстроки – метод insert()

Str00018

Более десяти методов insert() предназначены для вставки строки, указанной вторым параметром метода, в данную строку. Место вставки задается первым параметром метода, индексом символа строки, перед которым будет сделана вставка. Он должен быть неотрицательным и меньше длины строки, иначе возникнет исключительная ситуация. Строка раздвигается, емкость буфера при необходимости увеличивается. Методы возвращают ссылку на ту же самую, но преобразованную строку.
Основной метод insert(int, String) вставляет строку в данную строку перед ее символом с индексом int. Если ссылка str == null, вставляется строка "null".
Метод sb.insert(sb.length(), "xxx") будет работать так же, как метод sb.append("xxx").

Шесть методов insert(int, type elem) вставляют примитивные типы boolean, char, int, long, float, double, преобразованные в строку.

Два метода вставляют массив char и подмассив char символов, преобразованные в строку.

Оставшиеся методы вставляют Object преобразованный в строку и объекты класса CharSequence.

Немножко практики:

Str00019

Данная программа генерирует следующий вывод:

Str00020

 

 

 

 

 

 

Удаление подстроки или символа – методы delete() и deleteChar()

Str00021

Метод delete(int beging, int end) удаляет из строки символы, начиная с индекса begin включительно до индекса end исключительно; если end больше длины строки, то до конца строки. Если begin отрицательно, больше длины строки или больше end, возникает исключительная ситуация. Если begin == end, удаление не происходит.

Метод deleteCharAt(int ind) удаляет символ с указанным индексом ind. Длина строки уменьшается на единицу. Если индекс ind отрицателен или больше длины строки, возникает исключительная ситуация. Допишем предыдущий пример, дабы не плодить классы без необходимости:

Str00022

Вывод программы:

Str00023

 

 

 

 

 

 

 

Переворот строки – метод reverse()

Метод reverse() меняет порядок расположения символов в строке на обратный.

Поиск подстроки в строке – методы indexOf() и lastIndexOf()

Str00024

Метод indexOf(String str) выполняет поиск первого вхождения str. Возвращает индекс позиции совпадения или -1 в случае неудачи.

Метод indexOf(String str, int startIndex) выполняет поиск первого вхождения str, начиная с startIndex. Возвращает индекс позиции совпадения или -1 в случае неудачи.

Метод lastIndexOf(String str) выполняет поиск последнего вхождения str. Возвращает индекс позиции совпадения или -1 в случае неудачи.

Метод lastIndexOf(String str, int startIndex) выполняет поиск последнего вхождения str, начиная с startIndex. Возвращает индекс позиции совпадения или -1 в случае неудачи.

Str00025

Вывод программы:

Str00026

В этой версии программы все тоже достаточно просто, поэтому, я надеюсь, она не требует особых объяснений.

Единственное что хочется еще раз напомнить, что строки в классах StringBuilder и StringBuffer изменяемые и при изменениях не создается нового объекта, а меняется существующий.

Ну вот мы и рассмотрели все методы класса StringBuilder. Далее рассмотрим методы класса StringBuffer. Класс StringBuffer имеет все те же методы, что и класс StringBuilder, но так же еще имеет несколько других, поэтому мы рассмотрим только их.

Класс StringBuffer

Получение подстроки из строки – метод substring()

Вы можете получить часть StringBuffer вызовом substring(). Этот метод возвращает строку класса String и имеет две следующие формы:

substring(int startIndex)
substring(int
startIndex, int endIndex)

Первая форма возвращает подстроку, которая начинается от startIndex и продолжается до конца объекта StringBuffer. Вторая форма возвращает подстроку от позиции startIndex до endIndex-1.

Например данный код:

StringBuffer strBfr1 = new StringBuffer("Подстрочка");
println("substring(int) = " + strBfr1.substring(3));
println("substring(int, int) = " + strBfr1.substring(0, 3));

выдаст следующий вывод:

substring(int) = строчка
substring(int, int) = Под

Далее есть несколько методов работы с кодовыми точками Unicode:

Str00027

Методы codePointAt() и  codePointBefore() возвращают значение типа int которое представляет кодовую точку Unicode или ее часть для указанного в параметре вызова индекса (символа). Метод codePointCount() возвращает число кодовых точек строки, заключенной между первым и вторым индексами.

Метод offsetByCodePoints(int start, int num) возвращает индекс символа в строке, которая находится на num точек кода позади начального индекса, указанного в start.

И остался еще метод trimToSize() который уменьшает размер символьного буфера объекта с тем, чтобы он соответствовал текущему содержимому.

Все вышеперечисленные методы работы с кодовыми точками и trimToSize() были добавлены в JDK 5.

Ну и для закрепления материала примерчик:

Str00028

Вывод у программы следующий:

Str00029

Первые три строки создают строку и затем выделают из нее подстроки.

Затем начинается самое интересно с кодовыми точками. Я подсветил желтым на что надо обратить особое внимание.

Хотя всего в строке 5 символов (ракета, три минуса и монорельс), длина же строки показывает 7 символов (char), это происходит потому, что два символа, ракета и монорельс состоят из двух char.

Далее смотрим сюда внимательно:

Str00030

И сравниваем коды монорельса и каждого из его двух char с тем что выводит программа.

Ну и на последок показана работа метода trimToSize().

2 июн. 2015 г.

Строки. Часть 2 – создание строк, класс String.

Создание строк класса String. Разница между оператором присваивания (=) и new.

Теперь рассмотрим подробнее создание строк в Java. Как я говорил в предыдущей статье есть два способа создания строки (объекта класса String):

  • при помощи оператора присваивания (=)
  • при помощи оператора new

Между этими двумя способами есть разница и она заключается в том, каким образом выделяется память для этих строк и как присваиваются ссылки на эти переменным класса String.

Для строк созданных при помощи оператора присваивания и строкового литерала в Java существует специальный механизм хранения в "отдельной" области памяти называемой строковый пул (string pool). До JDK 7 это были разные области памяти. Начиная с Oracle JDK 7, string pool хранится в общем heap'е.

Если существует два или более одинаковых строковых литерала, то для них в памяти выделается место только для одного (так как нет смысла хранить несколько одинаковых строк), но ссылки на этот объект могут быть присвоены любому количеству строковых переменных. Это позволяет уменьшить использование памяти виртуальной машины и в какой-то степени оптимизировать работу с часто используемыми строками (как вы помните строки в объектах класса String неизменяемые).

Когда же строка создается при помощи оператора new, то для каждой строки, даже если они одинаковые, выделяется своя область памяти в куче (heap), так как оператор new создает новый объект и выделяет место для него в памяти виртуальной машины.

Для того чтобы разобраться с этим получше рассмотрим простую программу:

Данная программа генерирует следующий вывод:

Str00002

Статический метод iHash это просто обертка для System.identityHashCode. Я его создал только для того, чтобы код стал более наглядным. Этот метод возвращает хэш объекта, который не следует путать с хэшем строки хранящейся в объекте, возвращает который метод hashCode.

И так мы видим что строки на которые ссылаются s1, s2 и s3 это один и тот же объект в памяти, что видно по его хэшу.  s1 и s2 созданы при помощи строковых литералов, которые у нас в данном случае одинаковые – это слово "Hello".  s3 создан при помощи присвоения ссылки и как видите, опять же, указывает на тот же объект в памяти.

А вот s4 и s5 хотя и создают строку с одинаковым содержанием, но ссылаются на разные объекты в памяти, то есть оператор new породил два новых объекта в памяти. s6 создан присвоением ссылки и поэтому указывает на тот же объект, что и s5, то есть новый объект не был создан.

Чтобы стало еще более понятно приведу следующую диаграмму:

Str00003

Важно понять, что оператор new порождает новые объекты в памяти в любом случае, есть там такая же строка или нет, в то время как оператор присваивания (=) создает новый объект в string pool, только в том случае если там нет такой же строки. Хотя при помощи метода intern() класса String можно сделать, что оператор new будет работать так же, как и оператор присваивания.

Строки с 12 по 30 создают в памяти строки показанные на рисунке и выводят их на консоль.

Далее в строке 32 присваиванием s3 = "world"; мы создаем новый обект в string pool, что собственно видно по выводу программы. И хотя строки относятся к ссылочным типам данных изменения строк s1 и s2 не произошло, а вместо этого был создан новый объект (строка).

Когда мы в 38 строке присваиваем s6 строку "Hello", то мы таким образом переопределяем эту ссылку так, что она начинает указывать на "Hello" в string pool. Создания нового объекта в памяти опять не произошло.

Вызов метода intern() для s4 в строке 43 организует поиск строки содержащейся в s4 в string pool и при положительном результате возвращает ссылку на найденную строку, а при отрицательном – заносит значение (строку) в пул и возвращает ссылку на него. В нашем случае s4 указывает на строку "Hello", которая так же уже есть в string pool. Поэтому просто произошло перенаправление ссылки, то есть s4 теперь ссылается на тот же объект (строку) что и s1 к примеру.

Далее в строке 48 мы сперва создали строковый литерал «World» и сразу же вывели его на консоль, не присваивая ссылку на эту строку ни какой переменной, и все же, даже не смотря на это строка была размещена в пуле. Затем мы эту же строку присвоили переменной s7 и как видите это один и тот же объект, что мы выводили на консоль (сравните хэши объектов).

Затем в строке 53 мы изменили поведение оператора new, т.е. не был создан новый объект (строка) в следствии применения метода intern(). Метод intern() перед созданием объекта String смотрит есть ли этот объект в string pool и возвращает его. Иначе создается новый объект в string pool.

Ну и в конце у нас небольшой простенький и наивный тест производительности создания большой строки расположенной в string pool и heap. Как видно string pool работает помедленней чем heap. Но это на разных машинах может быть по разному и бенчмарки лучше делать несколько по другому, например использовать инструмент JMH: http://openjdk.java.net/projects/code-tools/jmh/

Вообще так, в цикле, делать конкатенацию строк категорически не рекомендуется, так как это порождает множество объектов в памяти, что очень не очень. Данный пример был приведен просто чтобы сравнить производительность.

Создание строк при помощи оператора new.

Класс String предоставляет вам более десяти конструкторов для создания строк, здесь мы перечислим лишь некотоыре:

  • String() — создается объект с пустой строкой;
  • String(String str) — конструктор копирования: из одного объекта создается его точная копия, поэтому данный конструктор используется редко;
  • String(StringBuffer str) — преобразованная копия объекта класса StringBuffer;
  • String(StringBuilder str) — преобразованная копия объекта класса StringBuilder;
  • String(byte[] byteArray) — объект создается из массива байтов byteArray;
  • String(char[] charArray) — объект создается из массива charArray символов Unicode;
  • String(byte[] byteArray, int offset, int count) — объект создается из части массива байтов byteArray, начинающейся с индекса offset и содержащей count байтов;
  • String(char[] charArray, int offset, int count) — то же, но массив состоит из символов Unicode;
  • String(int[] intArray, int offset, int count) — то же, но массив состоит из символов Unicode, записанных в массив целого типа, что позволяет использовать символы Unicode, занимающие больше двух байтов;
  • String(byte[] byteArray, String encoding) — символы, записанные в массиве байтов, задаются в Unicode-строке с учетом кодировки encoding;
  • String(byte[] byteArray, int offset, int count, String encoding) — то же самое, но только для части массива;
  • String(byte[] byteArray, Charset charset) — символы, записанные в массиве байтов, задаются в Unicode-строке с учетом кодировки, заданной объектом charset;
  • String(byte[] byteArray, int offset, int count, Charset charset) — то же самое, но только для части массива.

При неправильном задании индексов offset, count или кодировки encoding возникает исключительная ситуация.

Конструкторы, использующие массив байтов byteArray, предназначены для создания Unicode-строки из массива байтовых ASCII-кодировок символов. Такая ситуация возникает при чтении ASCII-файлов, извлечении информации из базы данных или при передаче информации по сети.

В самом простом случае компилятор для получения двухбайтовых символов Unicode добавит к каждому байту старший нулевой байт. Получится диапазон ‘ \u0000′ — ‘ \u00FF’ кодировки Unicode, соответствующий кодам Latin1. Тексты, записанные кириллицей, будут выведены неправильно.

Если же на компьютере сделаны местные установки, как говорят на жаргоне «установлена локаль» (locale) (в MS Windows это выполняется утилитой Regional Options (Язык и стандарты) в окне Control Panel (Панель управления)), то компилятор, прочитав эти установки, создаст символы Unicode, соответствующие местной кодовой странице. В русифицированном варианте MS Windows это обычно кодовая страница CP1251.

Если исходный массив с кириллическим ASCII-текстом был в кодировке CP1251, то строка Java будет создана правильно. Кириллица попадет в свой диапазон ‘ \u0400′ — ‘ \u04FF’ кодировки Unicode.

Но у кириллицы есть еще по меньшей мере четыре кодировки:

  • в MS-DOS применяется кодировка CP866;
  • в UNIX обычно применяется кодировка KOI8-R;
  • на компьютерах Apple Macintosh используется кодировка MacCyrillic;
  • есть еще и международная кодировка кириллицы ISO8859-5.

Например, байт 11100011 (0xE3 — в шестнадцатеричной форме) в кодировке CP1251 представляет кириллическую букву Г, в кодировке CP866 — букву у, в кодировке KOI8-R — букву Ц, в ISO8859-5 — букву у, в MacCyrillic — букву г. Если исходный кириллический ASCII-текст был в одной из этих кодировок, а местная кодировка — CP1251, то Unicode-символы строки Java не будут соответствовать кириллице.

В этих случаях применяются последние четыре конструктора, в которых параметром encoding или charset указывается, какую кодовую таблицу использовать конструктору при создании строки.

Это мы все говорили о создании строки из массива типа byte. Теперь, чтобы все это уложилось получше в мозгах, надо попрактиковаться.

Эта программа генерирует следующий вывод:

Str00004

Строка в кодировке 1251 в обоих случаях выводится правильно, так как в первом случае компилятор использовал кодировку ОС Windows, а во втором мы ему указали ее явно.

Строки же созданные без указания кодировки и содержащие слово "Россия" в кодировках отличных от 1251 вывелись не правильно, так как компилятор преобразовал их в строки Unicode, считая что это кодировка 1251, хотя это было не так. Собственно поэтому они вывелись не правильно. Если до сих пор не понятно почему это так, то рекомендую освежить в памяти эту статью.

В случае же когда мы указали кодировку все строки вывелись правильно.

Для пущего понимания рекомендую, так же, освежить в памяти и эту статью. Тем более она нам сейчас пригодится.

Далее рассмотрим создание строки из массива символов char. Хотя мы это уже делали несколько раз, но повторенье – мать ученья.

Str00005

Эта простая программа генерирует следующий вывод:

Str00006

Строка s1 была создана из массива char c, а строка s2 из части этого массива.

При создании строки использовался синтаксис указания первого символа (offset) с которого надо начать создавать строку и затем указано количество символов (count) которые надо скопировать, включая начальный и конечный.

А теперь перейдем к самому интересному, созданию строк, которые состоят из символов расширенного Unicode.

Напомню, что примитивный тип char имеет размер два байта. И этими двумя байтами можно представить 65536 символов.

Для того чтобы понимать тип char, надо иметь представление о принципах кодировки Unicode. Кодировка Unicode была изобретена для преодоления ограничений традиционных символьных схем. До появления Unicode существовало несколько различных стандартов: ASCII, ISO 8859-1, KOI-8, GB18030, BIG-5 и т.д. При этом возникали две проблемы. Во-первых, один и тот же код в разных кодировках соответствовал различным символам. Во-вторых, в языках с большим набором символов использовался код различной длины: часто употребляющиеся символы представлялись одним байтом, другие знаки — двумя, тремя и большим количеством байтов.

Для решения этих проблем была разработана кодировка Unicode. В результате исследований, начавшихся в 80-х годах, выяснилось, что двухбайтового кода более чем достаточно для представления всех символов, использующихся во всех языках; при этом оставался достаточный резерв для любых мыслимых расширений. В 1991 г. была выпущена спецификация Unicode 1.0, в которой было использовано меньше половины из возможных 65536 кодов. В Java изначально были приняты 16-битовые символы Unicode, что стало еще одним преимуществом перед другими языками, использующими 8-битовые символы.

Однако впоследствии случилось непредвиденное: количество символов превысило допустимые 65536. Причиной тому стали чрезвычайно большие наборы иероглифов китайского, японского и корейского языков. Поэтому в настоящее время 16-битового типа char недостаточно для описания всех символов Unicode.

Чтобы понять, как эта проблема решается в Java, начиная с Java SE 5.0, надо ввести несколько терминов. Назовем кодовой точкой (code point) значение, связанное с символом в схеме кодирования. Согласно стандарту Unicode, кодовые точки записываются в шестнадцатеричном формате и предваряются символами U+. Например, для буквы A кодовая точка равна U+0041. В Unicode кодовые точки объединяются в 17 кодовых плоскостей (code plane). Первая кодовая плоскость, называемая основной многоязыковой плоскостью (basic multilingual plane), состоит из “классических” символов Unicode с кодовыми точками от U+0000 до U+FFFF. Шестнадцать дополнительных плоскостей с кодовыми точками от U+10000 до U+10FFFF содержат дополнительные символы (supplementary character). Если сказать по простому, то кодовая точка (code point) представляет один символ в расширенной кодировке Unicode.

Кодировка UTF-16 — это способ представления всех кодов Unicode последовательностью переменной длины. Символы из основной многоязыковой плоскости представляются 16-битовыми значениями, называемыми кодовыми единицами (code unit). Дополнительные символы обозначаются последовательными парами кодовых единиц. Каждое из значений пары попадает на используемую 2048-байтовую область основной многоязыковой плоскости, называемой областью подстановки (surrogates area); от U+D800 до U+DBFF для первой кодовой единицы и от U+DC00 до U+DFFF для второй кодовой единицы. Такой подход позволяет сразу определить, соответствует ли значение коду конкретного символа или является ли частью кода дополнительного символа. Например, математическому коду символов, обозначающему множество целых чисел, соответствует кодовая точка U+1D56B и две кодовых единицы, U+D835 и U+DD6B (описание алгоритма кодирования можно найти по адресу http://en.wikipedia.org/wiki/UTF-16 и на русском https://ru.wikipedia.org/wiki/UTF-16).

Собственно от сюда и пошло понятие суррогатных пар в Java, которыми представляются символы расширенного Unicode. А теперь потанцуем попрактикуемся :)

Str00008

Теперь мы на практике реализовали то, что было описано в пяти абзацах выше.

Данная программа генерирует следующий вывод:

Str00009

Обратите внимание, что для того чтобы увидеть это у вас консоль должна поддерживать Unicode, а так же должна быть переключена в эту кодировку. Ну и кроме того надо при запуске использовать ключ -Dfile.encoding=UTF-8.

В первых двух строках мы задаем парой кодовых единиц (суррогатной парой)код математического символа, обозначающего множество целых чисел (Z). Затем мы по отдельности выводим эти символы на консоль, но сами по себе они не представляют ни какого символа, а только вместе. Поэтому для того чтобы вывести этот символ, эту сладкую парочку надо преобразовать кодовую точку в массиве типа int, так как int в два раза больше char, и уж затем, этот массив загнать в строку :). Да вот такой кордебалет :). Чтобы стало все чуть более понятно посмотрите сюда:

Str00007

Далее в программе, просто для примера, я создал еще строчку из символов планет и вывел ее на консоль.

Ну вот мы рассмотрели создание строк из массива byte, char и int. Остались только StringBuffer и StringBuilder, но с ними все просто, хотя для порядка мы рассмотрим и их тоже, но чуть позже.

Создание строк на основе различных типов.

Примитивные типы и объекты в Java могут быть превращены в строки. Какие строки будут для примитивных типов это достаточно очевидно, а вот для объектов это находится под контролем самих объектов. Мы можем получить строковое представление примитивного типа или объекта при помощи статического метода String.valueOf(). Например:

String iS = String.valueOf(1);      // строка из int
String dS = String.valueOf(2.22); // строка из double
String fS = String.valueOf(3.33f); // строка из foat
String bS = String.valueOf(true); // строка из boolean

Все объекты в Java имеют метод toString(), который наследуется от класса Object. Для некоторых объектов этот метод возвращает полезный результат, который показывает содержимое объекта. Для объектов не предоставляющих такой результат, использование их в качестве аргумента метода valueOf() даст строку, которая просто является уникальным идентификатором этого объекта, которую можно использовать для отладки. Метод String.valueOf() при вызове для объекта вызывает метод toString() этого объекта и возвращает результат. Одним из реальных отличий использования этого метода является то, что если вы передадите в него объект с нулевой ссылкой, то он вернет вам "null" (строку)  класса String, вместо создания исключения NullPointerExeption при использовании метода toString() для этого объекта.

Конкатенация (сцепление) строк, так же, не явно, использует метод valueOf(), поэтому вы можете "добавлять" к строке объект или примитив:

Date today = new Date();
System.out.print("Сегодня: "+ today);

Иногда вы можете увидеть, как некоторые программисты, для сокращения, используют пустую строку и оператор сложения (+) для получения строкового значения объекта:

String st = ""+2.22;

Ну и немножко практики, дабы все стало понятнее.

Str00010

Данная программа генерирует следующий вывод:

Str00011

Она достаточно простая и я думаю не требует особых объяснений.

Единственное на что можно обратить внимание так это на то, каким образом выводятся ссылки на массивы.

А так же на вывод ссылки символьного массива и его содержимого.

Ну и еще рекомендую посмотреть исходный код этих методов класса String.

На этом создание строк класса String закончим.