Строки. Операции над строками, StringBuilder

Объединение строк

Конкатенация строк или объединение может производиться как с помощью операции +, так и с помощью метода Concat:

string s1 = "hello";
string s2 = "world";
string s3 = s1 + " " + s2; // результат: строка "hello world"
string s4 = string.Concat(s3, "!!!"); // результат: строка "hello world!!!"
 
Console.WriteLine(s4);

Метод Concat является статическим методом класса string, принимающим в качестве параметров две строки. Также имеются другие версии метода, принимающие другое количество параметров.

Для объединения строк также может использоваться метод Join:

string s5 = "apple";
string s6 = "a day";
string s7 = "keeps";
string s8 = "a doctor";
string s9 = "away";
string[] values = new string[] { s5, s6, s7, s8, s9 };
 
string s10 = string.Join(" ", values);
Console.WriteLine(s10); // apple a day keeps a doctor away

Метод Join также является статическим. Использованная выше версия метода получает два параметра: строку-разделитель (в данном случае пробел) и массив строк, которые будут соединяться и разделяться разделителем.

Сравнение строк

Для сравнения строк применяется статический метод Compare:

string s1 = "hello";
string s2 = "world";
 
int result = string.Compare(s1, s2);
if (result<0)
{
    Console.WriteLine("Строка s1 перед строкой s2");
}
else if (result > 0)
{
    Console.WriteLine("Строка s1 стоит после строки s2");
}
else
{
    Console.WriteLine("Строки s1 и s2 идентичны");
}
// результатом будет "Строка s1 перед строкой s2"

Данная версия метода Compare принимает две строки и возвращает число. Если первая строка по алфавиту стоит выше второй, то возвращается число меньше нуля. В противном случае возвращается число больше нуля. И третий случай — если строки равны, то возвращается число 0.

В данном случае так как символ h по алфавиту стоит выше символа w, то и первая строка будет стоять выше.

Поиск в строке

С помощью метода IndexOf мы можем определить индекс первого вхождения отдельного символа или подстроки в строке:

string s1 = "hello world";
char ch = 'o';
int indexOfChar = s1.IndexOf(ch); // равно 4
Console.WriteLine(indexOfChar);
 
string substring = "wor";
int indexOfSubstring = s1.IndexOf(substring); // равно 6
Console.WriteLine(indexOfSubstring);

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

Еще одна группа методов позволяет узнать начинается или заканчивается ли строка на определенную подстроку. Для этого предназначены методы StartsWith и EndsWith. Например, в массиве строк хранится список файлов, и нам надо вывести все файлы с расширением exe:

var files = new string[]
{
    "myapp.exe",
    "forest.jpg",
    "main.exe",
    "book.pdf",
    "river.png"
};
 
for (int i = 0; i < files.Length; i++)
{
    if (files[i].EndsWith(".exe"))
        Console.WriteLine(files[i]);
}

Разделение строк

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

string text = "И поэтому все так произошло";
 
string[] words = text.Split(new char[] { ' ' });
 
foreach (string s in words)
{
    Console.WriteLine(s);
}

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

string[] words = text.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

Второй параметр StringSplitOptions.RemoveEmptyEntries говорит, что надо удалить все пустые подстроки.

Обрезка строки

Для обрезки начальных или концевых символов используется функция Trim:

string text = " hello world ";
 
text = text.Trim(); // результат "hello world"
text = text.Trim(new char[] { 'd', 'h' }); // результат "ello worl"

Функция Trim без параметров обрезает начальные и конечные пробелы и возвращает обрезанную строку. Чтобы явным образом указать, какие начальные и конечные символы следует обрезать, мы можем передать в функцию массив этих символов.

Эта функция имеет частичные аналоги: функция TrimStart обрезает начальные символы, а функция TrimEnd обрезает конечные символы.

Обрезать определенную часть строки позволяет функция Substring:

