Spiral group
_________________________________________________________________________________
< Раздел “Язык Java” > <
Раздел “Технологии Java” > < Основная страница >
Строки в Java. JDK 1.6.
Статья опубликована: 31.01.2007
Последнее изменение: 18.03.2007
Строковый тип (в программировании) — это тип данных, значениями которого является произвольная последовательность символов алфавита. Каждая переменная такого типа может иметь произвольную длину.
— строки могут иметь достаточно существенный размер (до нескольких десятков мегабайт);
— изменяющийся со временем размер – возникают трудности с добавлением и удалением символов.
Введение в строки
В представлении строк в памяти компьютера существует два принципиально разных подхода: представление массивом символов и представление с помощью завершающего байта.
Представление массивом символов.
В этом подходе строки представляются массивом символов; при этом размер массива хранится в отдельной (служебной) области. От названия языка Pascal, где этот метод был впервые реализован, данный метод получил название Pascal Strings.
— программа в каждый момент времени «знает» о размере строки, и операции копирования и получения размера строки выполняются достаточно быстро;
— каждый символ строки может изменяться;
— на программном уровне можно следить за выходом за границы строки при её обработке;
— возможно быстрое выполнение операции вида «взятие N-ого символа с конца строки».
— проблемы с хранением и обработкой символов произвольной длины;
— увеличение затрат на хранение строк — значение «длина строки» так же занимает место и в случае большого количества строк маленького размера может существенно увеличить требования алгоритма к оперативной памяти;
— ограничение максимального размера строки. В современных языках программирования это ограничение скорее теоретическое, так как обычно размер строки хранится в 32-битовом поле, что даёт максимальный размер строки в 2 147 483 647 байт (2 гигабайта).
Представление с помощью «завершающего байта».
Одно из возможных значений символов алфавита (как правило, это символ с кодом 0) выбирается в качестве признака конца строки, и строка хранится как последовательность байтов от начала до конца. Есть системы, в которых в качестве признака конца строки используется не символ 0, а байт 0xFF (255) или код символа «$».
Метод имеет три названия — ASCIIZ (символы в кодировке ASCII с нулевым завершающим байтом), C-strings (наибольшее распространение метод получил именно в языке Си) и метод нуль-терминированных строк.
— отсутствие дополнительной служебной информации о строке (кроме завершающего байта);
— возможность представления строки без создания отдельного типа данных;
— отсутствие ограничения на максимальный размер строки;
— экономное использование памяти;
— простота получения суффикса строки;
— возможность использовать алфавит с произвольным размером символа (например, UTF-8).
— долгое выполнение операций получения длины и конкатенации строк;
— отсутствие средств контроля над выходом за пределы строки, в случае повреждения завершающего байта возможность повреждения больших областей памяти, что может привести к непредсказуемым последствиям — потере данных, краху программы и даже всей системы;
— невозможность использовать символ завершающего байта в качестве элемента строки.
В языке Java применяется первый вид
представления в виде массива символов и две его разновидности:
— представление в виде массива
символов, инкапсулированного в объект специально предназначенного класса для
работы со строками.
— представление в виде массива
байтов, которые выражают коды символов (применяется только для восьмибитной
кодировки, которая заранее известна). Используется крайне редко, только в
определенных ситуациях для повышения производительности.
Представления символов в зависимости от кодировки
— Переключение языка управляющими кодами. Метод не стандартизирован и лишает текст самостоятельности (то есть последовательность символов без управляющего кода в начале теряет смысл); использовался в некоторых ранних русификациях ZX-Spectrum и БК.
— Использование двух или более байт для представления каждого символа (UTF-16, UTF-32). Главным недостатком этого метода является потеря совместимости с предыдущими библиотеками для работы с текстом при представлении строки как ASCIIZ. Например, концом строки должен считаться уже не байт со значением 0, а два или четыре подряд идущих нулевых байта, в то время как одиночный байт «0» может встречаться в середине строки, что сбивает библиотеку «с толку».
— Использование кодировки с переменным размером символа. Например, в UTF-8 часть символов представляется одним байтом, часть двумя, тремя или четырьмя. Этот метод позволяет сохранить частичную совместимость со старыми библиотеками (нет символов 0 внутри строки и поэтому 0 можно использовать как признак конца строки), но приводит к невозможности прямой адресации символа в памяти по номеру его позиции в строке.
В языке Java, в отличие от некоторых других
языков, ни строка, ни массив типа char не заканчиваются ‘\u0000’
(символом NUL). Внутреннее представление символов строки в Java хранится в кодировке Unicode, и,
поскольку, основной недостаток (завершающий символ) отсутствует, то это
является наилучшим вариантом.
Классы для работы со строками в Java
Строки
в Java являются стандартными объектами со встроенной языковой поддержкой. Пакет java.lang содержит несколько
классов для работы со строками: String, StringBuffer,
StringBuilder.
Класс String
Класс String представляет собой строку
в формате UTF-16, в которой дополнительные
символы представлены в виде суррогатной пары (см. класс Character
). Значения индексов ссылаются кодовые блоки char
,
так как дополнительный символ использует две позиции в строке
типа
String
.
Класс String снабжен методами для
работы с кодовыми точками Unicode (так
называемыми символами) и в дополнении к этим методам имеет методы для работы с
кодовыми блоками Unicode (так называемыми значениями символа). Начиная
с JDK версии
1.5 класс String поддерживает
32 битные коды символов Unicode.
Данный
класс используется для строк фиксированной длины, которые не будут изменяться в
процессе работы программы. Нельзя ни вставить новые
символы в уже существующую строку, ни поменять в ней одни символы на другие.
Добавить одну строку в конец другой тоже нельзя.
В этом классе есть методы, которые позволяют сравнивать
строки, осуществлять в них поиск и извлекать определенные символы и подстроки,
создавать копию строки с приведением всех символов к верхнему или нижнему
регистру.
Данный класс является наиболее употребимым для создания
строк.
Класс StringBuffer
Класс StringBuffer представляет собой потокозащищенную, изменяемую последовательность символов. StringBuffer используется для строк переменной длины, которые могут изменяться во время работы программы.
Строки типа StringBuffer является
защищенной при использовании нескольких потоков. Это означает, что операция со
строкой одним потоком гарантированно завершится до того момента, как другой
поток начнет оперировать с этой же строкой.
Наиболее
важными методами данного класса являются семейства методов append
и
insert
, которые перегружены для поддержки основных типов.
Каждая строка типа StringBuffer имеет емкость и длину. Эти понятия различны по своей сути. Если длина символьной последовательности содержащейся в строке – это количество символов, то емкость – это количество места в символах, выделяемого для строки в оперативной памяти. Если длина не превышает емкости, то нет необходимости выделять новое пространство в памяти для хранения символьной последовательности. Если внутренний буфер переполняется, то он автоматически расширяется.
Класс StringBuilder
Синхронизация для защиты объекта, при одновременном доступе потоков к нему, исключает состояние неопределенности, но оказывается дорогой с точки зрения вычислительных ресурсов. Поэтому в JDK 1.5 был добавлен еще один класс StringBuilder для работы со строками в одном потоке, в отличие от класса StringBuffer, который может использоваться в многопоточном режиме. Этот класс идентичен StringBuffer и поддерживает все методы класса StringBuffer, но выполняется в ряде случаев быстрее при отсутствии синхронизации.
Достаточно большое количество аспектов программирования в Java на каком-либо этапе подразумевает использование классов String, StringBuffer, StringBuilder. Они необходимы при отладке, работе с текстом, указании имен файлов и адресов URL и часто выступают в качестве аргументов методов.
String, StringBuffer и StringBuilder объявлены как final, что означает, что ни от одного из этих классов нельзя
производить подклассы. Это было сделано для того, чтобы можно было применить
некоторые виды оптимизации, позволяющие увеличить производительность при
выполнении операций обработки строк.
Экземпляр любого из этих классов по определению нельзя воспринимать как массив символов. Другими словами следующие переменные s1 и s2
String s1;
char[] s2;
это различные по своей сути переменные. Хотя если
заглянуть в исходные коды классов для работы со строками, можно увидеть, что
они содержат внутри себя символьный массив и многие операции внутри этих
классов оперируют символами этого массива. Но, тем не менее, экземпляры классов
для строк не являются массивами и не могут применить для возврата символ с заданным индексом с помощью оператора []. Поэтому
операция
char c = s1[2];
будет
неверной и вместо этого следует использовать метод charAt(int), который
есть во всех трех классах. Принимая целое значение индекса символа в строке, он
возвращает этот символ. Следует помнить, что индексация строки начинается с
нуля. Например,
char c = s1[0];
возвратит
первый символ непустой строки.
Символьные последовательности и строки
По поводу массивов символов можно добавить то, что сам массив символов char (но не его элементы) является объектом и поэтому для него доступны методы, относящиеся к классу java.util.Arrays, которые позволяют сортировать, искать производить заполнение в массивах. Кроме этого, класс java.lang.System предоставляет метод для копирования массива.
— если операции с символьной последовательностью критичны по времени, ожидается многократное изменение строки, возможна работа с кодовыми блоками кодовых точек Unicode, то лучше использовать StringBuffer (в многопоточном режиме) и StringBuilder (в однопоточном режиме).
—
в том случае, если операции с символьной последовательностью критичны по
времени, операции проводятся с кодами символов, кодировка символов заранее
известна и позволяет уместить все виды символов в размерность типа byte,
то можно прибегать к массивам типа byte.
Создание
строк
Строковые объекты могут создаваться явно, с помощью конструктора. Кроме этого строки могут создаваться и неявным образом:
— при помощи строкового литерала;
— за счет выполнения оператора конкатенации + над двумя объектами String;
— за счет выполнения оператора конкатенации с присваиванием += над двумя объектами String.
Строковый литерал состоит из ноля или большего количества символов, заключенных в двойные кавычки. Java создает объект для каждого стокового литерала, однако внешне это никак не проявляется.
Итак, строка типа String может быть создана непосредственно строковым литералом,
String str = "World";
что эквивалентно созданию строки с помощью конструктора:
String str
= new String("World");
Строка может быть создана с помощью конкатенации строковых литералов:
String s = "Hello " + "world!";
или
String s = "Hello ";
s += "world!";
В
строках можно использовать все управляющие последовательности, а также восьмеричные/шестнадцатиричные
формы записи, которые определены для символьных литералов и отражены в
следующей таблице.
Управляющая последовательность |
Описание |
\ddd |
Восьмеричный символ (ddd) |
\uxxxx |
Шестнадцатиричный символ UNICODE (xxxx) |
\' |
Апостроф |
\" |
Кавычка |
\\ |
Обратная косая черта |
\r |
Возврат каретки (carriage return) |
\n |
Перевод строки (line feed, new line) |
\f |
Перевод страницы (form feed) |
\t |
Горизонтальная табуляция (tab) |
\b |
Возврат на шаг (backspace) |
Например:
class LiteralDemo
{
public static void
main(String[] args)
{
String s = "\"column1\" \t
\"column2\" \t \"column3\" \r\n";
s += "\u0061 \t\t \u0062 \t\t
\u0063";
System.out.println(s);
}
}
Поскольку конкатенация строк является неотъемлемой частью создания строк, то стоит упомянуть о некоторых вариациях конкатенации отдельно.
В ряде случаев, когда строка имеет большую длину, удобно записывать конкатенацию построчно. Это может пригодиться для динамического формирования HTML и XML кода, XSD строк, XPATH и SQL запросов и т.п. При этом следует учитывать, что строчные литералы в Java обязательно должны начинаться и заканчиваться в одной и той же строке исходного кода. В Java, в отличие от многих других языков, нет управляющей последовательности для продолжения строкового литерала на новой строке.
Например:
class StringConcatenateDemo
{
public static void main(String[] args)
{
int n = 100;
String s = " SELECT title"
+ " FROM table1, table1"
+ " WHERE N_ID = 5"
+ " LIMIT "
+ n;
System.out.println(s);
}
}
Можно создавать строку и путем конкатенации с присваиванием, но это не всегда продуктивно, кроме того, увеличивается исходный код и требуется следить за тем, чтобы каждая строка заканчивалась точкой с запятой, в отличие от предыдущего варианта:
class StringConcatenateDemo
{
public static void main(String[] args)
{
int n = 100;
String s = " SELECT title";
s += " FROM table1, table1";
s += " WHERE N_ID = 5";
s += " LIMIT ";
s += n;
System.out.println(s);
}
}
При применении конкатенации следует отличать оператор +
для конкатенации строк и оператор +
для арифметического сложения. Например, результатом вывода следующей
программы
class StringConcatenateDemo2
{
public
static void main(String[] args)
{
int prefix = 83;
int region_number =
112;
String post_code1 = "The post code1:
" + prefix + region_number;
String post_code2 = "The post code2:
" + (prefix + region_number);
System.out.println(post_code1);
System.out.println(post_code2);
}
}
будут строки:
The post code1: 83112
The post code2: 195
Вывод свидетельствует о том, что в выражении (prefix + region_number) оператор + воспринят компилятором как
оператор сложения из-за принципа старшинства операций ( ). В то же время, при
отсутствии скобок оператор + воспринимается как оператор
конкатенации строк.
Создание
строк с помощью строкового литерала, и операторов конкатенации возможно только
для класса String и не распространяется на класс StringBuffer
и StringBuilder. Строки этих дух типов создаются с помощью
оператора new. Конкатенация
для строк типа StringBuilder
или
StringBuffer
осуществляется с помощью метода append
()
. Следует отметить, что форма создания короткой
строки с помощью оператора + и +=
более удобна в чтении, чем ее эквивалент, записанный с явными вызовами
методов append(). Тем не менее, при создании больших строк с помощью
конкатенации, рекомендуется создавать строки с помощью классов StringBuilder
или
StringBuffer
и
производить конкатенацию методом
append
()
, поскольку в этих строках не требуется промежуточное создание
нового объекта из слагаемых строк.
Возникает закономерный вопрос: “А почему нельзя обойтись одним классом String, позволив ему вести себя примерно так же, как StringBuffer?” Все дело в производительности. Тот факт, что объекты типа String в Java неизменны, позволяет транслятору быстро выполнять ряд операций. Примером такой операции может служить возврат длины строки, которую вычислять не нужно, поскольку строка неизменна (вернее нужно, но только единожды при ее создании).
Строка типа String является неизменяемым объектом, но если есть необходимость ее изменить, то это можно сделать через создание нового объекта. Этот подход медленный и расходует немало временной памяти, но хорошо сочетается с концепцией “сборки мусора”.
Для изменяемых строк длина может меняться многократно, что требует дополнительных вычислительных затрат, но это с лихвой компенсируется появлением операций удаления, добавления, замены в рамках одной и той же строки без создания нового объекта.
Пустая
строка и null
Не следует забывать разницу между пустой строкой "" , не содержащей ни одного символа, и пустой ссылкой null, не указывающей ни на какую строку и не являющейся объектом. В языке Java null является нулевой ссылкой, представляемой пустым литеральным указателем, который сформирован из символов ASCII.
class NullStringDemo
{
public static void
main(String[] args)
{
String s1 = null;
String s2 = "";
System.out.println("s1="+s1);
System.out.println("s2="+s2);
}
}
Результат вывода:
s1=null
s2=
Пустую строку можно создать и с помощью конструктора без параметров, а также с помощью конструктора с аргументом "", поэтому следующие три строки кода приведут к одинаковому результату:
String
s1 = new String();
String
s2 = new String("");
String s3 = "";
В случае, если ваша строковая переменная присваивается значению null, необходимо в дальнейшем коде внимательно следить, чтобы не вызывался метод строкового класса, иначе закономерно произойдет выброс исключения NullPointerExeption (ситуация вызова метода для несуществующего объекта). Данной ситуации можно избежать, проинициализировав переменную перед вызовом метода, или (если вам сильно необходимо именно null) создать условный блок проверки для обхода кода с вызовом метода для строковой переменной.
Кроме того, строковая переменная может быть не инициализирована, и любые действия с ней могут привести к ошибке компиляции.
В
том случае, если вы хотите иметь пустую строку (и подразумеваете строку, не
содержащую ни одного символа) то следует пользоваться инициализацией "" . И в том случае, если метод length() возвращает 0 для данной строки, то ее
можно считать пустой. В JDK 1.6 также для этих целей добавился специальный метод isEmpty()
,
который проверяет является ли строка пустой и упрощает
исходный код.
class EmptyStringDemo
{
public static void
main(String[] args)
{
int len1, len2,
len3;
String s1;
String s2 = null;
//Следующие три строки приведут
//к одинаковому результату - созданию пустой строки
//String s2 = new String();
//String s2 = new String("");
String s3 = "";
//len1 = s1.length();
//Приведет к ошибке компиляции:
//variable s1 might not
have been initialized
//len2 = s2.length(); //Приведет к выбросу исключения
//java.lang.NullPointerException на этапе выполнения
if(s2 !=
null)
{
len2 = s2.length();
System.out.println("len2="+len2);
}
else
System.out.println("s2=null");
len3 = s3.length(); //(пустая строка, длина равна 0)
System.out.println("len3="+len3);
//Следующая строка выполнится только в JDK 1.6
//if(s3.isEmpty())
System.out.println("s3 is empty");
}
}
Вывод программы:
s2=null
len3=0
Не
допускается создание строки с помощью конструктора, в который в качестве
аргумента передается null. Это приведет к выбросу
исключения NullPointerException на
этапе выполнения программы. Например,
class StringConstructorNullDemo
{
public static void
main(String[] args)
{
String s1 = null;
String s2 = new String(s1);
}
}
Создание строк типа String с
помощью конструкторов
Класс String предоставляет следующие
конструкторы:
String() — создает объект с пустой
строкой;
Следующие конструкторы, используют массив байтов в качестве
аргумента, предназначены для создания Unicode-строки из массива байтов с
определенной кодировкой символов. Такая ситуация возникает при чтении файлов,
извлечении информации из базы данных или при передаче информации по сети.
String(byte[] bytes)
Создает объект String, декодируя
указанный массив байтов используя платформенный набор
символов по-умолчанию.
String(byte[] bytes, String charsetName) — создает объект String, декодируя
указанный массив байтов в указанный
набор символов.
String(byte[] bytes, Charset charset) — создает объект String, декодируя
указанный массив байтов в указанный
набор символов.
String
(byte [] byteArray, int offset, int count)
Создает объект String из части массива
байтов используя платформенную
кодировку по умолчанию;
String(byte[] bytes, int offset, int count, String charsetName)
— создает объект String, декодируя часть
массива байтов в указанный набор
символов.
String(byte[] bytes, int offset, int count, Charset charset) — создает объект String, декодируя часть
массива байтов в указанный набор
символов.
Следующие конструкторы создают объект String из массива символов Unicode;
String (char [] charArray) — создает объект String из массива символов Unicode;
String
(char [] charArray, int offset, int count) — создает объект String из
части массива символов Unicode;
Следующий пример демонстрирует один из этих конструкторов:
char chars[] = { 'a', 'b', 'с', 'd', 'e', 'f' };
String s = new String(chars,2,3);
System.out.println(s);
Этот
фрагмент выведет:
cde
Следующий конструктор создает объект String из массива кодовых точек.
String(int[] codePoints, int offset,
int count)
Создает объект String, который содержит символы из подмассива кодовых
точек Unicode. Аргумент
offset
является индексом первой кодовой точки подмассива, а
аргумент count
определяет длину подмассива. Содержимое подмассива конвертируется в
символы типа char. Последующее изменение массива кодовых
точек
не влияет на новую созданную строку.
IllegalArgumentException
если найдена неверная кодовая точка Unicode в
codePoints
При неправильном задании индексов offset, count во всех конструкторах, где они используются, возникает
исключительная ситуация IndexOutOfBoundsException
String (String original)
Создает объект из другого, поэтому
этот конструктор используется редко;
String (StringBuffer buffer)
Создает String объект из последовательности
символов содержащейся в аргументе StringBuffer;
String (StringBuilder
builder)
Создает String объект из последовательности символов содержащейся в аргументе StringBuilder. Содержимое строки StringBuilder копируется; дальнейшее изменение StringBuilder не влияет на новую созданную строку.
Получать
строку String из StringBuilder лучше с помощью метода toString
()
,
поскольку
выполняется быстрее.
Создание строк типа StringBuffer с помощью конструкторов
Объект StringBuffer можно создать без
параметров, при этом в нем будет зарезервировано место для размещения 16
символов. Вы также можете передать конструктору целое число, для того чтобы
явно задать требуемый размер буфера. И, наконец, вы можете передать
конструктору строку, при этом она будет скопирована в объект и дополнительно к
этому в нем будет зарезервировано место еще для 16 символов. Текущую длину StringBuffer можно определить,
вызвав метод length(), а для определения всего места,
зарезервированного под строку в объекте StringBuffer нужно воспользоваться
методом capacity(). Ниже приведен
пример, поясняющий
это:
class StringBufferDemo
{
public static void
main(String args[])
{
StringBuffer sb = new StringBuffer("Hello");
System.out.println("buffer
= " + sb);
System.out.println("length
= " + sb.length());
System.out. println("capacity
= " + sb.capacity());
}
}
Из вывода этой
программы, видно, что в объекте StringBuffer для манипуляций со
строкой зарезервировано дополнительное место.
buffer = Hello
length = 5
capacity = 21
Как было описано ранее, строка типа StringBuffer имеет емкость и длину, причем эти понятия разнятся между собой. Для управления длиной и емкостью подобной строки существуют методы ensureCapacity() и setLength(). Метод ensureCapacity() применяется в тех случаях, когда после создания объекта StringBuffer вы захотите зарезервировать в нем место для определенного количества символов. Это бывает полезно, когда вы заранее знаете, что вам придется добавлять к буферу.
Если вам вдруг понадобится в явном виде установить длину строки в буфере, воспользуйтесь методом setLength(). Если вы зададите значение, большее чем длина содержащейся в объекте строки, этот метод заполнит конец новой, расширенной строки символами с кодом 0.
Абстрактный класс CharBuffer
В
JDK 1.4
был введен новый класс CharBuffer, который предназначен для ряда
операций, которые несвойственны строковым классам, массивам char[] и byte[]. Данный класс предназначен
для эффективного и гибкого управления элементами типа char. Данный класс имеет огромное
преимущество перед различными вариантами преобразований символов. Наряду с
абстрактными классами java.nio.charset.Charset и java.nio.ByteBuffer он участвует
в преобразованиях символьных и битовых массивов в обе стороны. При этом позволяет
указывать кодировку, а так же указывать порядок следования байтов. Класс CharBuffer реализует интерфейс CharSequence, который позволяет принимать
участие в символьно-ориентированных операциях, таких как регулярные выражения. которые отсутствовали раньше и
также были введены в JDK 1.4.
Интерфейс CharSequence
В
JDK 1.4 был введен новый интерфейс CharSequence,
который описывает специфическую, неизменяемую последовательность символов. Этот
новый интерфейс является абстракцией для разделения концепции
последовательности символов от специфической реализации, содержащей эти
символы. Строковые классы String, StringBuffer,
StringBuilder в Java теперь
настроены таким образом, что реализуют интерфейс CharSequence.
Интерфейс CharSequence дотаточно прост:
package
java.lang;
public
interface CharSequence
{
int length( );
char charAt (int index);
public String toString( );
CharSequence subSequence (int start, int end);
}
Интерфейс CharSequence является неизменяемым (immutable), поскольку не имеет
методов, изменяющих символьную последовательность. Однако базовый объект,
реализующий данный интерфейс может быть изменяемым. Поэтому методы CharSequence будут отражать текущее
состояние базового объекта. Если его состояние изменяется, то информация, возвращаемая методами CharSequence будет также изменяться.
Если вы зависите от CharSequence, и не знаете заранее тип базового объекта, то
запускайте метод toString(), чтобы получить действительно неизменяемую копию символьной
последовательности.
Следующая программа
демонстрирует поведение java.lang.CharSequence, имплементируемое String, StringBuffer и CharBuffer.
Файл
CharSeq.java
import java.nio.CharBuffer;
/**
* @author Ron Hitchens
(ron@ronsoft.com)
*/
class CharSeq
{
public static void
main (String [] argv)
{
StringBuffer stringBuffer = new StringBuffer
("Hello World");
CharBuffer charBuffer = CharBuffer.allocate
(20);
CharSequence charSequence = "Hello
World";
// derived directly from a String
printCharSequence
(charSequence);
// derived from a StringBuffer
charSequence =
stringBuffer;
printCharSequence
(charSequence);
// Change StringBuffer
stringBuffer.setLength (0);
stringBuffer.append ("Goodbye cruel
world");
// same "immutable" CharSequence
yields different result
printCharSequence
(charSequence);
// Derive CharSequence from CharBuffer
charSequence =
charBuffer;
charBuffer.put
("xxxxxxxxxxxxxxxxxxxx");
charBuffer.clear( );
charBuffer.put ("Hello World");
charBuffer.flip( );
printCharSequence
(charSequence);
charBuffer.mark( );
charBuffer.put ("Seeya");
charBuffer.reset( );
printCharSequence
(charSequence);
charBuffer.clear( );
printCharSequence
(charSequence);
//Changing underlying CharBuffer is reflected
in the
//read-only CharSequnce interface
}
private static void
printCharSequence (CharSequence cs)
{
System.out.println ("length=" + cs.length( )
+ ", content='" + cs.toString( ) + "'");
}
}
Вывод программы:
length=11,
content='Hello World'
length=11,
content='Hello World'
length=19,
content='Goodbye cruel world'
length=11,
content='Hello World'
length=11,
content='Seeya World'
length=20,
content='Seeya Worldxxxxxxxxx'
Метод toString()
Преобразование строки в строку типа String с помощью
метода toString
()
возможно в любом из классов предназначенных для строк. Метод
toString
()
присутствует также в любых других классах. Это обусловлено тем,
что метод
toString
()
определен в классе Object
, который является вершиной иерархии классов
и наследуется всеми классами Java.
Следует отметить, что String и CharSequence являются неизменяемыми (immutable), и поскольку CharSequence полностью описывает String, то метод toString() интерфейса CharSequence возвратит базовый объект String, а не его копию. Для объектов классов StringBuffer и CharSequence метод toString() создаст новую строку String, которая будет содержать копию символьной последовательности.
Производительность
String и StringBuffer/ StringBuilder при многократной конкатенации.
Создание строки во многом может зависеть от того, какие операции будут в дальнейшем проводиться с этой строкой. Многократная конкатенация при работе с большими строками является примером ситуации, где требуется осознанный выбор типа строки при ее создании.
Как
было ранее описано, при создании больших строк с помощью конкатенации, вместо
класса String рекомендуется создавать строки с
помощью классов StringBuilder
или
StringBuffer
и
производить конкатенацию методом
append
()
, поскольку в этих строках не требуется промежуточное создание
нового объекта из слагаемых строк.
Следующий тест демонстрирует преимущество конкатенации в строке
типа
StringBuffer
перед конкатенацией в строке типа
String
.
Файл
String-SBConcatenationDemo.java
class String-SBConcatenationDemo
{
public static void
main(String args[])
{
String-SBConcatenationDemo scd = new String-SBConcatenationDemo();
long start,end;
System.out.println("------------------
String --------------------");
start =
System.currentTimeMillis();
scd.appendString();
end =
System.currentTimeMillis();
System.out.println("Execution
time:" + (end-start));
System.out.println("------------------
StringBuffer ---------------");
start =
System.currentTimeMillis();
scd.appendStringBuffer();
end =
System.currentTimeMillis();
System.out.println("Execution
time:" + (end-start));
}
public void
appendString()
{
String s = "";
for (int i = 0; i
< 10000; i++)
{
s += "<>@#KF:OEJNLDSKN
DJSLDKmv dskv lskdv E(#*)FJ:L j jdfsl;kjf";
s += "First ";
s += "Second ";
s += "Third ";
}
}
public void
appendStringBuffer()
{
StringBuffer s = new StringBuffer("");
for (int i = 0; i
< 10000; i++)
{
s.append("<>@#KF:OEJNLDSKN
DJSLDKmv dskv lskdv E(#*)FJ:L j jdfsl;kjf");
s.append("First
");
s.append("Second
");
s.append("Third
");
}
}
}
Параметры запуска:
Добавляемые строки: "<>@#KF:OEJNLDSKN DJSLDKmv dskv lskdv E(#*)FJ:L j jdfsl;kjf"
"First
"
"Second
"
"Third "
Команда запуска: java
-Xmx256m StringFamilyDemo
Операционная система: Windows
XP Professional
Параметры компьютера: Pentium4
CPU 2,8 ГГц 512 Mb ОЗУ
Результат выполнения:
Количество добавлений в
цикле 10000 600000
Время выполнения для строки типа String: 10 082 281 мс не определено
Время выполнения для строки типа StringBuffer: 47 мс 1140
мс
Программа демонстрирует добавление в цикле подстрок к
строкам типов String, StringBuffer. Для корректной работы программа запускается с выделением
памяти в 256 Mb, поскольку значения по умолчанию в 64Mb может быть недостаточно. Для разнообразия первая
добавляемая строка подобрана таким образом, чтобы в ней присутствовали
различные символы, остальные строки добавляются для многократного использования
метода append() и оператора +=
с короткими строками. Скорость операций для строк String будет явно падать в десятки, сотни и более раз с ростом
размера строки. Поскольку время выполнения для String слишком велико
уже при 10000 добавлений, то им можно пренебречь.
Производительность
StringBuffer и StringBuilder.
Не совсем однозначная ситуация по времени выполнения
выходит со строками StringBuffer и StringBuilder. Согласно документации, использование класса StringBuilder более предпочтительно чем StringBuffer. Как было описано ранее, для строк StringBuilder ряд операций
выполняется быстрее из-за отсутствия синхронизации, а, следовательно, и
отсутствием потоковой безопасности.
Файл
StringFamilyDemo.java
class StringFamilyDemo
{
public static void
main(String args[])
{
StringFamilyDemo sfd = new StringFamilyDemo();
long start,end;
System.out.println("------------------
StringBuilder ---------------");
start =
System.currentTimeMillis();
for(int i = 0;
i<100; i++)
{
sfd.appendStringBuffer();
}
end =
System.currentTimeMillis();
System.out.println("Execution
time:" + (end-start));
System.out.println("------------------
StringBuffer ---------------");
start = System.currentTimeMillis();
for(int i = 0;
i<100; i++)
{
sfd.appendStringBuffer();
}
end =
System.currentTimeMillis();
System.out.println("Execution
time:" + (end-start));
}
public void
appendStringBuffer()
{
StringBuffer s = new StringBuffer("");
for (int i = 0; i
< 600000; i++)
{
s.append("<>@#KF:OEJNLDSKN
DJSLDKmv dskv lskdv E(#*)FJ:L j jdfsl;kjf");
s.append("First
");
s.append("Second
");
s.append("Third
");
}
}
public void
appendStringBuilder()
{
StringBuilder s = new StringBuilder("");
for (int i = 0; i
< 600000; i++)
{
s.append("<>@#KF:OEJNLDSKN
DJSLDKmv dskv lskdv E(#*)FJ:L j jdfsl;kjf");
s.append("First
");
s.append("Second
");
s.append("Third
");
}
}
}
Параметры запуска:
Добавляемые строки: "<>@#KF:OEJNLDSKN DJSLDKmv dskv lskdv E(#*)FJ:L j jdfsl;kjf"
"First
"
"Second
"
"Third "
Команда запуска: java
-Xmx256m StringFamilyDemo
Операционная система: Windows
XP Professional
Параметры компьютера: Pentium4
CPU 2,8 ГГц 512 Mb ОЗУ
Результат выполнения:
Количество вызовов методов с конкатенацией: 100 1000
Время выполнения для строки типа StringBuffer: 102359
мс 1017828 мс
Время выполнения для строки типа StringBuilder: 102141
мс 1017594 мс
По результатам видно, что в однопоточном режиме
многократная конкатенация показывает примерно одинаковые результаты. Показатели
времени выполнения поменяются местами, если в методе main()
поменять местами блоки с циклами вызовов методов appendStringBuilder()
и appendStringBuffer(). Другими словами,
производительность строк типа StringBuffer и StringBuilder одинаковая,
разве что только имеет место слабая зависимость от очередности выполнения
многократной конкатенации с различными типами строк.
Однако возникает следующий вопрос: “почему в однопоточном
режиме строки StringBuffer и StringBuilder имеют
одинаковую производительность, тогда как документация от Sun Microsystems
настойчиво рекомендует StringBuilder?”. Причину можно увидеть в следующих несложных примерах.
Первый пример содержит конкатенацию строк типа String
Файл
ConcatenationDemo1
class ConcatenationDemo1
{
public void printStr(String s)
{
System.out.println("prefix " + s + "
suffix");
}
}
Второй пример содержит конкатенацию строк типа StringBuffer
Файл
ConcatenationDemo2
class ConcatenationDemo2
{
public void
printStr2(String s)
{
StringBuffer buf = new StringBuffer("prefix").append(s).append("
suffix");
System.out.println(buf.toString());
}
}
Откомпилируем и дезассемблируем первый пример, используя Java 1.4.x, запустив
javap –c ConcatenationDemo1
public void
printStr(java.lang.String);
Code:
0: getstatic #2; //Field
java/lang/System.out:Ljava/io/PrintStream;
3: new
#3; //class java/lang/StringBuffer
6: dup
7: invokespecial #4;
//Method java/lang/StringBuffer."":()V
10: ldc #5; //String prefix
12: invokevirtual #6; //Method
java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
15: aload_1
16: invokevirtual #6; //Method
java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
19: ldc
#7; //String
suffix
21: invokevirtual #6; //Method
java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
24: invokevirtual #8; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
27: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
Откомпилируем и дезассемблируем второй пример, используя Java 1.4.x, запустив
javap –c ConcatenationDemo2
public void
printStr2(java.lang.String);
Code:
0: new
#3; //class java/lang/StringBuffer
3: dup
4: ldc #10; //String prefix
6: invokespecial #11; //Method
java/lang/StringBuffer."":(Ljava/lang/String;)V
9: aload_1
10: invokevirtual #6; //Method
java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
13: ldc
#7; //String
suffix
15: invokevirtual #6; //Method
java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
18: astore_2
19: getstatic #2; //Field
java/lang/System.out:Ljava/io/PrintStream;
22: aload_2
23: invokevirtual #8; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
26: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: return
Реальное отличие первого исходного кода от второго в том,
что во втором случае мы вручную создаем строку StringBuffer. Это не особо соответствует исходному коду, но приемлемо,
поскольку неявно увеличивается производительность и конкатенация происходит без
присваивания. Согласно спецификации языка, для примитивных объектов,
реализация может также оптимизировать длительное создание объекта-оболочки,
преобразуя непосредственно примитивный тип в строку.
Однако, что
произойдет, если откомпилировать второй пример с помощью Java
1.5+? Дезассемблирование даст следующий результат:
public void printStr(java.lang.String);
Code:
0: getstatic #2; //Field
java/lang/System.out:Ljava/io/PrintStream;
3: new
#3; //class java/lang/StringBuilder
6: dup
7: invokespecial #4; //Method java/lang/StringBuilder."":()V
10: ldc #5; //String prefix
12: invokevirtual #6; //Method
java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6; //Method
java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc
#7; //String
suffix
21: invokevirtual #6; //Method
java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
Мы получаем StringBuilder вместо StringBuffer, путем негласной перестройки байт-кода от Sun. Теперь становится вполне понятно, почему
производительность StringBuffer и StringBuilder относительно
исходного кода одинакова. Да потому что байт-код у них одинаковый!
По признаку лояльности к негласной оптимизации исходного
кода при компиляции, всех разработчиков условно можно разделить на два типа:
— Сторонники. Программисты, пишущие код, который изолирован
от другого кода (не имеющий влияния на другой код), не требует анализа,
многократного прохождения тестов.
— Противники. Программисты, которым не
один раз приходится анализировать написанный код на предмет утечки ресурсов,
которые сталкиваются со взаимовлияющими блоками кода, которые проводят
разностороннее тестирование, даже после незначительного изменения кода.
С одной стороны можно согласиться с Sun. В случае если вы отклоняетесь от рекомендаций, то “все
будет сделано за Вас так как следует и так как хочет Sun”. Но подобная практика приводит к тому, что программист
все меньше и меньше может быть уверенным, что его программа делает то, что в
ней написано и постоянно держать под рукой
дизассемблер. В то время как здоровая практика программирования предусматривает
два простых этапа: написание грамотного и ясно читаемого кода, использование
профайлера для определения мест, где необходима оптимизация исходного кода. Для
многих разработчиков, которые при утечках производительности не держат на
взводе диассемблер javap, данная ситуация станет откровением.
Помните, что в проектах проще оптимизировать ясно
функционирующий код, чем искать недочеты, отлаживая высоко оптимизированный
код. Но если вы уже столкнулись с подобной ситуацией, то не следует сбрасывать
со счетов, что различные компиляторы генерируют различный вывод. Тестируйте с
одним компилятором, с которым вы будете делать сборку, но примите во внимание
вывод Eclipse compiler, Jikes, GCJ, IBM и javac от Sun. Не попадите впросак, создавая код для одного компилятора.
Вернемся к вопросу потокозащищенности. Как известно,
потокозащищенность объекта во время его изменения, может быть обеспечена в Java с помощью блока synchronized. Следующий тест, в двух вариантах исполнения (для строк StringBuffer и строк StringBuilder), показывает потокозащищенность строк типа StringBuffer и потоконезащищенность
строк типа StringBuilder.
Файл
ThreadSBDemo.java
public class ThreadSBDemo
{
public static void
main(String args[])
{
StringBuffer sb = new StringBuffer("");
for(int i=0;
i<30; i++)
{
NewThread nt1 = new NewThread(sb,
"a");
NewThread nt2 = new NewThread(sb,
"b");
NewThread nt3 = new NewThread(sb,
"c");
nt1.start();
nt2.start();
nt3.start();
}
System.out.println("-----------------
StringBuffer -----------------------");
System.out.println(sb);
}
}
class NewThread extends Thread
{
Thread t;
StringBuffer sb;
String added_str;
public
NewThread(StringBuffer str, String added_s)
{
sb = str;
added_str = added_s;
t = new Thread(this);
t.start();
}
public void run()
{
synchronized(sb)
{
try
{
Thread.sleep(1);
sb.append(added_str);
Thread.sleep(1);
sb.append(added_str);
Thread.sleep(1);
sb.append(added_str);
Thread.sleep(1);
sb.append(added_str);
Thread.sleep(1);
}
catch(InterruptedException
e)
{}
}
}
}
Команда компиляции:
javac ThreadSBDemo.java
Команда запуска:
java ThreadSBDemo
Результаты выполнения:
-----------------
StringBuffer -----------------------
aaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbb
ccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaa
bbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbcccc
aaaabbbbccccaaaabbbbccccaaaabbbbaaaabbbbccccccccaaaabbbbccccaaaabbbbccccaaaabbbb
ccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaa
bbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbcccc
aaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbb
ccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccbbbb
aaaaccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbccccaaaabbbbcccc
Как
видно из программы, запускается три потока, которые одновременно пытаются обратиться к строке sb. Для исключения гонок потоков переменная sb синхронизирована.
В блоке синхронизации присутствует четыре конкатенации с помощью метода append()
и проставлены паузы между ними. Паузы будут больше гарантировать, что во время
выполнения одного потока другой поток будет пытаться обратиться к той же
строке. Согласно правилам языка блок synchronized для объекта sb в одном потоке должен отработать до того момента,
как другой поток начнет добавлять к строке новые данные.
Судя по выводам программы, действительно, нет ни одного
участка в выводимой строке, где бы не было четыре последовательно идущих
одинаковых символа. Это говорит о том, что объект sb блокируется и четыре метода append() отрабатывают
друг за другом без вклинивания методов append()
из другого потока.
Поменяем теперь в этом же самом тесте все записи StringBuffer на StringBuilder.
Файл
ThreadSBDemo.java
public class ThreadSBDemo
{
public static void
main(String args[])
{
StringBuilder sb = new StringBuilder("");
for(int i=0;
i<30; i++)
{
NewThread nt1 = new NewThread(sb,
"a");
NewThread nt2 = new NewThread(sb,
"b");
NewThread nt3 = new NewThread(sb,
"c");
nt1.start();
nt2.start();
nt3.start();
}
System.out.println("-----------------
StringBuilder -----------------------");
System.out.println(sb);
}
}
class NewThread extends Thread
{
Thread t;
StringBuilder sb;
String added_str;
public NewThread(StringBuilder
str, String added_s)
{
sb = str;
added_str = added_s;
t = new Thread(this);
t.start();
}
public void run()
{
synchronized(sb)
{
try
{
Thread.sleep(1);
sb.append(added_str);
Thread.sleep(1);
sb.append(added_str);
Thread.sleep(1);
sb.append(added_str);
Thread.sleep(1);
sb.append(added_str);
Thread.sleep(1);
}
catch(InterruptedException
e)
{}
}
}
}
Команда компиляции:
javac ThreadSBDemo.java
Команда запуска:
java ThreadSBDemo
Результаты выполнения:
-----------------
StringBuilder -----------------------
aaaabbbb
Как видно из вывода измененной программы, любой поток может
затереть в строке то, что добавлено до него другими потоками. Синхронизация ничем
не может помочь в случае со строками StringBuilder, что свидетельствует о том, то подобные строки не являются
потокозащищенными. Поэтому, если вы не пользуетесь многопоточным режимом в
конкретном коде, но его применение возможно в будущем, то пользуйтесь классом StringBuffer. Если вы четко знаете, что в конкретном коде не
предвидится использование множества потоков, то лучше пользоваться строками StringBuilder или “это
сделают за вас” как было описано выше.
Для ясности StringBuilder можно также использовать
в дополнение к StringBuffer в ситуации, когда заранее
неизвестно будет ли многократная конкатенация эффективна в одном потоке, или в
нескольких. Например, если строки, которые конкатенируются
сформированы заранее, то их, конечно же, лучше соединять в одном потоке. Если
же эти строки формируются из внешних источников (например, из файлов в сети) и
заранее неизвестно, какая строка сформируется первой, то лучше применять
многопоточный режим. В то время, пока формируется одна строка, другая
сформированная строка может быть сконкатенирована в другом потоке.
Модифицируем предыдущий тест и попробуем воссоздать
подобную ситуацию. Нижеприведенный тест не будет в полной мере воссоздавать
подобную ситуацию, однако хорошо отразит эффективность использования StringBuilder строки в одном потоке по сравнению с многопоточным
вариантом со строкой StringBuffer.
Файл
ThreadSBDemo2.java
public class ThreadSBDemo2
{
public static void
main(String args[])
{
long start,end;
StringBuffer s_buffer = new StringBuffer("");
StringBuilder s_builder = new StringBuilder("");
/* Закомментировать после первого выполнения */
start = System.currentTimeMillis();
for(int i=0;
i<400000; i++)
{
NewThread1 nt1 = new NewThread1(s_buffer,
"aaaa");
NewThread1 nt2 = new NewThread1(s_buffer,
"bbbb");
NewThread1 nt3 = new NewThread1(s_buffer,
"cccc");
NewThread1 nt4 = new NewThread1(s_buffer,
"dddd");
}
end =
System.currentTimeMillis();
System.out.println("Execution
time for StringBuffer: " + (end-start) + " ms");
/* Закомментировать после первого выполнения */
/* Снять
комментарий после первого выполнения
start = System.currentTimeMillis();
for(int
i=0; i<400000; i++)
{
NewThread2 nt = new NewThread2(s_builder, "aaaabbbbccccdddd");
}
end =
System.currentTimeMillis();
System.out.println("Execution
time for StringBuilder: " + (end-start) + " ms");
Снять комментарий после первого выполнения */
}
}
class NewThread1 extends Thread
{
Thread t;
StringBuffer sb;
String added_str;
public NewThread1(StringBuffer
str, String added_s)
{
sb = str;
added_str = added_s;
t = new Thread(this);
t.start();
}
public void run()
{
synchronized(sb)
{
sb.append(added_str);
}
}
}
class NewThread2 extends Thread
{
Thread t;
StringBuilder sb;
String added_str;
public
NewThread2(StringBuilder str, String added_s)
{
sb = str;
added_str = added_s;
t = new Thread(this);
t.start();
}
public void run()
{
sb.append(added_str);
}
}
После того как программа будет запущена в первый раз,
следует расскомментировать закомментированный блок кода, и закомментировать
указанный блок кода с потоками для строк StringBuffer, скомпилировать и запустить снова.
Команда компиляции:
javac ThreadSBDemo2.java
Команда запуска:
java –Xmx256m ThreadSBDemo2
Результаты выполнения при первом запуске:
Execution time for StringBuffer: 574125
Результаты выполнения при втором запуске:
Execution time for StringBuilder: 156875
Заключение
В завершение следует отметить, что приведенные ситуации с
выбором типа строки не только могут возникнуть в случае многократной
конкатенации, а и в случаях вставки, удаления, замены подстрок в строке и
некоторых других операциях.
Как известно, в Java final классы не
могут быть расширены. Тем не менее существует
достаточно специфический прием низкоуровневого программирования на Java, который позволяет обойти данный запрет. Несмотря на то,
что классы строк String, StringBuffer и StringBuilder являются final они все-таки могут
быть расширены. Этот прием заключается в использовании малоизвестного
класса sun.misc.Unsafe,
который входит в комплект Sun Java Runtime, начиная с первых версий. Им можно воспользоваться для расширения многих классов,
являющимся final. Данный прием описывается в статье “Unsafe Java I” на сайте www.wasm.ru. Следует помнить, что,
воспользовавшись данным приемом, вы лишите себя гарантий производительности и
совместимости. В ряде случаев лучше воспользоваться стандартными интерфейсами и
классами, которые введены начиная с JDK 1.4. С вводом
интерфейса CharSequence
можно создать собственный имплементирующий
класс для неизменяемых строк, а расширяясь от
абстрактного класса CharBuffer,
можно создать собственный класс для гибких операций чтения и записи, для
заключенной в нем символьной последовательности. Поэтому
необходимость в расширении строковых final-классов может возникнуть в крайне редких случаях.
В ряде ситуаций вообще лучше отказаться от строкового представления,
и на время манипуляций просто перевести строку в массив символов, а затем снова
создавать из него строку. Прибегать к такому способу иногда лучше, поскольку он
может обеспечить максимальную производительность. Однако нельзя не признать, что
с вводом CharBuffer, а также
регулярных выражений, начиная с JDK 1.4 таких ситуаций становится
гораздо меньше. В любом случае, выбор представления символьной
последовательности всегда должен быть обоснованным и проверенным с помощью
корректно написанных программ.
Ссылки:
1.
Ноутон Патрик, Шилдт Гербердт. Глава 11. Многопоточное программирование. с. 263-284. — СПб.: БХВ
Петербург, 2001. — 1072 с.
2. Хорстман К.С., Корнелл Г. Библиотека профессионала. Java 2. Том 1 Основы.
Глава 1. Многопоточность c. 15-58 — М.: Издательский дом Вильямс, 2003. — 848 с.
3. Ron Hitchens. JavaTM NIO. First Edition. O’Reilly, 2002.—312p.
4. Википедия, свободная энциклопедия. Строковый тип.
5. Evil String arithmetic revisited.
http://willcode4beer.blogspot.com/2005_05_01_willcode4beer_archive.html
6. Fred Swartz. Java notes
http://leepoint.net/notes-java/data/strings/23stringbufferetc.html
7. Packages: java.langString,
java.lang.StringBuffer, java.lang.StringBuilder. JavaTM 2 Platform Standard Edition
6.0 API Specification.
8. Джеймс Гослинг, Билл Джой, Гай
Стил. Спецификация Java.
Оптимизация конкатенации строк. Пер. с англ. М.Батрак,
С.Болтухова, И.Бураковой, Е.Гречниковой,
Д.Касимовой, В.Лешукова, Т.Морозовой, Е.Ожогина, Т.Сушниковой, С.Щепеткина.
http://www.uni-vologda.ac.ru/index.html
9. Stiver.
Автор: |
< Раздел “Язык Java” > <
Раздел “Технологии Java” > < Основная страница >