Клас StringBuffer

Якби програмісти могли працювати виключно з рядками, доступними тільки для читання, доводилося б створювати новий обєкт String для кожного проміжного результату при послідовних операціях з рядками Наприклад, давайте замислимося над тим, як компілятор буде обчислювати такий вираз:

public static String guillemete(String quote) {

return &quot’ + quote + &quot’;

}

Якби компілятор обмежувався виразами типу String, він діяв би наступним чином:

quoted = StringvalueOf(&quot’)concat(quote)

.concat(StringvalueOf(&quot’))

При кожному виклику valueOf і concat створюється новий обєкт String отже, в результаті подібної операції зявляться чотири обєкти String, з яких надалі буде використаний тільки один Що Щодо решти, то їх створення, присвоєння початкових значень і видалення буде повязане з непродуктивними витратами

Зрозуміло, компілятор діє значно ефективніше Для побудови проміжних рядків використовуються обєкти класу StringBuffer, а підсумкові обєкти String створюються лише в разі потреби Обєкти StringBuffer можуть змінюватися, тому для зберігання проміжних результатів не потрібні нові обєкти За допомогою StringBuffer наведене вище вираз може бути представлено в наступному вигляді:

quoted = new StringBuffer()append(&quot’)

.append(quote)append(&quot’)toString()

У цьому варіанті створюється всього один обєкт StringBuffer, який зберігає рядок, додає до неї нові фрагменти і користується методом toString для перетворення результатів своєї роботи в обєкт String

Для побудови і модифікації рядка можна скористатися класом StringBuffer

містить наступні конструктори:

public StringBuffer()

Конструює новий обєкт StringBuffer, початкове значення якого дорівнює “. public StringBuffer(String str)

Конструює новий обєкт StringBuffer, початкове значення якого збігається з str

в багатьох відношеннях нагадує клас String, а багато з містяться в ньому методів мають ті ж імена і контракти, що і методи String Проте StringBuffer не є розширенням String, і навпаки Обидва цих класу є незалежними розширеннями класу Object

881 Модифікація буфера

Існує кілька можливостей змінити вміст буфера обєкта StringBuffer, в тому числі додати нові символи в його кінець або вставити їх в середину Найпростіший з таких методів називається setCharAt і служити для заміни символу в конкретній позиції Також є метод replace, який робить те ж саме, що і

Stringreplace, проте працює з обєктом StringBuffer Метод replace не потребує створення нового обєкта для зберігання результату, тому кілька послідовних викликів replace можуть виконуватися з одним буфером:

public static void

replace(StringBuffer str, char from, char to)

{

for (int i = 0 i &lt&lt strlength() i++)

if (strcharAt(i) == from)

strsetCharAt(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()

bufappend(&quotsqrt(&quot)append(i)append()) bufappend(&quot = &quot)append(Mathsqrt(i)) return buftoString()

}

Методи append і insert повертають вихідний обєкт String, що в даному випадку дозволяє нам додати нові символи до результату попередньої дії

Метод insert отримує два параметри Перший з них – позиція, починаючи з якої в StringBuffer вставлятимуться нові символи Другий параметр – вставляється значення, при необхідності перетворене в String Наведемо метод, який вставляє додає дату в початок буфера:

public static StringBuffer addDate(StringBuffer buf) { String now = new javautilDate()toString() bufensureCapacity(buflength() + nowlength() + 2) bufinsert(0, now)insert(nowlength(), &quot: &quot)

return buf

}

Все починається з створення рядка, що містить поточну дату Для цієї мети використовується клас javautilDate його конструктор за замовчуванням створює обєкт, що представляє поточний час на момент створення Далі необхідно переконатися, що розмірів буфера вистачить для зберігання всіх додаються символів – збільшення буфера має відбуватися тільки один раз, а не після кожного виклику insert Потім в буфер вставляється рядок з поточним часом, за якою слід найпростіша рядок-роздільник Нарешті, переданий буфер повертається назад, щоб при виклику даного методу можна було здійснити що-небудь на зразок тієї конкатенації, яка згодилася при роботі з власними методами StringBuffer

Метод reverse змінює порядок проходження символів в StringBuffer Наприклад, якщо буфер містить рядок good, то після виконання reverse в ньому виявиться рядок doog.

882 Витяг даних

Щоб створити обєкт String на основі обєкта StringBuffer, слід викликати метод

toString

У StringBuffer не існує методів, які б видаляли частину вмісту буфера – вам доведеться перетворити буфер в символьний масив, видалити те, що потрібно, і побудувати новий буфер по масиву з рештою символами Ймовірно, для цього слід скористатися методом getChars, який аналогічний методу StringgetChars

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 &lt&lt 0 || cnt &lt&lt 0 || pos + cnt &gt&gt buflength())

throw new IndexOutOfBoundsException()

int leftover = buflength() – (pos + cnt)

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

bufsetlength(pos)

return buf

}

