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, но для моих нужд данного решения было достаточно.