Обробка рядків у Java
Вступ
Що ви знаєте про обробку рядків у Java? Як багато цих знань і наскільки вони поглиблені та актуальні? Спробуймо разом зі мною розібрати всі питання, пов'язані з цією важливою, фундаментальною і часто використовуваною частиною мови. Наш маленький гайд буде розбитий на п'ять публікацій, а саме:
- String, StringBuffer, StringBuilder (реалізація рядків)
- Pattern, Matcher (регулярні вирази)
- i18n (інтернаціоналізація)
- Кодування символів (Unicode, UTF-8)
- Locale, ResourceBundle (локалізація)
Реалізація рядків на Java представлена трьома основними класами:String,StringBuffer,StringBuilder. Давайте поговоримо про них.
Рядок - об'єкт, що представляє послідовність символів. Для створення та маніпулювання рядками Java платформа надає загальнодоступний фінальний (не може мати підкласів) класjava.lang.String. Цей клас є незмінним (immutable) — створений об'єкт класуStringможе бути змінено. Можна подумати, що методи мають право змінювати цей об'єкт, але це не так. Методи можуть лише створювати та повертати нові рядки, в яких зберігається результат операції. Незмінність рядків надає ряд можливостей:
- використання рядків у багатопотокових середовищах (Stringє потокобезпечним (thread-safe))
- використанняString Pool(це колекція посилань на об'єктиString, використовується для оптимізації пам'яті)
- використання рядків як ключів уHashMap(ключ рекомендується робити незмінним)
Ми можемо створити об'єкт класуStringдекількома способами:
1. Використовуючи рядкові літерали:
Рядковий літерал - послідовність символів ув'язнених у подвійні лапки. Важливо розуміти, що завжди, коли ви використовуєте рядковий літерал, компілятор створює об'єкт зі значенням цього літералу:
2. За допомогою конструкторів:
Якщо копія рядка не потрібна явно, використання цих конструкторів небажане і немає необхідності, оскільки рядки є незмінними. Постійне будівництво нових об'єктів у такий спосіб може призвести до зниження продуктивності. Їх краще замінити на аналогічні ініціалізації за допомогою рядкових літералів.
Також можна створити об'єкт рядка за допомогою масиву байтів. Додатково можна передати параметр класуCharset, який відповідатиме за кодування. Відбувається декодування масиву за допомогою зазначеного кодування (якщо не вказано - використовуєтьсяCharset.defaultCharset(), де за замовчуванням вказаноUTF-8) і, далі, отриманий масив символів копіюється в значення об'єкт.
Ну і нарешті конструктори використовують об'єктиStringBufferіStringBuilder, їх значення (getValue()) і довжину (length()) для створення рядка об'єкта. З цими класами ми познайомимося трохи згодом.
Наведено приклади конструкторів класуString, які найчастіше використовуються, насправді їх п'ятнадцять (два з яких позначені якdeprecated).
Важливою частиною кожного рядка є його довжина. Дізнатися її можна звернувшись до об'єктаStringза допомогою методу доступу (accessor method)length(), який повертає кількість символів у рядку, наприклад:
Конкатенація
Конкатенація - операція об'єднання рядків, що повертає новий рядок, що єрезультатом об'єднання другого рядка із закінченням першого. Операція для об'єктаStringможе бути виконана двома способами:
1. Методconcat
Важливо розуміти, що методconcatне змінює рядок, а лише створює новий як результат злиття поточної та переданої як параметр. Так, метод повертає новий об'єкт String, тому можливі такі довгі ланцюжки.
2. Перевантажені оператори "+" та "+="
Це одні з небагатьох перевантажених операторів у Java - мова не дозволяє перевантажувати операції для об'єктів класів користувача. Оператор "+" не використовує методconcat, тут використовується наступний механізм:
Використовуйте методconcat, якщо злиття потрібно провести лише один раз, для решти випадків рекомендовано використовувати або оператор "+" абоStringBuffer/StringBuilder. Також варто зазначити, що отримати NPE (NullPointerException), якщо один з операнда дорівнюєnull, неможливо за допомогою оператора "+" або "+=", чого не скажеш про методconcat, наприклад:
Форматування
КласStringнадає можливість створення форматованих рядків. За це відповідає статичний методformat, наприклад:
Завдяки безлічі методів надається можливість маніпулювання рядком та його символами. Описувати їх тут немає сенсу, тому що Oracle має добрі статті про маніпулювання та порівняння рядків. Також у вас під рукою завжди є їхня документація. Хотілося відзначити новий статичний методjoin, який з'явився в Java 8. Тепер ми можемо зручно об'єднувати кілька рядків в один, використовуючи роздільник (було додано класjava.lang.StringJoiner, що за ньоговідповідає), наприклад:
Це не єдина зміна класу в Java 8. Oracle повідомляє про поліпшення продуктивності в конструкторіString(byte[], *)і методgetBytes(). Також зміни торкнулисяhashCode(новий алгоритмhashing) таsplit(оптимізація).
Перетворення
1. Число в рядок
2. Рядок до числа
StringBuffer
Рядки є незмінними, тому часта їх модифікація призводить до створення нових об'єктів, що у свою чергу витрачає дорогоцінну пам'ять. Для вирішення цієї проблеми було створено класjava.lang.StringBuffer, який дозволяє ефективніше працювати над модифікацією рядка. Клас єmutable, тобто змінним - використовуйте його, якщо Ви хочете змінювати вміст рядка.StringBufferможе бути використаний у багатопотокових середовищах, так як всі необхідні методи є синхронізованими.
Існує чотири способи створення об'єкта класуStringBuffer. Кожен об'єкт має свою місткість (capacity), що відповідає за довжину внутрішнього буфера. Якщо довжина рядка, що зберігається у внутрішньому буфері, не перевищує розмір цього буфера (capacity), то немає необхідності виділяти новий масив буфера. Якщо ж буфер переповнюється, він автоматично ставатиме більше.
Модифікація
У більшості випадків ми використовуємоStringBufferдля багаторазового виконання операцій додавання (append), вставки (insert) та видалення (delete) підрядків. Тут все дуже просто, наприклад:
Всі інші методи роботи зStringBufferможна подивитися в документації.
StringBuilder
StringBuilder- клас, що представляє змінну послідовністьсимволів. Клас був введений Java 5 і має повністю ідентичний API зStringBuffer. Єдина відмінність -StringBuilderне синхронізовано. Це означає, що його використання у багатопотокових середовищах є небажаним. Отже, якщо ви працюєте з багатопоточністю, Вам ідеально підходитьStringBuffer, інакше використовуйтеStringBuilder, який працює набагато швидше у більшості реалізацій. Напишемо невеликий тест для порівняння швидкості роботи цих двох класів: