Кен Арнольд Джеймс Гослинг

Вид материалаДокументы

Содержание


8.6. Строки и символьные массивы
8.7. Строки и массивы byte
String(byte[] bytes, int hiByte, int offset, int count)
String(byte[] bytes, int hiByte)
8.8. Класс StringBuffer
StringBuffer(String str)
8.8.1. Модификация буфера
8.8.2. Извлечение данных
8.8.3. Работа с емкостью буфера
StringBuffer(int capacity)
Подобный материал:
1   ...   40   41   42   43   44   45   46   47   ...   81

8.6. Строки и символьные массивы


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

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

public static String squeezeOut(String from, char toss) {

char[] chars = from.toCharArray();

int len = chars.length;

for (int i = 0; i << len; i++) {

if (chars[i] == toss) {

--len;

System.arraycopy(chars, i + 1,

chars, i, len - i);

--i; // рассмотреть повторно

}

}

return new String (chars, 0, len);

}

Метод squeezeOut сначала преобразует свою входную строку from в символьный массив при помощи метода toCharArray. Затем он в цикле перебирает элементы массива в поисках символа toss. Когда такой символ находится, длина возвращаемой строки уменьшается на 1, а все следующие символы массива сдвигаются к началу. Значение i уменьшается, чтобы можно было проверить новый символ в позиции i и выяснить, не следует ли удалить и его. Когда метод завершает просмотр массива, он возвращает новый объект String, содержащий “выжатую” строку. Для этого применяется конструктор String, которому в качестве аргументов передается исходный массив, начальная позиция внутри массива и количество символов.

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

При желании вместо конструкторов можно воспользоваться двумя статическими методами String.copyValueOf. Например, метод squeezeOut мог бы заканчиваться следующей строкой:

return String.copyValueOf(chars, 0, len);

Вторая форма copyValueOf получает один аргумент и копирует весь массив. Для полноты было решено сделать два статических метода copy ValueOf эквивалентными двум конструкторам String.

Метод toCharArray прост и достаточен в большинстве случаев. Когда желательно иметь больше возможностей для контроля за процессом копирования фрагментов строки в символьный массив, можно воспользоваться методом getChars:

public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

Копирует символы из строки в массив. Символы заданной подстроки копируются в массив начиная с dst[dstBegin]. Подстрока представляет собой фрагмент исходной строки, который начинается с позиции srcBegin и заканчивается на srcEnd (но не включает ее!). Любая попытка выхода за пределы строки или массива char приводит к возбуждению исключения IndexOutOfBoundsException.

8.7. Строки и массивы byte


Существуют методы, предназначенные для преобразования массивов 8-разрядных символов в объекты String в 16-разрядной кодировке Unicode и наоборот. Эти методы помогают создавать строки Unicode из символов ASCII или ISO-Latin-1, которые являются первыми 256 символами в наборе Unicode. Данные методы аналогичны своим прототипам, предназначенным для работы с символьными массивами:

public String(byte[] bytes, int hiByte, int offset, int count)

Конструктор создает новую строку, состоящую из символов заданного подмассива, входящего в массив bytes с позиции offset и состоящего из count символов. Старшие 8 бит каждого символа могут быть заданы в переменной hiByte, значение которой обычно равно 0, так как конструктор чаще всего используется для преобразования символов 8-разрядной кодировки ASCII или ISO-Latin-1 в 16-разрядные строки Unicode. Если значения аргументов offset и coun t заставляют конструктор обратиться к элементам, выходящим за границы массива, возбуждается исключение IndexOutOfBounds.

public String(byte[] bytes, int hiByte)

Сокращенная форма для String(bytesm hibyte, 0, bytes.length).

public void int  getBytes(int srcBegin, int srcEnd, byte[] dst, dstBegin)

Создает копию фрагмента строки в массиве dst, начиная с dst[dstBegin]. При этом теряются старшие 8 бит каждого символа. Копирование производится с позиции srcBegin и заканчивается в srcEnd (но не включает ее!). Любая попытка выхода за пределы строки или массива char приводит к возбуждению исключения IndexOutOfBoundsException.

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

8.8. Класс StringBuffer


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

public static String guillemete(String quote) {

return '"' + quote + '"';

}

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

quoted = String.valueOf('"').concat(quote)

.concat(String.valueOf('"'));

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

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

quoted = new StringBuffer().append('"').

.append(quote).append('"').toString();

В этом варианте создается всего один объект StringBuffer, который хранит строку, добавляет к ней новые фрагменты и пользуется методом toString для преобразования результатов своей работы в объект String.

Для построения и модификации строки можно воспользоваться классом StringBuffer. Класс StringBuffer содержит следующие конструкторы:

public StringBuffer()

Конструирует новый объект StringBuffer, начальное значение которого равно “”.

public StringBuffer(String str)

Конструирует новый объект StringBuffer, исходное значение которого совпадает с str.

Класс StringBuffer во многих отношениях напоминает класс String, а многие из содержащихся в нем методов имеют те же имена и контракты, что и методы String. Тем не менее StringBuffer не является расширением String, и наоборот. Оба этих класса являются независимыми расширениями класса Object.

8.8.1. Модификация буфера


Существует несколько возможностей изменить содержимое буфера объекта StringBuffer, в том числе добавить новые символы в его конец или вставить их в середину. Самый простой из таких методов называется setCharAt и служить для замены символа в конкретной позиции. Также имеется метод replace, который делает то же самое, что и String.replace, однако работает с объектом StringBuffer. Метод replace не нуждается в создании нового объекта для хранения результата, поэтому несколько последовательных вызовов replace могут выполняться с одним буфером:

