我正在逐行解析大文件,其中文件大小可以从几千行到几百万行。当文件很小(例如,几万行)时,所有组件(下图中用红色箭头指示的那些组件)都会逐行更新,从而真实地指示过程的位置。当文件很大时,更新会有很大的延迟,在指标反映进度之前可能需要30秒!我可以手动调整大小以刷新显示。
逐行传输文件时调用的方法是updateProgress()
。在那里,progressPrevVal
从0变为1(每个增量为1.0 / 2,675,150
),如果我们在图像中使用示例,则lineNum
从1变为2,675,150。
以下是整个代码:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
public class DxProgressIndicator extends Application {
private static ProgressBar pb;
private static ProgressIndicator pi;
private static TextField tfNumbers;
private static Label lblLineNumVal;
private static Label lblAllLines;
private static double progressIncrement;
private static double progressPrevVal;
private static int lineNum;
private static Label lblFile;
@Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Dexter Parser");
lblFile = new Label();
final HBox hbFile = new HBox();
hbFile.setAlignment(Pos.CENTER_LEFT);
Label lblCurrentFile = new Label("Current file: ");
lblCurrentFile.setMinWidth(90);
lblCurrentFile.setFont(Font.font(null, FontWeight.BOLD, 13));
hbFile.getChildren().addAll(lblCurrentFile, lblFile);
pb = new ProgressBar(0);
pb.setMinWidth(450);
pi = new ProgressIndicator(0);
final HBox hbPis = new HBox(10);
hbPis.setAlignment(Pos.CENTER_LEFT);
hbPis.getChildren().addAll(pb, pi);
lblLineNumVal = new Label();
lblLineNumVal.setMaxWidth(200);
Label slash = new Label(" / ");
lblAllLines = new Label();
final HBox hbLines = new HBox();
hbLines.setAlignment(Pos.CENTER_LEFT);
Label lblLineNum = new Label(" Currently parsing Line # : ");
lblLineNum.setFont(Font.font(null, FontWeight.BOLD, 12));
hbLines.getChildren().addAll(lblLineNum, lblLineNumVal, slash, lblAllLines);
tfNumbers = new TextField();
tfNumbers.setEditable(false);
tfNumbers.setMaxWidth(100);
final HBox hbTrxValues = new HBox();
hbTrxValues.setAlignment(Pos.CENTER_LEFT);
Label lblNumbers = new Label("(transaction, step, record) = ");
lblNumbers.setFont(Font.font(null, FontWeight.BOLD, 12));
hbTrxValues.getChildren().addAll(lblNumbers, tfNumbers);
final VBox vb = new VBox(10);
vb.getChildren().addAll(hbFile, hbPis, hbLines, hbTrxValues);
vb.setPadding(new Insets(10));
scene.setRoot(vb);
stage.show();
}
public static void updateProgress() {
Platform.runLater(() -> {
progressPrevVal += progressIncrement;
pb.setProgress(progressPrevVal);
pi.setProgress(progressPrevVal);
lblLineNumVal.setText(Integer.toString(lineNum++));
});
}
public static void setFileMetadata(String str) {
Platform.runLater(() -> {
lblAllLines.setText(str);
progressIncrement = 1 / Double.valueOf(str);
progressPrevVal = 0d;
lineNum = 1;
});
}
public static void main(String[] args) {
launch(args);
}
}
从客户端的主要方法,一旦我在自己的线程中启动GUI,就像这样:
Runnable task = () -> {
DxProgressIndicator.main(null);
};
Thread thread = new Thread(task);
thread.start();
然后我可以浏览文件,逐行流式传输:
try (Stream<String> stream = Files.lines(file)) {
stream.forEach(line -> {
DxProgressIndicator.updateProgress();
// a bunch of stuff to do with the line
});
} catch (IOException e) {
}
那么,我错过了什么?我如何强制刷新以便组件在每次迭代时平滑更新,而不是在处理非常大的文件时每隔几分钟更新一次?
谢谢。
答案 0 :(得分:1)
使用Task
,并调用updateProgress()
以便以限制Platform.runLater()
的调用的方式更新进度,以免过多地使FX应用程序线程泛滥要执行的更新。
所以你可以这样做:
public class ParseTask extends Task<Void> {
private final Path file ;
private long totalLines ;
public ParseTask(...) {
file = ... ;
totalLines = ... ;
}
public Void call() throws IOException {
// better to use AtomicLong here in case you parallelize the parsing
// at any point...
AtomicLong linesRead = new AtomicLong() ;
try (Stream<String> stream = Files.lines(file)) {
stream.forEach(line -> {
updateProgress(linesRead.incrementAndGet(), totalLines);
// do stuff with line...
});
}
return null ;
}
public long getTotalLines() {
return totalLines ;
}
}
然后
public class DxProgressIndicator {
private final VBox vb ;
private ProgressBar pb;
private ProgressIndicator pi;
private TextField tfNumbers;
private Label lblLineNumVal;
private Label lblAllLines;
private double progressIncrement;
private double progressPrevVal;
private int lineNum;
private Label lblFile;
private final DoubleProperty progress = new SimpleDoubleProperty();
public DoubleProperty progressProperty() {
return progress ;
}
public final double getProgress() {
return progressProperty().get();
}
public final void setProgress(double progress) {
progressProperty().set(progress);
}
public DxProgressIndicator() {
lblFile = new Label();
final HBox hbFile = new HBox();
hbFile.setAlignment(Pos.CENTER_LEFT);
Label lblCurrentFile = new Label("Current file: ");
lblCurrentFile.setMinWidth(90);
lblCurrentFile.setFont(Font.font(null, FontWeight.BOLD, 13));
hbFile.getChildren().addAll(lblCurrentFile, lblFile);
pb = new ProgressBar(0);
pb.setMinWidth(450);
pi = new ProgressIndicator(0);
pb.progressProperty().bind(progress);
pi.progressProperty().bind(progress);
final HBox hbPis = new HBox(10);
hbPis.setAlignment(Pos.CENTER_LEFT);
hbPis.getChildren().addAll(pb, pi);
lblLineNumVal = new Label();
lblLineNumVal.setMaxWidth(200);
Label slash = new Label(" / ");
lblAllLines = new Label();
final HBox hbLines = new HBox();
hbLines.setAlignment(Pos.CENTER_LEFT);
Label lblLineNum = new Label(" Currently parsing Line # : ");
lblLineNum.setFont(Font.font(null, FontWeight.BOLD, 12));
hbLines.getChildren().addAll(lblLineNum, lblLineNumVal, slash, lblAllLines);
tfNumbers = new TextField();
tfNumbers.setEditable(false);
tfNumbers.setMaxWidth(100);
final HBox hbTrxValues = new HBox();
hbTrxValues.setAlignment(Pos.CENTER_LEFT);
Label lblNumbers = new Label("(transaction, step, record) = ");
lblNumbers.setFont(Font.font(null, FontWeight.BOLD, 12));
hbTrxValues.getChildren().addAll(lblNumbers, tfNumbers);
vb = new VBox(10);
vb.getChildren().addAll(hbFile, hbPis, hbLines, hbTrxValues);
vb.setPadding(new Insets(10));
}
public Parent getRoot() {
return vb ;
}
public void setTotalLines(long totalLines) {
lblAllLines.setText(Long.toString(totalLines));
}
}
然后只是
public class MyApp extends Application {
@Override
public void start(Stage primaryStage) {
ParseTask task = new ParseTask(...);
DxProgressIndicator indicator = new DxProgressIndicator();
indicator.setTotalLines(task.getTotalLines());
indicator.progressProperty().bind(task.progressProperty());
Scene scene = new Scene(indicator.getRoot());
primaryStage.setScene(scene);
primaryStage.setTitle("Dexter Parser");
primaryStage.show();
new Thread(task).start();
}
}
显然你可能需要修补这个细节 - 例如你可以updateMessage(linesRead + " / " + totalLines);
并将标签的文本绑定到任务的messageProperty()
- 但它应该给你基本的想法。
答案 1 :(得分:1)
javaFX Task类使用原子引用来合并更新,以便它们不会泛滥事件队列(这会导致您的问题)。您可以轻松地在代码中实现此机制。
首先你需要一个助手类:
private static final class ProgressUpdate {
private final double workDone;
private ProgressUpdate(double p) {
this.workDone = p;
}
}
然后你需要一个存储对这个类的对象的原子引用的字段:
private static final AtomicReference<ProgressUpdate> progressUpdate = new AtomicReference<>();
最后你可以像这样调整你的updateProgress()
:
public static void updateProgress() {
progressPrevVal += progressIncrement;
if(progressUpdate.getAndSet(new ProgressUpdate(progressPrevVal)) == null){
Platform.runLater(() -> {
ProgressUpdate update = progressUpdate.getAndSet(null);
pb.setProgress(update.workDone);
pi.setProgress(update.workDone);
lblLineNumVal.setText(Integer.toString(lineNum++));
});
}
}
这样,如果GUI已经消耗了前一个应用程序线程,那么只有一个新的更新排队等候FX应用程序线程。
但是,您最好只是为了了解javaFX如何在内部处理问题,然后以可以使用内置Task
类javaFX的方式重新设计代码。 ,这将自动解决您当前和可能的其他一些问题。