char[] chrs = new char[leftover] bufgetChars(pos + cnt, bufLength(), chrs, 0) bufsetLength(pos)

bufappend(chrs)

return buf

}

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

883 Робота з ємністю буфера

Буфер обєкта 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) bufappend(&quotsqrt(&quot)append(i)append()) bufappend(&quot = &quot)append(Mathsqrt(i)) return buftoString()

}

Єдина зміна полягає в тому, що на цей раз використовується конструктор, який створює досить великий обєкт StringBuffer, здатний вмістити рядок з результатом Значення 50 кілька перевищує максимально необхідне отже, буфер ніколи не доведеться збільшувати заново

Вправа 84

Напишіть метод для перетворення рядків з десятковими числами, при якому після кожної третьої цифри справа ставиться кома Наприклад, для початкового рядка 1542729 метод повинен повертати рядок ”1,542,729&quot.

Вправа 85

Змініть метод з попереднього вправи так, щоб при його виклику можна було вказати символ-роздільник і кількість цифр між роздільниками

Глава 9

ПОТОКИ

Як можна знаходитися в двох місцях одночасно, якщо насправді взагалі ніде не знаходишся Firesign Theater

Більшість програмістів звикло писати програми, які виконуються крок за кроком, в певній послідовності На наведеній нижче ілюстрації показано, як витягується банківський баланс, сума на рахунку збільшується і заноситься назад в запис про стан рахунку:

Аналогічні дії виконуються як живими банківськими працівниками, так і компютерними програмами Подібна послідовність дій, виконуваних по одному, називається потоком (th read) У більшості мов програмістам доводиться мати справу з однопоточному моделлю програмування

Однак у справжніх банках подібні операції відбуваються одночасно Кілька працівників незалежно один від одного можуть оновлювати стан банківських рахунків:

Аналог подібної ситуації в компютері називається багатопоточність (multithreading) Потік (як і банківський працівник) може працювати незалежно від інших потоків І подібно до того, як двоє банківських службовців можуть користуватися одними і тими ж картотеками, потоки також здійснюють спільний доступ до обєктів

Спільний доступ одночасно є і одним з найбільш корисних властивостей багатопоточності, і джерел найбільших проблем При використанні наведеної вище схеми вибірка-зміна-запис виникає потенційна небезпека того, що при одночасній роботі двох потоків з одним і тим же обєктом відбудеться накладення, що приводить до руйнування обєкта Давайте уявимо, що в нашому прикладі з банком хтось бажає внести кошти на рахунок

Майже одночасно другий клієнт наказує іншому працівнику банку покласти гроші на той же самий рахунок Обидва працівники йдуть в архів, щоб знайти інформацію про рахунок (були ж часи, коли в банках використовувалися паперові картотеки) і отримують однакові дані Потім вони повертаються до своїх столів, заносять необхідну суму на рахунок і йдуть назад в архів, щоб записати свої результати, отримані незалежно один від одного У такому випадку на стан рахунку відібється лише остання з записаних транзакцій перша транзакція буде просто втрачена

У справжніх банках проблема вирішувалася просто: працівник залишав у папці записку

“Зайнято почекайте завершення роботи . У компютері відбувається практично те ж

саме: з обєктом звязується поняття блокування (lock), За якою можна визначити,

використовується обєкт чи ні

Багато реальні завдання програмування найкраще вирішуються із застосуванням декількох потоків Наприклад, інтерактивні програми, призначені для графічного відображення даних, нерідко дозволяють користувачеві змінювати параметри відображення в реальному часі Оптимальне динамічну поведінку інтерактивних програм досягається завдяки використанню потоків У однопоточних системах ілюзія роботи з декількома потоками зазвичай досягається за рахунок використання переривань або програмних запитів (polling) Програмні запити служать для обєднання частин додатка, керуючих відображенням інформації і введенням даних Особливо ретельно повинна бути написана програма відображення – запити від неї повинні надходити досить часто, щоб реагувати на введення інформації користувачем протягом часток секунди Ця програма або повинна подбати про те, щоб операції графічного виведення займали мінімальне час, або переривати свою власну роботу для виконання запитів Таке змішання двох різнорідних аспектів програми призводить до появи складного, а часом і нежиттєздатного коду

З зазначеними проблемами найпростіше впоратися в багатопотокової системі Один потік оновлює зображення на основі поточних даних, а інший – обробляє введення з боку користувача Якщо введення виявляється складним (наприклад, користувач заповнює екранну форму), перший потік (висновок даних) може працювати незалежно, аж до отримання нової інформації У моделі із застосуванням програмних запитів припадає або припиняти оновлення зображення, щоб дочекатися завершення нетривіального введення, або проводити складну синхронізацію, щоб зображення могло оновлюватися під час заповнення форми користувачем Модель з поділом процесів введення і відображення може підтримуватися в багатопотокової системі безпосередньо, замість того щоб заново підганяти її для реалізації чергової завдання

Джерело: Арнольд К, Гослінг Д – Мова програмування Java (1997)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*