【问题标题】:How do I determine progress when reading a file读取文件时如何确定进度
【发布时间】:2017-02-01 13:14:25
【问题描述】:

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

有什么建议吗?实现越简单越好 - 感谢您的宝贵时间。

【问题讨论】:

  • JavaDoc 供您参考:Task<V>
  • 我认为bytes read / total bytes 将是真正的进步
  • 你的实用方法效率很低(说得委婉些)。要读取每一行,您从头开始打开文件,并扫描文件到读取的最后一行,然后获取下一行。所以要读取 9000 行,总共要读取 1+2+3+...+9000=40,504,500 行,打开和关闭文件 9000 次。也许如果你只是阅读文件,你甚至不需要一个进度指示器(或者可以只使用一个不确定的)。
  • @James_D 谢谢你,我很懒,使用了我为另一个项目编写的实用函数来节省时间。我会研究一下 NIO 通道/缓冲区,显然它相当快。
  • 对于 9000 行,我认为普通的缓冲阅读器会执行得很好。

标签: java file javafx task


【解决方案1】:

一种简单的方法是使用底层输入流来跟踪您已读取的字节数。 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);
    }
}

【讨论】:

  • 感谢这么好的SSCCE。你真的不必如此彻底,但我非常感谢如此精确而简洁的回应。先生,干得好!
  • @orion_IX 我不确定它是否能在没有测试的情况下工作;)。
  • 伟大程序员的标志! ;) 另一方面,像你这样的好人让这样的体验变得如此有价值,而不是摸索并最终放弃!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-04
  • 1970-01-01
  • 1970-01-01
  • 2014-06-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多