При записи огромного количества данных часть его теряется / Когда все данные присутствуют, процесс записи очень медленный

У меня проблема с буферизованным писателем при записи большого количества строк в файл.

Ситуация: мне нужно прочитать большой текстовый файл (›100 тыс. Строк) и внести некоторые изменения в каждую строку (удалить пробелы, проверить наличие дополнительных команд и т. Д.) И записать измененное содержимое в новый файл.

Я пробовал две возможности записи в файл и получил только один из двух следующих результатов:

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

Подходы и результаты:

  1. Ужасно медленно, но полно
// read file content and put it in List<String> fileContent
for (String line : fileContent)
{
  try(BufferedWriter writer = new BufferedWriter(new OutputStreamwriter(new FileOutputStream(filename, true))))
    {
      writer.write(modifyFileContent(fileContent));
    }
}

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

  1. Быстрая, но неполная запись
// read file content and put it in List<String> fileContent
// This is placed in a try/catch block, I'm omitting it here for brevity
BufferedWriter writer = new BufferedWriter(new OutputStreamwriter(new FileOutputStream(filename, true);
for (String line : fileContent)
{
  writer.write(modifyFileContent(fileContent));
}
writer.close();

Это работает быстрее, но я получаю следующее содержимое в файле результатов (для этой цели отладки я использую номер строки из исходного файла):

...
Very long line with interesting content // line nb 567
Very long line with interesting content // line nb 568
Very long line with interesting content // line nb 569
Very long line wi
Very long line with interesting content // line nb 834
Very long line with interesting content // line nb 835
Very long line with interesting content // line nb 836
...

При выводе этой строки на консоль я не вижу пробелов в нумерации строк! Похоже, где-то проблема с буферизацией ...

Другие подходы: я также пробовал NIO-версию newBufferedWriter, в которой также пропущено несколько строк.

Вопрос: Что мне здесь не хватает? Есть ли способ получить здесь хорошую производительность записи с правильностью? Входные файлы обычно занимают площадь в несколько 100 МБ и миллионы строк ... Любые подсказки приветствуются :)

[редактировать]

Благодаря сэру Лопесу я нашел рабочее решение. Я никогда раньше не сталкивался с RandomAccessFile ...

Теперь, имея эту информацию, я думаю, я столкнулся с состоянием гонки или чем-то еще, связанным с потоком ... Поскольку я начал работать с потоками совсем недавно, я думаю, этого можно было ожидать ...

Чтобы дать правильное представление, я сделал минимальный пример, который показывает контекст, в котором изначально возникла моя проблема. Любые отзывы приветствуются :):

package minex;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.OutputStreamWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.BASELINE;
import static javax.swing.GroupLayout.Alignment.LEADING;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.WindowConstants;

/**
 * Read a file line by line, modify its content and write it to another file.
 * @author demo
 */
public class gui extends JFrame {

  /**
   * Back ground task, so the gui isn't blocked and the progress bar can be updated.
   */
  class fileConversionWorker extends SwingWorker<Integer, Double>
  {
    private final File file;
    
    public fileConversionWorker(File file)
    {
      this.file = file;
    }
 
    /**
     * Count the lines in the provided file. Needed to set the boundary
     * settings for the progress bar.
     * @param aFile File to read.
     * @return Number of lines present in aFile
     * @throws IOException 
     * @see quick and dirty taken from https://stackoverflow.com/a/1277955
     */
    private int countLines(File aFile) throws IOException {
    LineNumberReader reader = null;
    try {
        reader = new LineNumberReader(new FileReader(aFile));
        while ((reader.readLine()) != null);
        return reader.getLineNumber();
    } catch (Exception ex) {
        return -1;
    } finally { 
        if(reader != null) 
            reader.close();
    }
}
    
    /**
     * Reads a file line by line, modify the line
     * content and write it back to a different file immediately.
     * @return 
     */
    @Override
    public Integer doInBackground()
    {
      int totalLines = 0;
      try {
        // Indicate, that something is happening
        barProgress.setIndeterminate(true);
        totalLines = countLines(file);
        barProgress.setIndeterminate(false);
      } catch (IOException ex) {
        Logger.getLogger(gui.class.getName()).log(Level.SEVERE, null, ex);
      }
      
      // only proceed, when we at least have 1 line to manipulate.
      if (totalLines > 0)
      {
        BufferedReader br = null;
        BufferedWriter writer = null;
        try {
          barProgress.setMaximum(totalLines);
          br = new BufferedReader(new FileReader(file));
          String filename =  file.getAbsolutePath() + ".mod";
          long lineNb = 0;
          
          writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename, true)));
          
          String line;
          // Read original file, modify line and immediately write to new file
          while ((line = br.readLine()) != null)
          {
            writer.write(line + " // " + lineNb);
            writer.newLine();

            publish((double)(lineNb / totalLines));
            lineNb++;
          }
        } catch (FileNotFoundException ex) {
          Logger.getLogger(gui.class.getName()).log(Level.SEVERE, null, ex);
        } catch ( IOException ex) {
          Logger.getLogger(gui.class.getName()).log(Level.SEVERE, null, ex);
        }
        finally {
          // Tidying up
          try {
            if (br != null)
              br.close();
            if (writer != null)
              writer.close();
          } catch (IOException ex) {
            Logger.getLogger(gui.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      }
      return 0;
    }
    
    /**
     * Prevent any interaction, which could interrupt the worker
     */
    @Override 
    public void done()
    {
      butLoadFile.setEnabled(true); 
    }
    
    /**
     * Update progress the progress bar,
     * @param aDoubles
     */
    @Override
    protected void process(java.util.List<Double> aDoubles) {    
      int amount = barProgress.getMaximum() - barProgress.getMinimum();
      barProgress.setValue( ( int ) (barProgress.getMinimum() + ( amount * aDoubles.get( aDoubles.size() - 1 ))) );
    }
  }
  
  /**
   * Start the gui.
   */
  public static void main()
  {
    EventQueue.invokeLater(() -> {
      new gui().setVisible(true);
    });
  }
  
  /**
   * Initialize all things needed.
   */
  public gui()
  {
    initComponents();
  }
  
  /**
   * Load a file and immediately begin processing it.
   * @param evt 
   */
  private void butLoadFileActionListener(ActionEvent evt)
  {
    javax.swing.JFileChooser fc = new javax.swing.JFileChooser("/home/demo/fileFolder");
    int returnVal = fc.showOpenDialog(gui.this);
    
    if (returnVal == JFileChooser.APPROVE_OPTION) {
      File file = fc.getSelectedFile();
      butLoadFile.setEnabled(false);
      fileConversionWorker worker = new fileConversionWorker(file);
      worker.execute();
    }
  }
  
  /**
   * Paint the canvas.
   */
  private void initComponents()
  {
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    setResizable(false);
    setTitle("Min Example");
    
    butLoadFile = new JButton("Load file");
    butLoadFile.addActionListener((ActionEvent evt) -> {
      butLoadFileActionListener(evt);
    });
    
    barProgress = new JProgressBar();
    barProgress.setStringPainted(true);
    barProgress.setMinimum(0);
    
    javax.swing.GroupLayout layout = new GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    
    layout.setHorizontalGroup(
    layout.createParallelGroup(LEADING)
            .addComponent(butLoadFile, GroupLayout.PREFERRED_SIZE, 200, GroupLayout.PREFERRED_SIZE)
            .addComponent(barProgress, GroupLayout.PREFERRED_SIZE, 200, GroupLayout.PREFERRED_SIZE)
    );

    layout.setVerticalGroup(
    layout.createParallelGroup(BASELINE)
            .addGroup(layout.createSequentialGroup()
            .addComponent(butLoadFile, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)
            .addComponent(barProgress, GroupLayout.PREFERRED_SIZE, 20, GroupLayout.PREFERRED_SIZE)            
            )
    );
    
    pack();
  }
  
  private JButton butLoadFile;        /** Button to load a file. */
  private JProgressBar barProgress;   /** Progress bar to visualize progress. */  
}

[/редактировать]


person gelfuegelsalat    schedule 22.09.2020    source источник
comment
Я сильно подозреваю, что проблема в коде, который вы пропустили. Опубликуйте полный пример кода, воссоздающего вашу проблему. Вероятность того, что вы обнаружите ошибку в устаревших служебных классах Java, таких как FileOutputStream или BufferedWriter, практически равна нулю. Проблема почти наверняка в вашем коде, который вы не публиковали.   -  person Andrew Henle    schedule 22.09.2020
comment
Спасибо за отзыв, я отредактировал сообщение, чтобы включить исходную проблему. Благодаря ссылкам, предоставленным сэром Лопесом, я нашел рабочее решение для своих нужд ... И я также оцениваю вероятность обнаружения ошибок как крайне низкую, поскольку в большинстве случаев ошибка возникает между стулом и клавиатурой;) Иногда, это всего лишь подсказка, которая открывает новые перспективы ...   -  person gelfuegelsalat    schedule 24.09.2020


Ответы (1)


Может это поможет тебе

Самый быстрый способ записывать огромные данные в текстовый файл Java

https://www.quora.com/How-do-to-read-and-write-large-size-file-in-Java-efficiently

person Sir Lopez    schedule 22.09.2020