string text = "Хороший день";
// обрезаем начиная с третьего символа
text = text.Substring(2);
// результат "роший день"
Console.WriteLine(text);
// обрезаем сначала до последних двух символов
text = text.Substring(0, text.Length - 2);
// результат "роший де"
 Console.WriteLine(text);

Функция Substring также возвращает обрезанную строку. В качестве параметра первая использованная версия применяет индекс, начиная с которого надо обрезать строку. Вторая версия применяет два параметра — индекс начала обрезки и длину вырезаемой части строки.

Вставка

Для вставки одной строки в другую применяется функция Insert:

string text = "Хороший день";
string substring = "замечательный ";
 
text = text.Insert(8, substring);
Console.WriteLine(text);    // Хороший замечательный день

Первым параметром в функции Insert является индекс, по которому надо вставлять подстроку, а второй параметр — собственно подстрока.

Удаление строк

Удалить часть строки помогает метод Remove:

string text = "Хороший день";
// индекс последнего символа
int ind = text.Length - 1;
// вырезаем последний символ
text = text.Remove(ind); 
Console.WriteLine(text);    // Хороший ден
 
// вырезаем первые два символа
text = text.Remove(0, 2);
Console.WriteLine(text);    // роший ден

Первая версия метода Remove принимает индекс в строке, начиная с которого надо удалить все символы. Вторая версия принимает еще один параметр — сколько символов надо удалить.

Замена

Чтобы заменить один символ или подстроку на другую, применяется метод Replace:

string text = "хороший день";
 
text = text.Replace("хороший", "плохой");
Console.WriteLine(text);    // плохой день
 
text = text.Replace("о", "");
Console.WriteLine(text);    // плхй день

Во втором случае применения функции Replace строка из одного символа «о» заменяется на пустую строку, то есть фактически удаляется из текста. Подобным способом легко удалять какой-то определенный текст в строках.

Смена регистра

Для приведения строки к верхнему и нижнему регистру используются соответственно функции ToUpper() и ToLower():

string hello = "Hello world!";
 
Console.WriteLine(hello.ToLower()); // hello world!
Console.WriteLine(hello.ToUpper()); // HELLO WORLD!

Класс StringBuilder

Хотя класс System.String предоставляет нам широкую функциональность по работе со строками, все таки он имеет свои недостатки. Прежде всего, объект String представляет собой неизменяемую строку. Когда мы выполняем какой-нибудь метод класса String, система создает новый объект в памяти с выделением ему достаточного места. Удаление первого символа — не самая затратная операция. Однако когда подобных операций множество, а объем текста, для которого надо выполнить данные операции, также не самый маленький, то издержки при потере производительности становятся более существенными.

Чтобы выйти из этой ситуации во фреймворк .NET был добавлен новый класс StringBuilder, который находится в пространстве имен System.Text. Этот класс представляет динамическую строку.

Создание StringBuilder

Для создания объекта StringBuilder можно использовать ряд его конструкторов. Прежде всего можно создать пустой StringBuilder:

using System.Text;
 
StringBuilder sb = new StringBuilder();

Можно сразу инициализировать объект определенной строкой:

StringBuilder sb = new StringBuilder("Привет мир");

С помощью метода ToString() мы можем получить строку, которая хранится в StringBuilder:

var sb = new StringBuilder("Hello World");
Console.WriteLine(sb.ToString());    //  Hello World

Либо можно просто передать объект StringBuilder:

var sb = new StringBuilder("Hello World");
Console.WriteLine(sb);    //  Hello World

Длина и емкость StringBuilder

Для хранения длины строки в классе StringBuilder определенно свойство Length. Однако есть и вторая величина — емкость выделенной памяти. Это значение хранится в свойстве Capacity. Емкость — это выделенная память под объект. Установка емкости позволяет уменьшить выделения памяти и тем самым повысить производительность.

Если строка, которая передается в конструктор StringBuilder, имеет длину 16 символов или меньше, то начальная ёмкость в StringBuilder равна 16. Если начальная строка больше 16 символов, то в качестве начальной емкости StringBuilder будет использовать длину строки.

