2017-02-01 5 views
0

Я использую JavaFX Task для чтения текстового файла и разбора его в HashMap<String, String> (с использованием объекта Mapper<String, String). Мой код полностью функционирует, но я хочу показать прогресс чтения пользователю, поскольку входной файл содержит более 9000 строк данных.Как определить прогресс при чтении файла

@Override 
protected Mapper<String, String> call() throws Exception { 
    // Read and parse contents of source mapping file. 

    /* Format: 
    * Ignore first line of file, then: 
    * 
    * <old_name>  <new_name> 
    * NAME_A    NAME_X    
    * NAME_B    NAME_Y    
    * NAME_C    NAME_Z 
    * 
    * Where <old_name> = K, <new_name> = V in Mapper object. 
    */ 

    int i=beginReadingFileFromLine; // Ignore first line of file 
    String line; 
    List<String> keys = new ArrayList<>(); 
    List<String> values = new ArrayList<>(); 
    updateMessage("Loading data..."); 
    while ((line=FileUtils.readLine(sourceMappingFilePath, i)) != null) { 
     // Parse line into String[] split by delim char. 
     String[] parsedLine = line.split(fileDelimChar); 
     keys.add(parsedLine[0]); 
     values.add(parsedLine[1]); 
     i++; 
    } 
    updateProgress(i, i); 
    updateMessage("Data loaded!"); 
    return new Mapper<String, String>(keys, values); 
} 

В существующем состоянии этого кода, я не могу придумать способ, чтобы определить, сколько из входного файла я прочитал. Метод FileUtils.readLine(sourceMappingFilePath, i) выше обычай реализации:

/** 
* Reads a single line, according to supplied line number from specified file. 
* @param filepath 
* @param lineNumber zero based index. 
* @return Line from file without linefeed character. NULL if at EoF. 
* @throws IOException 
*/ 
public static String readLine(String filepath, int lineNumber) throws IOException { 
    FileReader fReader = new FileReader(filepath); 
    LineNumberReader lineNumberReader = new LineNumberReader(fReader); 

    String desiredLine = null; 

    int i=0; 
    String line; 
    while((line=lineNumberReader.readLine()) != null) { 

     if(lineNumber==i) { 
      desiredLine=line; 
     } 
     i++; 
    } 
    lineNumberReader.close(); 
    return desiredLine; 
} 

Любые предложения? Чем проще реализовать, тем лучше - спасибо за ваше время.

+0

JavaDoc для справки: [Task ] (https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html) –

+3

Я думаю, что 'байт read'/'total bytes' будет реальным прогрессом. –

+3

Ваш полезный метод очень неэффективен (это мягко сказано). Чтобы прочитать каждую строку, вы открываете файл с нуля и просматриваете файл до последней строки, а затем получаете следующую строку. Таким образом, чтобы читать 9000 строк, в общей сложности вы читаете 1 + 2 + 3 + ... + 9000 = 40,504,500 строк, открывая и закрывая файл 9000 раз. Возможно, если вы просто прочитаете файл, вам даже не понадобится индикатор прогресса (или он может просто использовать неопределенный вариант). –

ответ

2

Простым подходом является использование базового потока ввода, который отслеживает, сколько байтов вы прочитали. Метод Files.size() даст вам общее количество байтов в файле, так что это даст вам достаточно информации для вычисления общего прогресса. Вы можете сделать что-то вроде

public class CountingInputStream extends InputStream implements AutoCloseable { 

    private long bytesRead = 0 ; 

    private final InputStream stream ; 

    public CountingInputStream(InputStream stream) { 
     this.stream = stream ; 
    } 

    @Override 
    public int read() throws IOException { 
     int result = stream.read() ; 
     if (result != -1) { 
      bytesRead++; 
     } 
     return result ; 
    } 

    @Override 
    public void close() throws IOException { 
     super.close(); 
     stream.close(); 
    } 

    public long getBytesRead() { 
     return bytesRead ; 
    } 
} 

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

Здесь находится SSCCE. В моей системе вам нужно загрузить файл с ~ 100 000 строк, чтобы увидеть индикатор выполнения. Вы можете создать его, если у вас нет одного доступного (SSCCE позволяет вам сначала создать файл).

import java.io.BufferedReader; 
import java.io.BufferedWriter; 
import java.io.File; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.nio.file.Files; 
import java.nio.file.Path; 
import java.util.Map; 
import java.util.stream.Collectors; 

import javafx.application.Application; 
import javafx.concurrent.Task; 
import javafx.geometry.HPos; 
import javafx.geometry.Insets; 
import javafx.scene.Scene; 
import javafx.scene.control.Alert; 
import javafx.scene.control.Alert.AlertType; 
import javafx.scene.control.Button; 
import javafx.scene.control.ButtonType; 
import javafx.scene.control.Label; 
import javafx.scene.control.ProgressBar; 
import javafx.scene.control.TextField; 
import javafx.scene.layout.GridPane; 
import javafx.stage.FileChooser; 
import javafx.stage.Stage; 

public class ReadFileWithProgress extends Application { 


    public static class CountingInputStream extends InputStream implements AutoCloseable { 

     private long bytesRead = 0 ; 

     private final InputStream stream ; 

     public CountingInputStream(InputStream stream) { 
      this.stream = stream ; 
     } 

     @Override 
     public int read() throws IOException { 
      int result = stream.read() ; 
      if (result != -1) { 
       bytesRead++; 
      } 
      return result ; 
     } 

     @Override 
     public void close() throws IOException { 
      stream.close(); 
     } 

     public long getBytesRead() { 
      return bytesRead ; 
     } 
    } 

    @Override 
    public void start(Stage primaryStage) { 
     GridPane root = new GridPane(); 
     TextField numLinesField = new TextField(); 

     FileChooser chooser = new FileChooser(); 

     Button create = new Button("Create File..."); 
     create.setOnAction(e -> { 
      int numLines = Integer.parseInt(numLinesField.getText()); 
      File file = chooser.showSaveDialog(primaryStage); 
      if (file != null) { 
       try { 
        createFile(file.toPath(), numLines); 
       } catch (Exception exc) { 
        exc.printStackTrace(); 
       } 
      } 
     }); 

     Button loadFile = new Button("Load file"); 
     ProgressBar progress = new ProgressBar(0); 
     loadFile.setOnAction(e -> { 
      File file = chooser.showOpenDialog(primaryStage); 
      if (file != null) { 
       Task<Map<String, String>> task = readFileTask(file.toPath()); 
       progress.progressProperty().bind(task.progressProperty()); 
       task.setOnSucceeded(evt -> new Alert(AlertType.INFORMATION, "File loaded", ButtonType.OK).showAndWait()); 
       task.setOnFailed(evt -> new Alert(AlertType.ERROR, "File could not be loaded", ButtonType.OK).showAndWait()); 
       new Thread(task).start(); 
      } 
     }); 

     root.addRow(0, new Label("Number of lines:"), numLinesField, create); 
     root.add(loadFile, 0, 1, 3, 1); 
     root.add(progress, 0, 2, 3, 1); 

     GridPane.setFillWidth(progress, true); 
     GridPane.setHalignment(progress, HPos.CENTER); 
     GridPane.setFillWidth(loadFile, true); 
     GridPane.setHalignment(loadFile, HPos.CENTER); 

     root.setPadding(new Insets(20)); 
     root.setHgap(5); 
     root.setVgap(10); 
     primaryStage.setScene(new Scene(root)); 
     primaryStage.show(); 
    } 

    private Task<Map<String, String>> readFileTask(Path path) { 

     return new Task<Map<String, String>>() { 

      @Override 
      protected Map<String, String> call() throws IOException { 
       try (
         CountingInputStream input = new CountingInputStream(Files.newInputStream(path)); 
         BufferedReader in = new BufferedReader(new InputStreamReader(input)); 
        ) { 

         long totalBytes = Files.size(path); 

         return in.lines() 
           .peek(line -> updateProgress(input.getBytesRead(), totalBytes)) 
           .map(line -> line.split("\t")) 
           .collect(Collectors.toMap(tokens -> tokens[0], tokens -> tokens[1])); 
        } 

      } 

     }; 

    } 

    private void createFile(Path path, int numEntries) throws IOException { 
     try (BufferedWriter out = Files.newBufferedWriter(path)) { 
      for (int i = 1; i <= numEntries ; i++) { 
       out.write(String.format("key %d\tvalue %d%n", i, i)); 
      } 
     } 
    } 

    public static void main(String[] args) { 
     launch(args); 
    } 
} 
+0

Спасибо за такой хороший SSCCE. Вам действительно не обязательно быть таким тщательным, но я очень ценю такой точный, но краткий ответ. Веселая хорошая работа сэр! –

+0

@orion_IX Я не был уверен, что он будет работать без тестирования;). –

+0

отличительный знак великого кодера! ;) на другой ноте, хорошие люди, как вы делаете ТАК такой достойный опыт, в отличие от возиться и сдаваться в конце! –