Разделение строки C # без генерации массива

#c# #string #split

#c# #строка #разделение

Вопрос:

У меня в памяти огромная строка (несколько сотен мегабайт, я не могу загрузить ее частично), интересно, есть ли какой-нибудь простой способ разделить эту строку на строки, разделенные Environment.NewLine: a) без дублирования этой структуры в памяти (если я использую string.Разделение будет дублировать его) б) с некоторыми методами c # по умолчанию / минимальным количеством эффективного (когда дело доходит до производительности) кода

Практически я хотел бы иметь метод GetNextLineFromString .

Комментарии:

1. Итак, StreamReader? Конечно, для этого требуется поток памяти поверх байтов строки, но почему он вообще хранится в памяти в виде строки?

2. В дополнение к комментарию @Andrewwwilliamson, поскольку исходная строка настолько огромна, вы должны иметь возможность довольно легко проверить String.Split использование памяти, используя, скажем, диспетчер задач.

3. @CodeCaster ну, я использую какую-то устаревшую dll, которая выдает результат в виде строки

4. substrings share the same memory as the original string Я не думаю, что это правда, несмотря на то, что кто-то поддержал ваш комментарий. dotnetfiddle.net/4B6l91 — запустите этот код локально и протестируйте его на себе.

5. Мои извинения, я рад, что кто-то это подтвердил, потому что я неправильно запомнил. Я не нашел никакой официальной документации о Substring поведении, но ваш ответ демонстрирует это довольно четко, и я нашел предупреждение , которое предполагает, что substring создает копию памяти

Ответ №1:

Вы можете попробовать перечислять строки, например

 private static IEnumerable<string> LinesFromString(string value) {
  if (string.IsNullOrEmpty(value))
    yield break;

  int last = 0;

  while (true) {
    int next = value.IndexOf(Environment.NewLine, last);

    if (next < 0) {
      yield return value.Substring(last);

      yield break;
    }

    yield return value.Substring(last, next - last);

    last = next   Environment.NewLine.Length;
  }
}
 

ДЕМОНСТРАЦИЯ:

 string text = string.Join(Environment.NewLine, 
  "ABC", 
  "D", 
  "", // <- Empty line
  "EF"
);

Console.Write(string.Join(";", LinesFromString(text)));
 

Результат:

 ABC;D;;EF
 

Если вам нужна N строка, вы можете выполнить запрос text с помощью Linq:

 int N = 1; // N is zero-based

string line = LinesFromString(text)
  .Skip(N - 1)
  .FirstOrDefault(); // Either Nth string or null
 

Редактировать: если вы используете .Net Core 3.1, .Net Standard 2.1 или выше вы можете попробовать представить long string as ReadOnlyMemory<char> и работать с его фрагментами; например

 private static IEnumerable<ReadOnlyMemory<char>> LinesFromMemory(ReadOnlyMemory<char> value) {
  int last = 0;

  while (true) {
    int at = value.Span.Slice(last).IndexOf(Environment.NewLine);

    if (at < 0) {
      yield return value.Slice(last);

      yield break;
    }

    yield return value.Slice(last, at);

    last  = at   Environment.NewLine.Length;
  }
} 
 

Затем

 string huge = string.Join(Environment.NewLine, 
  "ABC", 
  "D", 
  "", // <- Empty line
  "EF"
);

// Note, memory is not a string, but some kind of pointer (to huge) 
foreach (var memory in LinesFromMemory(huge.AsMemory())) {
  // Let's convert memory to string and print it out
  string st = memory.ToString();

  Console.Write(st);
  Console.Write(';'); 
}
 

Ответ №2:

Не могли бы вы сохранить строку во временный файл, использовать String.Spilt() via a StreamReader , а затем, наконец, удалить файл?

Комментарии:

1. docs.microsoft.com/en-us/dotnet/api /… , вероятно, проще, если вы собираетесь сначала записать его в файл.

2. Конечно, согласен, @mjwills, но моя точка зрения была в основном об освобождении памяти, если это основная проблема.