Насколько быстр String.Substring относительно других методов обработки строк? - PullRequest
4 голосов
/ 22 мая 2009

Я использую VB.NET для обработки длинной записи фиксированной длины. Кажется, самый простой вариант - загрузить всю запись в строку и использовать подстроку для доступа к полям по позиции и длине. Но кажется, что в методе Substring будет некоторая избыточная обработка, которая происходит при каждом отдельном вызове. Это заставило меня задуматься о том, могу ли я получить лучшие результаты, используя подход на основе потоков или массивов.

Содержимое начинается с байтового массива, содержащего символьные данные UTF8. Несколько других подходов, о которых я подумал, перечислены ниже.

  1. Загрузка строки в StringReader и чтение ее блоков за раз
  2. Преобразование байтового массива в массив символов и доступ к символам в массиве
  3. (Этот кажется глупым, но я его выкину) Копирование байтового массива в поток памяти и использование StreamReader

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

Ответы [ 4 ]

6 голосов
/ 22 мая 2009

Первичная стоимость с подстрокой - это удаление подстроки в новую строку. Используя Reflector вы можете увидеть это:

private unsafe string InternalSubString(int startIndex, int length, bool fAlwaysCopy)
{
    if (((startIndex == 0) && (length == this.Length)) && !fAlwaysCopy)
    {
        return this;
    }
    string str = FastAllocateString(length);
    fixed (char* chRef = &str.m_firstChar)
    {
        fixed (char* chRef2 = &this.m_firstChar)
        {
            wstrcpy(chRef, chRef2 + startIndex, length);
        }
    }
    return str;
}

Теперь, чтобы попасть туда (обратите внимание, что это не Substring()), он должен пройти 5 проверок длины и тому подобное.

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

Если это, как правило, «одноразовый» доступ, то создайте его подстроку, в противном случае рассмотрите возможность разбиения вверх. Возможно, System.Data.DataTable будет полезным? Если вы выполняете множественный доступ и анализируете другие типы данных, DataTable выглядит для меня более привлекательным. Если вам нужна только одна запись в памяти за раз, тогда Dictionary<string,object> должно быть достаточно для хранения одной записи (имена полей должны быть уникальными).

В качестве альтернативы, вы можете написать собственный универсальный класс, который будет обрабатывать чтение записей фиксированной длины. Укажите начальный индекс каждого поля и тип поля. Длина поля определяется по началу следующего поля (исключение - последнее поле, которое может быть выведено из общей длины записи). Типы могут быть автоматически преобразованы, например int.Parse(), double.Parse(), bool.Parse() и т. Д.

RecordParser r = new RecordParser();
r.AddField("Name", 0, typeof(string));
r.AddField("Age", 48, typeof(int));
r.AddField("SystemId", 58, typeof(Guid));
r.RecordLength(80);

Dictionary<string, object> data = r.Parse(recordString);

Если рефлексия подходит вам по вкусу:

[RecordLength(80)]
public class MyRecord
{
    [RecordFieldOffset(0)]
    string Name;

    [RecordFieldOffset(48)]
    int Age;

    [RecordFieldOffset(58)]
    Guid Systemid;
}

Просто запустите свойства, где вы можете получить PropertyInfo.PropertyType, чтобы узнать, как обращаться с подстрокой из записи; вы можете вытянуть смещения и общую длину из атрибутов; и вернуть экземпляр вашего класса с заполненными данными. По сути, вы можете использовать отражение, чтобы извлечь информацию для вызова RecordParser.AddField () и RecordLength () из моего предыдущего предложения.

Затем заверните все в аккуратный маленький класс без суеты:

RecordParser<MyRecord> r = new RecordParser<MyRecord>();
MyRecord data = r.Parse(recordString);

Может даже зайти так далеко, чтобы вызвать r.EnumerateFile("path\to\file") и использовать синтаксис перечисления yield return для анализа записей

RecordParser<MyRecord> r = new RecordParser<MyRecord>();
foreach (MyRecord data in r.EnumerateFile("foo.dat"))
{
    // Do stuff with record
}
3 голосов
/ 22 мая 2009

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

1 голос
/ 23 мая 2009

То, что вы пытаетесь сделать, звучит как задача разбора. Если я правильно понимаю, вы загружаете огромную строку, которая содержит несколько полей и их значения. Для этого конкретного типа сценария Substring не будет особенно производительным. Для каждого поля и его значения вам необходимо вызвать Substring с определенной позицией и длиной в большей строке. Это довольно много накладных расходов.

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

Если ваша входная строка более сложная, чем просто последовательность полей и значений (то есть ее структурированная), вероятно, потребуется более сложный синтаксический анализатор. Существует много инструментов, таких как antler , которые предоставляют интегрированные среды, которые могут генерировать для вас грамматику, генерировать синтаксический анализатор и предоставлять хороший API для использования проанализированного содержимого.

0 голосов
/ 22 мая 2009

Как вы вообще читаете запись?

вы читаете символ за символом или построчно?

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

в случае, если вы должны прочитать один раз, а затем обработать, затем прочитать в строку и использовать StringReader, это позволит вам читать символ за символом или количеством символов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...