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 применяется первый вид представления в виде массива символов и две его разновидности:

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

— представление в виде массива байтов, которые выражают коды символов (применяется только для восьмибитной кодировки, которая заранее известна). Используется крайне редко, только в определенных ситуациях для повышения производительности.

 

Представления символов в зависимости от кодировки

Представление символов строки можно реализовать различными способами. До последнего времени один символ всегда кодировался одним байтом (8 двоичных битов; применялись также кодировки с 7 битами на символ), что позволяло представлять 256 (128 при семибитной кодировке) возможных значений. Однако для полноценного представления символов алфавитов нескольких языков (многоязыковых документов, типографских символов — несколько видов кавычек, тире, нескольких видов пробелов и для написания текстов на иероглифических языках — китайском, японском и корейском) 256 символов недостаточно. Для решения этой проблемы существует несколько методов:

— Переключение языка управляющими кодами. Метод не стандартизирован и лишает текст самостоятельности (то есть последовательность символов без управляющего кода в начале теряет смысл); использовался в некоторых ранних русификациях 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];

 

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

 

Символьные последовательности и строки

 

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

Символьная последовательность — это абстрагированное понятие. Однако символьная последовательность может быть реализована в форме символьного массива или строки-объекта с заданным типом. Иными словами, символьная последовательность это символы в чистом виде, которые можно занести в массив символов типа char, в объекты-строки  или даже занести преобразованные коды символов в массив байтов типа byte.

Не следует приравнивать понятие символьной последовательности к интерфейсу CharSequence, который реализуют классы String, StringBuffer, StringBuilder.

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

Как было описано выше, строки являются стандартными объектами одного из типов: String, StringBuffer, StringBuilder, но массив символов типа char это уже не объект-строка. Не смотря на то, что символьные последовательности, которые заложены и в массив и в объект-строку могут быть одинаковыми, сам массив и объект-строка не являются эквивалентными между собой.

В языке Java, объект типа String является неизменяемым (immutable), то есть никогда не меняется содержимое символьной последовательности, заключенной в этом объекте. Изменение содержимого возможно только через создание нового объекта типа String, что нельзя назвать прямым изменением. В то же время, массив символов типа char имеет элементы, значения которых могут многократно изменяться.

 Массив символов типа char используется для повышения быстродействия, когда доминируют операции с отдельными символами символьной последовательности с фиксированной длиной и большая часть времени затрачивается на посимвольные операции. Кроме того, используя массив символов, можно многократно изменять отдельные символы символьной последовательности, чего не всегда можно эффективно сделать в строках типа StringBuffer и StringBuilder, а в строках типа String, как было описано выше, прямым изменением сделать это вообще невозможно.

Фиксированная длина массива позволяет вести обработку массива в простом цикле. Быстродействие же, при использовании массива символов типа char объясняется тем, что элементы массива имеют примитивный тип. А, как известно, примитивные типы в Java лежат вне дерева наследования классов и все операции с ними производятся быстрее, поскольку не требуют создания объекта и связанных с объектом операций. Более того, заглянув в исходные коды классов String, StringBuffer, StringBuilder можно заметить, что в них создается массив символов типа char из параметра конструктора, и многие внутренние операции проделываются уже на этом массиве.

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

По поводу массивов символов можно добавить то, что сам массив символов char (но не его элементы) является объектом и поэтому для него доступны методы, относящиеся к классу java.util.Arrays, которые позволяют сортировать, искать производить заполнение в массивах. Кроме этого, класс java.lang.System предоставляет метод для копирования массива.

 

Подводя итог, можно сделать следующие выводы:

 — если операции с символьной последовательностью не критичны по времени, строка не претерпевает многократных изменений, то лучше использовать реализацию в виде строки;

          если операции с символьной последовательностью критичны по времени, ожидается многократное изменение строки, возможна работа с кодовыми блоками кодовых точек Unicode, то лучше использовать StringBuffer (в многопоточном режиме) и StringBuilder (в однопоточном режиме).

 — в том случае, если операции с символьной последовательностью критичны по времени, работа с кодовыми блоками кодовых точек Unicode не требует существенных затрат, предстоящие операции не имеют эквивалентных методов в классах-строках, то следует прибегать к массивам char;

  в том случае, если операции с символьной последовательностью критичны по времени, операции проводятся с кодами символов, кодировка символов заранее известна и позволяет уместить все виды символов в размерность типа 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. OReilly, 2002.—312p.

4. Википедия, свободная энциклопедия. Строковый тип.

http://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%BE%D0%BA%D0%BE%D0%B2%D1%8B%D0%B9_%D1%82%D0%B8%D0%BF

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.

http://java.sun.com

8. Джеймс Гослинг, Билл Джой, Гай Стил. Спецификация Java. Оптимизация конкатенации строк. Пер. с англ. М.Батрак,

С.Болтухова, И.Бураковой, Е.Гречниковой, Д.Касимовой, В.Лешукова, Т.Морозовой, Е.Ожогина, Т.Сушниковой, С.Щепеткина.

http://www.uni-vologda.ac.ru/index.html

9. Stiver. Unsafe Java I. Раздел СТАТЬИ > Байт код

www.wasm.ru 

 

 

 

 

 

 

 

Автор:

 

 

Загребин Виктор Александрович

 

 

< Раздел “Язык Java > < Раздел “Технологии Java > < Основная страница >

 

 

Hosted by uCoz