public static void

replace(StringBuffer str, char from, char to)

{

for (int i = 0; i << str.length(); i++)

if (str.charAt(i) == from)

str.setCharAt(i, to);

}

Метод setLength обрезает или расширяет строку, хранящуюся в буфере. Если передать setLength величину, меньшую длины текущей строки, то строка будет обрезана до указанного значения. Если же передаваемая длина превышает текущую, то строка расширяется, а новые позиции заполняются нуль-символами (\u0000).

Кроме того, имеются методы append и insert, которые преобразуют данные любого типа в String, после чего присоединяют результат преобразования к концу строки либо вставляют его в середину. При выполнении метода insert существующие символы сдвигаются, чтобы освободить место для вставляемых новых символов. Методы append и insert преобразуют данные следующих типов:

Object String char[]

boolean char int int

long float double

Также существуют методы append и insert, которым в качестве аргумента передается часть массива char. Например, для создания объекта String Buffer с описанием квадратного корня из целого числа можно написать следующее:

String sqrtInt(int i) {

StringBuffer buf = new StringBuffer();


buf.append("sqrt(").append(i).append(')');

buf.append(" = ").append(Math.sqrt(i));

return buf.toString();

}

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

Метод insert получает два параметра. Первый из них — позиция, начиная с которой в StringBuffer будут вставляться новые символы. Второй параметр — вставляемое значение, при необходимости преобразованное в String. Приведем метод, который вставляет добавляет дату в начало буфера:

public static StringBuffer addDate(StringBuffer buf) {

String now = new java.util.Date().toString();

buf.ensureCapacity(buf.length() + now.length() + 2);

buf.insert(0, now).insert(now.length(), ": ");

return buf;

}

Все начинается с создания строки, содержащей текущую дату. Для этой цели используется класс java.util.Date; его конструктор по умолчанию создает объект, представляющий текущее время на момент создания. Далее необходимо убедиться, что размеров буфера хватит для хранения всех добавляемых символов — увеличение буфера должно происходить только один раз, а не после каждого вызова insert. Затем в буфер вставляется строка с текущим временем, за которой следует простейшая строка-разделитель. Наконец, переданный буфер возвращается обратно, чтобы при вызове данного метода можно было осуществить что-нибудь наподобие той конкатенации, которая пригодилась при работе с собственными методами StringBuffer.

Метод reverse изменяет порядок следования символов в StringBuffer. Например, если буфер содержит строку “good”, то после выполнения reverse в нем окажется строка “doog”.

8.8.2. Извлечение данных


Чтобы создать объект String на основе объекта StringBuffer, следует вызвать метод toString.

В StringBuffer не существует методов, которые бы удаляли часть содержимого буфера — вам придется преобразовать буфер в символьный массив, удалить то, что нужно, и построить новый буфер по массиву с оставшимися символами. Вероятно, для этого следует воспользоваться методом getChars, который аналогичен методу String.getChars.

public void getChars(int srcBegin, int srcEnd, char[] dst, int   dstBegin)

Копирует символы из заданной части буфера (определяемой позициями srcBegin и srcEnd) в массив dst начиная с dst[dstBegin]. Копирование ведется с позиции srcBegin до srcEnd (но не включает ее!). Позиция srcBegin должна представлять собой допустимый индекс для данного буфера, а позиция srcEnd не может превышать длины текущей строки в буфере (которая на единицу больше, чем последний индекс). Если какой-либо из индексов окажется недопустимым, возбуждается исключение Inde x OutOfBoundsException.

Приведем метод, в котором getChars используется для удаления части содержимого буфера:

public static StringBuffer

remove(StringBuffer buf, int pos, int cnt)

{

if (pos << 0 || cnt << 0 || pos + cnt >> buf.length())

throw new IndexOutOfBoundsException();


int leftover = buf.length() - (pos + cnt);

if (leftover == 0) { // простое обрезание строки

buf.setlength(pos);

return buf;

}


char[] chrs = new char[leftover];

buf.getChars(pos + cnt, buf.Length(), chrs, 0);

buf.setLength(pos);

buf.append(chrs);

return buf;

}

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

8.8.3. Работа с емкостью буфера


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

Исходный размер буфера объекта StringBuffer может быть задан с помощью конструктора, получающего всего один параметр типа int:

public StringBuffer(int capacity)

Конструирует новый объект StringBuffer с заданной исходной емкостью и начальным значением “”.

public synchronized void ensureCapacity(int minimum)

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

public int capacity()

Возвращает текущую емкость буфера.

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

String sqrtIntFaster(int i) {

StringBuffer buf = new StringBuffer(50);

buf.append("sqrt(").append(i).append(')');

buf.append(" = ").append(Math.sqrt(i));

return buf.toString();

}

Единственное изменение заключается в том, что на этот раз используется конструктор, который создает достаточно большой объект StringBuffer, способный вместить строку с результатом. Значение 50 несколько превышает максимально необходимое; следовательно, буфер никогда не придется увеличивать заново.

Упражнение 8.4

Напишите метод для преобразования строк с десятичными числами, при котором после каждой третьей цифры справа ставится запятая. Например, для исходной строки “1542729" метод должен возвращать строку ”1,542,729".

Упражнение 8.5

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