Как быстро заменить символы в массиве

Я использую XML Text reader в XML файле, который может содержать недопустимые для читателя символы. Моя первоначальная мысль заключалась в том, чтобы создать мою собственную версию считывателя потока и очистить плохие символы, но это сильно замедляет мою программу.

public class ClensingStream : StreamReader
{
        private static char[] badChars = { '\x00', '\x09', '\x0A', '\x10' };
    //snip
        public override int Read(char[] buffer, int index, int count)
        {
            var tmp = base.Read(buffer, index, count);

            for (int i = 0; i < buffer.Length; ++i)
            {
                //check the element in the buffer to see if it is one of the bad characters.
                if(badChars.Contains(buffer[i]))
                    buffer[i] = ' ';
            }

            return tmp;
        }
}

согласно моему профилировщику, код тратит 88% своего времени в if(badChars.Contains(buffer[i])), каков правильный способ сделать это, чтобы я не вызывал ужасную медлительность?

Ответ 1

Причина, по которой он проводит столько времени в этой строке, состоит в том, что метод Contains проходит через массив для поиска символа.

Поместите символы в HashSet<char> вместо:

private static HashSet<char> badChars =
  new HashSet<char>(new char[] { '\x00', '\x09', '\x0A', '\x10' });

Код для проверки, содержит ли набор символ, выглядит так же, как при поиске в массиве, но он использует хэш-код символа для поиска его вместо того, чтобы перебирать все элементы в массиве.

В качестве альтернативы вы можете поместить символы в переключатель, таким образом, компилятор создаст эффективное сравнение:

switch (buffer[i]]) {
  case '\x00':
  case '\x09':
  case '\x0A':
  case '\x10': buffer[i] = ' '; break;
}

Если у вас больше символов (пять или шесть IIRC), компилятор фактически создаст хеш-таблицу для поиска дел, так что это будет похоже на использование HashSet.

Ответ 2

У вас могут быть лучшие результаты с помощью инструкции switch:

switch (buffer[i])
{
    case '\x00':
    case '\x09':
    case '\x0A':
    case '\x10':
        buffer[i] = ' ';
        break;
}

Это должно быть скомпилировано до быстрого кода компилятором JIT во время выполнения. Черт, компилятор тоже может приблизиться. Вам также не нужен метод вызова.

Ответ 3

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

Однако ваш код также выглядит хорошо для меня, я думаю, регулярное выражение также не может сделать ничего, кроме поиска в вашем тексте... и вам нужно взять строку, в которой вам не нужно делать с другим варианты.

Ответ 4

вы можете проверить, насколько он оптимизирован, просто проверяя символы чтения, делая его

for (int i = index; i < index + count; i++){
  //etc
}

Не знаю, насколько это вам поможет, вам нужно будет профилировать приложение реального мира, чтобы проверить

Ответ 5

Попробуйте преобразовать char[] в строку, а затем с помощью IndexOfAny.

Ответ 6

Вы можете использовать булевский массив

char[] badChars = { '\x00', '\x09', '\x0A', '\x10' };
char maxChar = badChars.Max();
Debug.Assert(maxChar < 256);
bool[] badCharsTable = new bool[maxChar + 1];

Array.ForEach(badChars, ch => badCharsTable[ch] = true);

и замените badChars.Contains(...) на (ch < badCharsTable.Length && badCharsTable[ch]).

Изменить: наконец, успел улучшить ответ.