Кратко съдържание


Escaping последователности



страница63/73
Дата21.07.2018
Размер9.03 Mb.
#76887
1   ...   59   60   61   62   63   64   65   66   ...   73

Escaping последователности


Когато искаме да съхраним в сорс кода на програмата специален знак или низ, който не може да се изпише директно в кода (например символа за празен ред), трябва да използваме т. нар. escaping последо­ва­телности. Те представляват последователност от символи със специално значение и при компилация се заместват със съответния специален символ.

В таблицата са дадени някои често използвани escaping последовател­ности:



Изписване

Значение

Код

\n

символ за нов ред LF (line feed)

\x0A

\r

символ CR (carriage return)

\x0D

\"

символ двойни кавички

\x22

\t

символ табулация

\x09

\'

символ апостроф

\x27

\\

символ обратна наклонена черта

\x5C

\0

символ null

\x00

В езика C# чрез escaping последователности може да се използва произ­волен ASCII и Unicode символ. Това става чрез следните escaping последо­вателности:

  • \xXX – обозначава символ с ASCII код XX (шестнайсетично), напри­мер \x0A е символ за нов ред (LF), а \0x41 е буквата A (главна, латинска)

  • \uXXXX – обозначава Unicode символ с номер XXXX (шестнайсетично), напр. \u03A3 е символа Σ, а \u20AC е символа

В C# символът @ пред даден низ указва, че низът е цитиран (verbatim string) и escaping последователностите в него се игнорират.

Това е удобно, когато задаваме пълния път за достъп до файл, например:



string fileName = @"C:\Windows\System32\calc.exe";

В горния пример обратната наклонена чертичка "\" не задава escaping последователности, защото низът е цитиран и се възприема без измене­ния, точно както е зададен.

Следва още един пример, който показва как да ползваме escaping после­довател­ности и цитиране на низове в C#:



static void Main()

{

string message = @"Добре дошли на биреното парти,



скъпи приятели! Има Загорка, Beck's,

Каменица, Шуменско, Ариана и Амстел.";

string copyright = "\xA9 2004 Бира Софт";

System.Windows.Forms.MessageBox.Show(message, copyright);

}


Резултатът от изпълнението на програмата е следният диалогов прозорец:

Специалният символ "©" се получава чрез escaping последователността \xA9. Съдържанието на диалога е цитиран низ и това запазва новите редове в текста и позволява той да бъде разположен на няколко реда в сорс кода.

В примера използвахме класа System.Windows.Forms.MessageBox. Този клас е дефиниран в системното асембли System.Windows.Forms.dll, което не се включва по подразбиране към проекта при създаване на конзолно приложение от VS.NET. По тази причина горният примерен код може да не се компи­лира успешно, ако се постави в конзолно приложение.

Ако компилаторът съобщи за грешка с текст "The type or namespace name 'Windows' does not exist in the class or namespace 'System' (are you missing an assembly reference?)", трябва да добавим асемблито System.Windows. Forms.dll към проекта. Това става като изберем "Add Reference" от кон­текстното меню на Solution Explorer във VS.NET и след това изберем съответното асембли от диалога за добавяне на референция:






Ефективно конструиране на низове чрез класа StringBuilder


Ако ви се наложи да добавяте символи към вече дефиниран низ, може би този код ще ви се стори логичен:

string result = "";

for (int i=0; i<10000; i++)

{

result += "някакъв текст";



}

Проблемът с долепването на низове


Този код има един много сериозен недостатък. Тъй като в .NET Framework стойността на обект от тип string не може да бъде променяна след като той веднъж е бил инициали­зиран, долепването на низ към друг низ е много бавна операция. Тя работи на няколко стъпки:

  1. Създава се нов междинен обект (буфер) за резултата.

  2. Копира се първият низ в началото на буфера.

  3. Копира се вторият обект в буфера, след края на първия.

  4. Създава се нов обект, символен низ и в него се копира стойността от буфера.

Старият низ и буферът остават неизползвани и се отбелязват за почист­ване от систе­мата за почистване на паметта (garbage collector).

Когато изпълним процеса на долепване на низове в цикъл 10 000 пъти, се получава чувствително забавяне на програмата, което може да се избег­не, ако низът се конструира по правилния начин.





Никога не конструирайте низове чрез долепване на техни части в цикъл!

Поради неизменяемостта на символните низове в .NET Framework и нуж­дата от допълнителен буфер, използването на операторите + и += и мето­дите Insert(…) и Remove(…) e изключително неефективно при много­крат­но изпълнение. Когато е необходимо един низ да бъде променян много пъти, много по-удачно е да се ползва класът StringBuilder.

Решението на проблема – класът StringBuilder


За разлика от String, класът StringBuilder представлява изменяема пос­ледователност от символи. Той съдържа указател към масив от символи, всеки от които може да бъде променян. Могат да бъдат добавяни и нови символи в края на низа. При добавянето на нови символи се извършва проверка дали общата дължина на низа не надхвърля заделената памет (т. нар. капацитет). Ако дължината е по-голяма от капацитета, StringBuilder заделя нов масив, който е двойно по-голям от предишния, т. е. капацитетът се удвоява.

Горният пример може да се преработи да използва StringBuilder по следния начин:



StringBuilder resultBuilder = new StringBuilder();

for (int i=0; i<10000; i++)

{

resultBuilder.Append("някакъв текст");



}

string result = resultBuilder.ToString();



По този начин ефективността е много пъти по-голяма, защото при всяко долепване на низ не се създават нови обекти и не се копират низове от едно място от паметта в друго. Понеже капацитетът нараства двойно при нужда, броят изпълнения на бавната операция "преоразмеряване" е малък.

Членове на StringBuilder


В таблицата по-долу е дадено описание на по-важните свойства на класа StringBuilder:

Свойство

Описание

Capacity

Извлича или задава капацитета (максималният брой символи, за които има заделена памет в текущата ин­станция). Обикновено StringBuilder заделя повече памет, отколкото му е необходима, за да не се налага преоразмеряване при добавяне или вмъкване на сим­воли или низове. Операцията преоразмеряване е бавна, защото е свързана с преместването на всички символи на нова позиция в паметта.

Chars[int]

Взима или задава символа на зададената позиция в инстанцията. В C# това свойство е реализирано като индексатор на класа StringBuilder.

Length

Взима или задава дължината на инстанцията, т. е. броя символи. Чрез задаване на по-малка дължина от теку­щата може по ефективен начин да се премахват символи от края на текущия низ.

MaxCapacity

Връща максималния брой символи, които могат да бъдат съхранени в инстанцията (в .NET Framework 1.1 това ограничение е 2 147 483 647, т. е. 2 GB).

Методи за работа със StringBuilder:

Метод

Описание

Append(obj)

Добавя низ или низовото представяне на даден обект към края на инстанцията.

AppendFormat(…)

Добавя низовото представяне на обект или група обекти, форматирани по даден формати­ращ низ.

EnsureCapacity(int capacity)

Обезпечава минималния брой символи, които могат да бъдат съхранени в инстанцията, като заделя памет за посочения брой символи, ако текущото количество заделена памет е по-малко от указаното.

Equals(obj)

Проверява дали даден обект е равен на теку­щата инстанция. Извършва се сравнение символ по символ.

Insert(index, obj)

Вмъква низово представяне на ука­зания обект с начало указаната позиция.

Remove(int index, int lenght)

Премахва указана област от символи.

Replace(…)

Замества всеки срещнат указан символ или низ с друг указан символ или низ.

ToString(…)

Конвертира StringBuilder към String.

Използване на StringBuilder – пример


За да илюстрираме по-добре работата със StringBuilder, ще дадем пример за неговото използване за модифициране на символни низове. Функцията UpcaseTextInBrackets(…) приема като параметър низ и връща като резултат същия низ, като частта от низа, която е била в скоби, се преобразува в главни букви:

public static string UpcaseTextInBrackets(string aText)

