Чтение нескольких строк из большого файла в порядке возрастания - PullRequest
2 голосов
/ 09 октября 2019

У меня очень большой текстовый файл, более 1 ГБ, и у меня есть список целых чисел, представляющих номера строк, и необходимо создать другой файл, содержащий текст номеров строк исходных файлов в новом файле.

Пример оригинального большого файла:

ogfile line 1
some text here
another line
blah blah

Поэтому, когда я получаю список «2,4,4,1», выходной файл должен выглядеть так:

some text here
blah blah
blah blah
ogfile line 1

Я попытался string lineString = File.ReadLines(filename).Skip(lineNumList[i]-1).Take(1).First();

, но это занимает много времени, так как файл должен быть прочитан, пропущен до рассматриваемой строки, а затем перечитан в следующий раз ... и мы говорим миллионы строк вфайл объемом 1 ГБ, а мой List<int> - это тысячи номеров строк.

Существует ли лучший / более быстрый способ чтения одной строки или считыватель может перейти к определенному номеру строки, не пропуская строку за строкой

Ответы [ 4 ]

5 голосов
/ 09 октября 2019

Старший бит здесь: вы пытаетесь решить проблему с базой данных, используя текстовые файлы. Базы данных предназначены для решения больших проблем с данными;текстовые файлы, как вы обнаружили, ужасны при произвольном доступе. Используйте базу данных, а не текстовый файл .

Если вы одержимы использованием текстового файла, вам нужно воспользоваться тем, что вы знаете овероятные параметры проблемы . Например, если вы знаете, что, как вы подразумеваете, существует ~ 1 млн строк, каждая строка составляет ~ 1 КБ, а набор извлекаемых строк составляет ~ 0,1% от общего количества строк, тогда вы можете найти эффективное решение, такое какthis:

  • Создайте набор, содержащий номера строк для чтения. Набор должен быстро проверять членство.
  • Создайте словарь, который сопоставляет номера строк и их содержимое. Это должно быть быстро, чтобы искать по ключу и быстро добавлять новые пары ключ / значение.
  • Читайте каждую строку файла по одной;если номер строки находится в наборе, добавьте содержимое в словарь.
  • Теперь выполните итерацию списка номеров строк и сопоставьте содержимое словаря;теперь у нас есть последовательность строк.
  • Дамп этой последовательности в файл назначения.

У нас есть пять операций, так что, надеюсь, это около пяти строк кода.

void DoIt(string pathIn, IEnumerable<int> lineNumbers, string pathOut)
{
  var lines = new HashSet<int>(lineNumbers);
  var dict = File.ReadLines(pathIn)
    .Select((lineText, index) => new KeyValuePair<int, string>(index, lineText))
    .Where(p => lines.Contains(p.Key))
    .ToDictionary(p => p.Key, p => p.Value);
  File.WriteAllLines(pathOut, lineNumbers.Select(i => dict[i]));
}

ОК, получил в шесть. Довольно хорошо.


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

И наоборот, можем ли мы извлечь дополнительную эффективность? Да, при условии, что мы знаем факты о вероятных входных данных . Предположим, например, что мы знаем, что один и тот же файл будет повторяться несколько раз, но с разными наборами номеров строк, но эти наборы, вероятно, будут перекрываться. В этом случае мы можем повторно использовать словари вместо их восстановления. То есть, предположим, что предыдущая операция оставила Dictionary<int, string>, вычисленную для строк (10, 20, 30, 40) и файла X. Если затем приходит запрос для строк (30, 20, 10) для файла X, мы ужеиметь словарь в памяти.

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

3 голосов
/ 09 октября 2019

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

Редактировать: БлагодаряЭрик Липперт, я включил HashSet для быстрого поиска.

List<int> lineNumbers = new List<int>{2,4,4,1};
HashSet<int> lookUp = new HashSet<int>(lineNumbers);
Dictionary<int,string> lines = new Dictionary<int,string>();

using(StreamReader sr = new StreamReader(inputFile)){
    int lastLine = lookUp.Max();
    for(int currentLine=1;currentLine<=lastLine;currentLine++){
        if(lookUp.Contains(currentLine)){
            lines[currentLine]=sr.ReadLine();
        }
        else{
            sr.ReadLine();
        }       
    }   
}
using(StreamWriter sw = new StreamWriter(outputFile)){
    foreach(var line in lineNumbers){
        sw.WriteLine(lines[line]);
    }
}
2 голосов
/ 09 октября 2019

Вы можете использовать метод StreamReader и ReadLine, чтобы читать строку за строкой, не шокируя память:

var lines = new Dictionary<int, string>();
var indexesProcessed = new HashSet<int>();
var indexesNew = new List<int> { 2, 4, 4, 1 };

using ( var reader = new StreamReader(@"c:\\file.txt") )
  for ( int index = 1; index <= indexesNew.Count; index++ )
    if ( reader.Peek() >= 0 )
    {
      string line = reader.ReadLine();
      if ( indexesNew.Contains(index) && !indexesProcessed.Contains(index) )
      {
        lines[index] = line;
        indexesProcessed.Add(index);
      }
    }

using ( var writer = new StreamWriter(@"c:\\file-new.txt", false) )
  foreach ( int index in indexesNew )
    if ( indexesProcessed.Contains(index) )
      writer.WriteLine(lines[index]);

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

Мы используем HashSet для хранения обработанных индексов для ускорения. Содержит вызовы, так как вы указываете, что размер файла может превышать 1 ГБ.

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

Вывод:

some text here
blah blah
blah blah
ogfile line 1
0 голосов
/ 09 октября 2019

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

Посколькуномер строки 1, а массивы 0 (т. е. номер строки 1 - индекс массива 0), мы вычитаем 1 из номера строки при указании индекса массива:

static void Main(string[] args)
{
    var inputFile = @"f:\private\temp\temp.txt";
    var outputFile = @"f:\private\temp\temp2.txt";

    var fileLines = File.ReadAllLines(inputFile);
    var linesToDisplay = new[] {2, 4, 4, 1};

    // Write each specified line in linesToDisplay from fileLines to the outputFile
    File.WriteAllLines(outputFile, 
        linesToDisplay.Select(lineNumber => fileLines[lineNumber - 1]));

    GetKeyFromUser("\n\nDone! Press any key to exit...");
}

Другой способ сделать это, который должен быть более эффективным, - это чтение файла только до максимального номера строки (с использованием метода ReadLines), а не чтение всего файла (с использованием метода ReadAllLines)и сохраните только строки, которые нам нужны, в словаре, который отображает номер строки в текст строки:

static void Main(string[] args)
{
    var inputFile = @"f:\private\temp\temp.txt";
    var outputFile = @"f:\private\temp\temp2.txt";

    var linesToDisplay = new[] {2, 4, 4, 1};
    var maxLineNumber = linesToDisplay.Max();
    var fileLines = new Dictionary<int, string>(linesToDisplay.Distinct().Count());

    // Start lineNumber at 1 instead of 0
    int lineNumber = 1;

    // Just read up to the largest line number we need 
    // and save the lines we care about in our dictionary
    foreach (var line in File.ReadLines(inputFile))
    {
        if (linesToDisplay.Contains(lineNumber))
        {
            fileLines[lineNumber] = line;
        }

        // Increment our lineNumber and break if we're done
        if (++lineNumber > maxLineNumber) break;
    }

    // Write the output to our file
    File.WriteAllLines(outputFile, linesToDisplay.Select(line => fileLines[line]));

    GetKeyFromUser("\n\nDone! Press any key to exit...");
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...