Reputation: 734
I'm using JavaFX Task
to read a text file and parse it into a HashMap<String, String>
(using aMapper<String, String
object). My code is fully functioning, but I wish to show read progress to user as the input file contains over 9000 rows of data.
@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);
}
In the existing state of this code, I can't think of a way to determine how much of the input file I've read. The method FileUtils.readLine(sourceMappingFilePath, i)
above is a custom implementation:
/**
* 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;
}
Any suggestions? The simpler to implement, the better - thanks for your time.
Upvotes: 0
Views: 1415
Reputation: 209674
A simple approach is to use an underlying input stream that tracks how many bytes you have read. The Files.size()
method will give you the total number of bytes in a file, so this gives you enough information to compute the overall progress. You can do something like
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 ;
}
}
Note that if you wrap this in a BufferedReader
, getBytesRead()
will return the number of bytes read from the underlying stream, including those still stored in the buffer. This is probably good enough for displaying a progress bar (since it's very fast to read from the buffer), but isn't going to be technically 100% accurate.
Here's a SSCCE. On my system, you need to load a file with ~100,000 lines to see the progress bar. You can create one if you don't have one available (the SSCCE allows you to create a file first).
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);
}
}
Upvotes: 2