Например, посмотрим, что содержат данные свойства:

using System.Text;
 
StringBuilder sb = new StringBuilder("Привет мир");
Console.WriteLine($"Длина: {sb.Length}");       // Длина: 10
Console.WriteLine($"Емкость: {sb.Capacity}");   // Емкость: 16

Хотя в данном случае длина равна 10 символов, но реально емкость будет составлять по умолчанию 16 символов. То есть мы видим, что при создании строки StringBuilder выделяет памяти больше, чем необходимо этой строке. При увеличении строки в StringBuilder, когда количество символов превосходит начальную емкость, то емкость увеличивается в два и более раз.

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

var sb = new StringBuilder(32);

StringBuilder также позволяет сразу задать строку и емкость:

var sb = new StringBuilder("Привет мир", 32);

Операции со строками в StringBuilder

Для операций над строками класс StringBuilder определяет ряд методов:

  • Append: добавляет подстроку в объект StringBuilder
  • Insert: вставляет подстроку в объект StringBuilder, начиная с определенного индекса
  • Remove: удаляет определенное количество символов, начиная с определенного индекса
  • Replace: заменяет все вхождения определенного символа или подстроки на другой символ или подстроку
  • AppendFormat: добавляет подстроку в конец объекта StringBuilder

Теперь посмотрим на примере метода Append() использование и преимущества класса StringBuilder:

using System.Text;
 
var sb = new StringBuilder("Название: ");
Console.WriteLine(sb);   // Название: 
Console.WriteLine($"Длина: {sb.Length}"); // 10
Console.WriteLine($"Емкость: {sb.Capacity}"); // 16
 
sb.Append(" Руководство");
Console.WriteLine(sb);   // Название: Руководство
Console.WriteLine($"Длина: {sb.Length}"); // 22
Console.WriteLine($"Емкость: {sb.Capacity}"); // 32
 
sb.Append(" по C#");
Console.WriteLine(sb);   // Название: Руководство по C#
Console.WriteLine($"Длина: {sb.Length}"); // 28
Console.WriteLine($"Емкость: {sb.Capacity}"); // 32

При создании объекта StringBuilder выделяется память по умолчанию для 16 символов, так как длина начальной строки меньше 16.

Дальше применяется метод Append — этот метод добавляет к строке подстроку. Так как при объединении строк их общая длина — 22 символа — превышает начальную емкость в 16 символов, то начальная емкость удваивается — до 32 символов.

Если бы итоговая длина строки была бы больше 32 символов, то емкость расширялась бы до размера длины строки.

Далее опять применяется метод Append, однако финальная длина уже будет 28 символов, что меньше 32 символов, и дополнительная память не будет выделяться.

Используем остальные методы StringBuilder:

var sb = new StringBuilder("Привет мир");
sb.Append("!");
sb.Insert(7, "компьютерный ");
Console.WriteLine(sb);  // Привет компьютерный мир!
 
// заменяем слово
sb.Replace("мир", "world");
Console.WriteLine(sb);  // Привет компьютерный world!
 
// удаляем 13 символов, начиная с 7-го
sb.Remove(7, 13);
Console.WriteLine(sb);  // Привет world!
 
// получаем строку из объекта StringBuilder
string text = sb.ToString();
Console.WriteLine(text);    // Привет world!

Когда надо использовать класс String, а когда StringBuilder?

Microsoft рекомендует использовать класс String в следующих случаях:

  • При небольшом количестве операций и изменений над строками
  • При выполнении фиксированного количества операций объединения. В этом случае компилятор может объединить все операции объединения в одну
  • Когда надо выполнять масштабные операции поиска при построении строки, например IndexOf или StartsWith. Класс StringBuilder не имеет подобных методов.

Класс StringBuilder рекомендуется использовать в следующих случаях:

  • При неизвестном количестве операций и изменений над строками во время выполнения программы
  • Когда предполагается, что приложению придется сделать множество подобных операций

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *