17 November 2009

Silverlight и кодировки

Silverlight

Silverlight довольно удобен тем, что предоставляет почти «полноценный» .net в клиентских приложениях. Если бы не это «почти», то всё было бы замечательно. Недавно мне понадобилась необходимость использовать одну .net-библиотеку. Я начал с того, что переставил настройки проекта на silverlight и добавил её к основному проекту. Приложение откомпилировалось и я уже обрадовался, что вот так легко можно использовать уже имеющиеся наработки, но радоваться было рано...



Приложение начало валиться в самых необычных местах. Отладка показала, что библиотека не может найти необходимой ей кодировки latin1. Я подумал, что кодировка в данном случае называется немного по-другому, и начал гуглить. Оказалось всё намного хуже: как сообщает сам микрософт, ядро сильверлайта поддерживает только 3 кодировки (utf-8, utf-16LE, utf-16BE), а нужная мне библиотека требовала latin1 (да и в наших реалиях — в некоторых случаях windows-1251).



Upd: перевести на юникод библиотеку было нельзя, т.к. её задача состояла в том, чтобы прочитать с клиентской машины файлы в той кодировке, в которой они там сохранены.



Готовых решений я не нашёл, только аналогичные жалобы на форумах. Поэтому решил написать собственный велосипед.



Источник кодировок для велосипеда — «полноценный» настольный дотнет. Т.к. требовались только однобайтные кодировки, то все символы из них легко получить, передав на вход Encoding.GetChars массив, заполненный от 0 до 255.



Сначала «на коленке» была собрана первая версия метода GetString(byte[] bytes, int start, int count):


var sb = new System.Text.StringBuilder(count) { Length = count };
count += start;
for (var i = start; i < count; i++)
sb[i - start] = chars[bytes[i]];
return sb.ToString();

* This source code was highlighted with Source Code Highlighter
.

Далее захотелось слегка увеличить производительность, и lookup по массиву был заменён на switch:


var sb = new System.Text.StringBuilder(count) { Length = count };
count += start;
for (var i = start; i < count; i++) {
char tmp;
switch (bytes[i]) {
case 0: tmp = '\u0000'; break;
case 1: tmp = '\u0001'; break;
...
default: tmp = '\u02D9'; break;
}
sb[i - start] = tmp;
}
return sb.ToString();

* This source code was highlighted with Source Code Highlighter
.

Заодно я решил развеять свои сомнения насчёт того, как быстрее работать с классом StringBuilder
вариант 3 (использование .Append() вместо индекса):


var sb = new System.Text.StringBuilder(count);
count += start;
for (var i = start; i < count; i++) {
switch (bytes[i]) {
case 0: sb.Append('\u0000'); break;
case 1: sb.Append('\u0001'); break;
...
default: sb.Append('\u02D9'); break;
}
}
return sb.ToString();

* This source code was highlighted with Source Code Highlighter
.

Все методы показали невысокую производительность, почти на порядок медленней встроенной реализации utf-8 на файлах с английским текстом (т.е. когда utf-8 тоже умещает 1 байт в 1 символ).


Тогда я решил использовать просто массив символов char[]:



var sb = new char[count];
for (var i = 0; i < sb.Length; i++) {
switch (bytes[i + start]) {
case 0: sb[i] = '\u0000'; break;
case 1: sb[i] = '\u0001'; break;
...
default: sb[i] = '\u02D9'; break;
}
}
return new string(sb);

* This source code was highlighted with Source Code Highlighter
.

Update: в комментариях посоветовали# объединить первый и последний метод, получился такой код:


var result = new char[count];
for (var i = 0; i < result.Length; i++)
result[i] = charMap[bytes[i + index]];
return result;

* This source code was highlighted with Source Code Highlighter
.

Так же был проверен аналогичный код, где для первых 128 символов было использовано прямое приведение к (char), но он получился медленнее (т.к. lookup по небольшому массиву выполняется быстрее, чем сравнение с числом, и приведение).



Замеры производительности:

Вариант Время, мс
utf-8 (встроенный) 140-156
№1 (array lookup) 1340-1352
№2 (StringBuilder[]) 1562-1578
№3 (StringBuilder.Append) 1344-1375
№4 (char[]) 451-468
№5 (char[] + array lookup) 306-319

Результат, я думаю, очевиден, и мной был выбран метод №4 метод №5.



Спасибо всем пользователям за полезные комментарии! Удалось сэкономить ещё несолько миллисекунд и десятки килобайт сгенерированного кода ;)



К сожалению, данные кодировки нельзя напрямую «подсунуть» классам StreamReader/StreamWriter, но для моих нужд данного решения было достаточно.

Tags:SilverlightEncodingGetEncodingwindows-1251производительность StringBuilder
Hubs: Silverlight
+5
1.7k 10
Comments 38
Popular right now
Профессия iOS-разработчик
November 30, 202075,000 ₽SkillFactory
Основы HTML и CSS
November 30, 2020FreeНетология
Frontend-разработчик с нуля
November 30, 202077,940 ₽Нетология
Курс по аналитике данных
November 30, 202053,500 ₽SkillFactory
SMM-менеджер
November 30, 202059,998 ₽GeekBrains