Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Delphi: Общие вопросы > Разбить файл на указанное количество строк \ Доработка


Автор: Kirilis2018 11.11.18, 08:11
Ребята, выручите в решении данного вопроса.
Задание: Есть текстовый файл, в котором 150000 строк. Указываю число 15000 (на сколько строк разбить) и программа разбивает его на файлы, в каждом из которых 15000 строк. Получаю результат в виде 10 файлов по 15000 строк в каждом. Если остается остаток меньше 15000 строк, то его записываем в последний файл.

Код что ниже - работает. Но меня смущает вот этот участок кода (Тут остаточная часть, текстового файла - грузится в память и жестко -забивает её. Как можно переделать построчно ?):
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // Если осталось прочитано строк меньше, чем значение Amount, то
            // эти строки будут содержаться в OutputFile. Все, что останется - загрузить
            // предыдущий файл и добавить к нему эти строки. В связи с этим приходиться
            // создавать дополнительный список.
            TempList := TStringList.Create;
            try
              TempList.LoadFromFile(OutputFileName + IntToStr(Index - 1) + FileExt);
              TempList.AddStrings(OutputFile);
              TempList.SaveToFile(OutputFileName + IntToStr(Index - 1) + FileExt);
            finally
              TempList.Free;



Вот основной код в котором есть участок кода что выше:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    procedure TForm1.aButton1Click(Sender: TObject);
    var
      FileToRead: TextFile;
      TempList: TStringList;
      OutputFile: TStringList;
      ReadString: String;
      OutputFileName: String;
      FileExt: String;
      Amount: Integer;
      CurrentStringIndex: Integer;
      LineIndex: Integer;
      Index: Integer;
      Delta: Integer;
      i: Integer;
    begin
      Amount := 6;
      OutputFileName := 'C:\FileName_';
      FileExt := '.txt';
     
      // Открываем файл для чтения
      AssignFile(FileToRead, 'C:\FileToRead.txt');
      try
        Reset(FileToRead);
     
        OutputFile := TStringlist.Create;
        try
          CurrentStringIndex := 0;
          LineIndex := 0;
          Index := 0;
     
          // Работаем со строками в файле, открытом для чтения
          while not EoF(FileToRead) do
            begin
              if LineIndex <> Amount then
                begin
                  // Читаем строку из файла
                  ReadLn(FileToRead, ReadString);
     
                  // Добавляем строку в список
                  OutputFile.Add(ReadString);
                  Inc(CurrentStringIndex);
                  Inc(LineIndex);
                end
              else  // Прочитали нужное количество строк - сохраним их!
                begin
                  LineIndex := 0;
                  OutputFile.SaveToFile(OutputFileName + IntToStr(Index) + FileExt);
                  OutputFile.Clear;
     
                  // Увеличиваем индекс файла
                  Inc(Index);
                end;
              end;
     
            // Если осталось прочитано строк меньше, чем значение Amount, то
            // эти строки будут содержаться в OutputFile. Все, что останется - загрузить
            // предыдущий файл и добавить к нему эти строки. В связи с этим прходится
            // создавать дополнительный список.
            TempList := TStringList.Create;
            try
              TempList.LoadFromFile(OutputFileName + IntToStr(Index - 1) + FileExt);
              TempList.AddStrings(OutputFile);
              TempList.SaveToFile(OutputFileName + IntToStr(Index - 1) + FileExt);
            finally
              TempList.Free;
            end;
        finally
          OutputFile.Free;
        end;
      finally
        // Закрываем файл
        CloseFile(FileToRead);
      end;
    end;

Автор: Славян 11.11.18, 09:26
Я б построчно делал, всё равно всё прокэшируется и существенного отличия не увижу:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    for i:=0 to N-1 do
    begin
      if (i mod K)=0 then поменятьФайлВыхода; // сравнение на каждой строке ничтожно по времени в сравнении с чтением строки (ниже)
      ReadLn( входной ... )
      WriteLn( выходной ...);
      Inc(i);
    end;


Добавлено
Inc, небось, не нужен, сие на автомате в Паскале (не помню). :blush:

Автор: Kirilis2018 11.11.18, 09:39
Славян, Идея, но к примеру если делить по 11 000 000 строк то даже в построчном чтении выскакивает ошибка нехватки памяти... Почему так ?

Автор: Славян 11.11.18, 10:27
Не, ничего не выскакивает! :no: Надо просто эту же строку и записывать, а не класть в список, кой потом хочется махом сбросить в файл. Сразу делайте (запись прочитаной строчки) и всё будет норм. :yes:

Автор: Kirilis2018 11.11.18, 10:30
Славян, Ух и ох :), помогите исправить. Чего то я сегодня вообще туплю и конкретно.

Автор: Славян 11.11.18, 10:32
Цитата Kirilis2018 @
если делить по 11 000 000 строк то даже в построчном чтении выскакивает ошибка нехватки памяти...
Именно такое быть может, думается, токмо если в файле всего несколько строк (а задали "делить по 11 млн"), но каждая строка - в несколько гигабайт. :jokingly: Ну тогда - да, надо будет тщательнее строку порциями читать/записывать. Но тут явно не этот (весьма экзотический) случай.

Добавлено
Цитата Kirilis2018 @
помогите исправить.
Да легко. Бросьте код с построчным чтением/записью, - посмотрим что и как там осталось дошлифовать.

Автор: Kirilis2018 11.11.18, 10:36
Славян, тут файл в 1 гигабайт и его нужно разделить на 11 000 000 строк. Строки не длинные, по 120 символов строка. И остаток, если меньше 11 000 000 записать в отдельный файл. Вот потому не знаю как правильно оптимизировать код и прошу потому помощи!

Добавлено
Славян, Вот мой код, посмотрите:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    procedure Tfrm_Main.Button1Click(Sender: TObject);
    var
      FileToRead: TextFile;
      TempList: TStringList;
      OutputFile: TStringList;
      ReadString: String;
      OutputFileName: String;
      FileExt,_FilePath: String;
      Amount: Integer;
      CurrentStringIndex: Integer;
      LineIndex: Integer;
      p: Integer;
      Index: Integer;
      Delta: Integer;
      i: Integer;
    begin
    if opendialog6.execute then
     
      Amount := 11000000;
      OutputFileName := 'File';
      FileExt := '.txt';
     
      // Открываем файл для чтения
      AssignFile(FileToRead, opendialog6.filename);
      try
        Reset(FileToRead);
     
        OutputFile := TStringlist.Create;
        try
          CurrentStringIndex := 0;
          LineIndex := 0;
          Index := 1;
     
          // Работаем со строками в файле, открытом для чтения
          while not EoF(FileToRead) do
            begin
              if LineIndex <> Amount then
                begin
                  // Читаем строку из файла
                  ReadLn(FileToRead, ReadString);
     
                  // Добавляем строку в список
                  OutputFile.Add(ReadString);
                  Inc(CurrentStringIndex);
                  Inc(LineIndex);
                end
              else  // Прочитали нужное количество строк - сохраним их!
                begin
                  LineIndex := 0;
     
                  CreateDir('f' + inttostr(Index));
                  OutputFile.SaveToFile('f' + inttostr(Index)+'\' + OutputFileName + IntToStr(Index)+ FileExt);
                  OutputFile.Clear;
     
                  // Увеличиваем индекс файла
                  Inc(Index);
                end;
              end;
     
             // Сохраняем остаток
          TempList := TStringList.Create;
          try
            TempList.AddStrings(OutputFile);
            TempList.SaveToFile('f' +IntToStr(Index) + FileExt);
          finally
            TempList.Free;
          end;
     
        finally
          OutputFile.Free;
        end;
      finally
        // Закрываем файл
        CloseFile(FileToRead);
      end;
    end;

Автор: Славян 11.11.18, 10:50
Так всё ж написано же! Ваяйте нечто вида:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
          LineIndex := 0;
     
          // Работаем со строками в файле, открытом для чтения
          while not EoF(FileToRead) do
          begin
              if LineIndex mod Amount = 0 then
            begin
                CloseFile(FileToWrite); // Закрываем предыдущий файл
                ReadString := OutputFileName + IntToStr(Index) + FileExt // выходной (тут бы ведущие нули)
                AssignFile(FileToWrite, ReadString); // как-то для записи. Не помню как в Паскале
            end;
            ReadLn(FileToRead, ReadString); // Читаем строку из файла
            WriteLn(FileToWrite, ReadString); // Пишем строку в файл
        Inc(LineIndex); // ещё одну строку обработали!
        end;
        ...
        CloseFile(FileToWrite); // Закрываем хвост

Автор: Kirilis2018 11.11.18, 11:00
Славян, Туплю немного я еще, ну попробую разобраться. И на этом спасибо.

Автор: Славян 11.11.18, 11:27
В итоге так заработало:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
        FileName := 'D:\1\22';
        Amount := 4;
        Index := 0;
        LineIndex := 0;
     
        // Работаем со строками в файле, открытом для чтения
        // Открываем файл для чтения
        AssignFile(FileToRead, FileName);
        Reset( FileToRead );
        OutputFileName := 'd:\1\File';
        FileExt := '.txt';
        ReadString := OutputFileName + IntToStr(Index) + FileExt; // выходной (тут бы ведущие нули)
        AssignFile( FileToWrite, ReadString); // как-то для записи. Не помню как в Паскале
        ReWrite( FileToWrite );
        while not EoF(FileToRead) do
        begin
            if (LineIndex mod Amount) = 0 then
            begin
            CloseFile(FileToWrite); // Закрываем предыдущий файл
            ReadString := OutputFileName + IntToStr(Index) + FileExt; // выходной (тут бы ведущие нули)
            AssignFile(FileToWrite, ReadString); // как-то для записи. Не помню как в Паскале
            ReWrite( FileToWrite ); // вот как в Паскале
            Inc(Index) // ещё один файл начали!
            end;
            ReadLn(FileToRead, ReadString); // Читаем строку из файла
            WriteLn(FileToWrite, ReadString); // Пишем строку в файл
            Inc(LineIndex) // ещё одну строку обработали!
        end;
    //    ...
        CloseFile(FileToRead); // Закрываем
        CloseFile(FileToWrite); // Закрываем хвост
Имена входных/выходных файлов поправьте на свои.

Автор: Kirilis2018 11.11.18, 11:40
Славян, 1000 благодарностей и миллионы плюсов - Вам!. Все заработало как нужно!. Я 4 часа долбил код, по своей неопытности. А у Вас мгновенно все получилось. Низкий поклон Вам! :good:

Автор: Славян 11.11.18, 12:23
Эх, хорошо, если бы вы проверили 2 способа: построчный и через списки строк, да сказали нам отличия по времени. А то у меня вроде нет таких могучих текстовых файлов. Хотя... есть один XML на 15 гиг, но... как-то неохота возиться, если честно. :'(

Автор: Kirilis2018 11.11.18, 14:36
Славян, Проверил, на файле в 8 гигабайт. Построчно, у меня, обрабатывает за 30 минут 41 секунду , и через списки строк за 24 минуты 25 секунд. Но построчно тянет большие куски а вот в варианте по списку, крошиться оперативная память, даже на 500 мегабайтах. Потому, преимущественно - использовать вариант построчно, как для меня. У кого, правда, оперативы туча, то можно и по списку - но по скорости, этот вариант, особо не выигрывает. Это конечно мое мнение, я не сильно опытный в программировании, можно сказать начинающий.

Автор: Славян 11.11.18, 16:34
Цитата Kirilis2018 @
построчно тянет большие куски а вот в варианте по списку, крошиться оперативная память, даже на 500 мегабайтах.
Ясно. Спасибо!! Надо будет самому́ что-то в этом варианте покопать - интересно стало! :thanks:

Автор: Kirilis2018 11.11.18, 16:40
Славян, Да это Вам спасибо. Я просто разными документами занимаюсь. И мне вот программы по тексту нужны. Но то что продают просто жесть... Потому пытаюсь сам под свои нужды сделать. Еще раз спасибо!

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)