我正在使用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 :(得分: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);
}
}