{

StringBuilder result = new StringBuilder(aText);



int brackets = 0;

for (int i=0; i

{

if (result[i] == '(')



{

brackets++;

}

else if (result[i] == ')')



{

brackets--;

}

else if (brackets > 0)



{

result[i] = Char.ToUpper(result[i]);

}

}

string resultStr = result.ToString();



return resultStr;

}


От указания в конструктора низ се създава обект StringBuilder, който позволява да бъдат променяни отделните символи чрез индексатора this. След като приключи промяната на низа се използва StringBuilder. ToString(), за да се извлече променения низ във вид на String.

При създаването на обекта result от тип StringBuilder е използван конструкторът StringBuilder(string). Чрез него на инстанцията на StringBuilder се задава първоначалната стойност, а като размер на масива от символи се задава най-близката по-голяма от дължината на низа степен на 2, или 16, ако низът е по-къс от 16 знака. Например ако извикаме разгледаната в примера функция UpcaseTextInBrackets(…) с параметър "Test", размерът на заделената памет, който можем да прове­рим чрез свойството Capacity, ще е 16. Ако подадем като параметър низ с дължина от 100 символа, за масива ще бъдат заделени 128 позиции.

Заделянето на повече от указания брой позиции позволява при добавя­нето на неголям брой символи да се избегне заделянето на нов масив и копирането на данните в него, защото това е бавна операция. Заделяната допълнителна памет е с размер, близък до дължината на низа, за да не се използва прекомерно количество памет.

Задаване на първоначален размер за StringBuilder


Винаги, когато се работи със StringBuilder и е приблизително известен очакваният размер на резултатния низ, който ще бъде конструиран, се препоръчва да се задели предварително количеството памет, необходимо за него или дори малко повече от него (да има малък резерв). Това увеличава значително производителността, защото след първоначалното заделяне на паметта не се извършва нито едно преоразмеряване по време на работа, което е много бавна операция. Началният капацитет на StringBuilder може да се подаде в конструктора му.

Следният пример илюстрира динамично конструиране на низ (фактура в XML формат) като използва конструктора StringBuilder(int), чрез който задава първоначален размер на работния масив от символи. Така се избягва заделянето на нова памет при добавянето на текст чрез методите Append(…) и AppendFormat(…).



using System;

using System.Text;


class StringsDemo

{

static string CreateXmlInvoice(string aName, double aAmmount)



{

StringBuilder invoiceXml = new StringBuilder(1024);

invoiceXml.Append("\n");

invoiceXml.Append("\n");

invoiceXml.AppendFormat("\t{0}\n", aName);

invoiceXml.AppendFormat("\t{0}\n",

aAmmount);

invoiceXml.Append("\n");

return invoiceXml.ToString();

}
static void Main()

{

string invoice = CreateXmlInvoice("Бай Иван", 137.60);



Console.WriteLine(invoice);

}

}



Резултатът е изпълнението на примера е следният:


Сравнение на скоростта на String и StringBuilder – пример


За да илюстрираме разликата в производителността между долепването на низове с оператора "+" на класа String и долепването на низове с помощта на StringBuilder, ще използваме следния пример:

static void Main()

{

// Append strings directly - runs very slowly



long startTime = Environment.TickCount;

Console.WriteLine("Direct string concatenation started.");

string result = "";

for (int i=0; i < 15000; i++)

{

result += "Дайте бира! ";



}

long endTime = Environment.TickCount;

long duration = endTime-startTime;

Console.WriteLine("Operation took {0:F8} sec.",

(decimal)duration/1000);
// Append strings with StringBuilder - the correct way

startTime = Environment.TickCount;

Console.WriteLine("String concatenation with StringBuilder started.");

StringBuilder resultBuilder = new StringBuilder();

for (int i=0; i < 15000; i++)

{

resultBuilder.Append("Дайте бира! ");



}

result = resultBuilder.ToString();

endTime = Environment.TickCount;

duration = endTime-startTime;

Console.WriteLine("Operation took {0:F8} sec.",

(decimal)duration/1000);

}


В примера се измерва времето за изпълнение на 15000 слепвания на низ, реализиран по два начина – чрез оператора += от класа String и по правилния начин – чрез класа StringBuilder. Полученият резултат е подобен на следния:

Виждаме, че има огромна разлика във времето за изпълнение на двете реализации на слепванията на 15000 низа. При първата реализация във всяко изпълнение на цикъла се създава нов обект, друг обект се маркира за унищожаване и се копират данни между двата. Тъй като тази операция се изпълнява хиляди пъти, това довежда до голямо забавяне в изпълне­нието. Втората реализация работи няколко хиляди пъти по-бързо, защото не изпълнява постоянно тежката операция "преместване на низ".

Понякога при изпълнението на горния пример е възможно да бъде отче­тено време 0,00000000 секунди за изпълнението на втората реали­зация, но това се дължи на слабата прецизност на таймера, с който се отчита времето.




Сподели с приятели:
1   ...   59   60   61   62   63   64   65   66   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница