如何在读取文件时确定进度

时间:2017-02-01 13:14:26

标签: java file javafx task

我正在使用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;
}

有什么建议吗?实施起来越简单越好 - 感谢您的时间。

1 个答案:

答案 0 :(得分: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);
    }
}