【问题标题】:JavaFX application is very slowJavaFX 应用程序非常慢
【发布时间】:2020-04-09 20:51:49
【问题描述】:

我们构建了一个信息检索引擎作为我们课程项目的一部分。我们被要求使用 JavaFX 运行我们的程序。

问题在于该项目非常繁忙,其中包括:解析文档(460,000 个文档和多达 300 万个单词)、将术语添加到字典以及更多需要时间的功能。

项目在没有 GUI 的情况下运行所需的时间约为 19 分钟;它包括合并 post 文件并将字典从磁盘加载到 RAM。 问题是当我们添加 GUI 时,时间乘以接近 56 分钟。

我们认为我们在构建的 GUI 中做错了什么:

控制器

package sample;

import javafx.event.ActionEvent;
import javafx.scene.control.Alert;
import javafx.scene.control.TableView;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import main.Indexer;
import main.ReadFile;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class Controller {
    public javafx.scene.control.CheckBox checkBox_Stemming;
    public javafx.scene.control.Button btn_start;
    public javafx.scene.control.Button btn_browse_Corpus;
    public javafx.scene.control.Button btn_browse_postfile;
    public javafx.scene.control.Button btn_reset;
    public javafx.scene.control.TextField tf_postfilePath;
    public javafx.scene.control.TextField tf_corpusPath;
    public javafx.scene.control.Button btn_dictionary;
    public TableView table_dic;
    public javafx.scene.control.TableColumn tc_term;
    public javafx.scene.control.TableColumn  tc_tf;
    private Stage mainStage;
    private Indexer indexer;
    private ReadFile rf;

    public void initialize(Stage mainStage) {
        this.mainStage = mainStage;
        mainStage.setMinHeight(600);
        mainStage.setMinWidth(800);
    }

    public String openFile(ActionEvent event) {
        DirectoryChooser chooser = new DirectoryChooser();
        File defaultDirectory = new File("C:\\");
        chooser.setInitialDirectory(defaultDirectory);
        File selectedDirectory = chooser.showDialog(new Stage());
        return selectedDirectory.getPath();
    }

    public void setStopWord(ActionEvent event){
        tf_postfilePath.textProperty().setValue(openFile(event));
    }

    public void setCorpusPath(ActionEvent event){
        tf_corpusPath.textProperty().setValue(openFile(event));
    }

    public void startIndex(ActionEvent event) {
        String corpusPath = tf_corpusPath.textProperty().getValue();
        String postfilePath = tf_postfilePath.textProperty().getValue();

        if(corpusPath.length() > 0 && postfilePath.length() > 0 ){
            long startTime = System.currentTimeMillis();
            indexer=new Indexer();
            rf= new ReadFile();

            try { 


   indexer.Start(rf,corpusPath,checkBox_Stemming.isSelected(),postfilePath);
            } catch (IOException e) {
                e.printStackTrace();
            }

            long stopTime = System.currentTimeMillis();
            long elapsedTime = stopTime - startTime;
            long minutes = TimeUnit.MILLISECONDS.toMinutes(elapsedTime);
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("Information");
            alert.setHeaderText("Look, an Information Dialog");
            alert.setContentText("Number of documents:"+ main.Indexer.n+"\n"
                    +"Number of uniq terms:"+ "\n"+"Running time:"+minutes+ "minutes\n");

            alert.showAndWait();
        } else if (postfilePath.length() > 0) {
            indexer = new Indexer();
            try {
                indexer.createFinalDictionary(postfilePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            Alert alert = new Alert(Alert.AlertType.ERROR);
            alert.setTitle("Problem");
            alert.setHeaderText("Look, an Information Dialog");
            alert.setContentText("The path are empty or both not. please insert only one path postingfile or corpus");

            alert.showAndWait();
        }
    }

    public void resetIndexer(ActionEvent event) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle("Information");
        alert.setHeaderText("Look, an Information Dialog");
        alert.setContentText("The dictionary and the postingfile deleted");
        alert.showAndWait();
        indexer = null;
        rf = null;
        tf_postfilePath.textProperty().setValue("");
        tf_corpusPath.textProperty().setValue("");
    }

    public void showDictionary(ActionEvent event) {
        System.out.println("hello");

        /**if(indexer != null) {
            HashMap<String, String> dic = indexer.getDic();
            List<String> sortedKeys = new ArrayList(dic.keySet());
            Collections.sort(sortedKeys);
            for (String k:sortedKeys) {
            table_dic.getColumns().addAll(k);
        } else {
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("Information");
            alert.setHeaderText("Look, an Information Dialog");
            alert.setContentText("The dictionary is empty");
            alert.showAndWait();
        }*/
    }
}

FXML 文件

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.Text?>

<AnchorPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="800.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/11.0.1">
    <Button fx:id="btn_reset" layoutX="33.0" layoutY="70.0" mnemonicParsing="false" text="Reset" onAction='#resetIndexer' />
    <Button fx:id="btn_browse_Corpus" layoutX="129.0" layoutY="112.0" mnemonicParsing="false" onAction="#setCorpusPath" text="Browse" />
    <Button fx:id="btn_browse_postfile" layoutX="129.0" layoutY="149.0" mnemonicParsing="false" onAction="#setStopWord" text="Browse" />

    <TableView fx:id="table_dic" layoutX="451.0" layoutY="70.0" prefHeight="439.0" prefWidth="427.0">
        <columns>
            <TableColumn fx:id="tc_term" prefWidth="211.20001220703125" text="Term " />
            <TableColumn fx:id="tc_tf" minWidth="2.4000244140625" prefWidth="215.20001220703125" text="tf" />
        </columns>
    </TableView>

    <TextField fx:id="tf_postfilePath" layoutX="204.0" layoutY="148.0" prefHeight="26.0" prefWidth="218.0" />
    <TextField fx:id="tf_corpusPath" layoutX="205.0" layoutY="111.0" prefHeight="26.0" prefWidth="217.0" />
    <CheckBox fx:id="checkBox_Stemming" layoutX="125.0" layoutY="74.0" mnemonicParsing="false" text="Stemming" />
    <Button fx:id="btn_dictionary" layoutX="39.0" layoutY="233.0" mnemonicParsing="false" text="Dictionary" onAction="#showDictionary" />
    <Button fx:id="btn_start" layoutX="35.0" layoutY="187.0" mnemonicParsing="false" text="Start" onAction='#startIndex' />
    <Text layoutX="46.0" layoutY="129.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Corpus" />
    <Text layoutX="33.0" layoutY="163.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Posting files" wrappingWidth="75.0" />
</AnchorPane>

主类

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("IR2020");
        primaryStage.setScene(new Scene(root, 300, 275));
        Controller controller=new Controller();
        controller.initialize(primaryStage);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

感谢您的帮助,谢谢。

【问题讨论】:

  • 可能与性能问题无关,但您对 fxml 的使用是完全错误的(控制器和加载的页面之间没有关系) - 请务必了解通过 fxml 注入 ui 的基础知识: )
  • 绝对不相关:请学习 java 命名约定并遵守它们。
  • 问题可能是,您在 GUI 线程中启动索引(进程繁重的任务),在方法 startIndex 的事件范围内,您应该启动一个不会中断 GUI 线程的线程。完成此任务后,通过Platform.runLater(()-&gt;{}) 检索信息
  • 除了您在 Luxusproblem 提到的 JavaFX 应用程序线程上运行所有内容之外:JavaFX 当然会占用一些资源(线程、内存)。根据这些资源的紧张程度,这可能或多或少是个问题......

标签: user-interface javafx information-retrieval


【解决方案1】:

我关注@Slaw 和@LuxusProblem 的答案;您应该创建一个服务,其唯一作用是执行指令indexer.Start(rf,corpusPath,checkBox_Stemming.isSelected(),postfilePath);。然后在你的控制器中,你必须启动这个服务然后监听这个服务的状态;如果它切换到 SUCCEEDED,那么您可以显示您的消息框。

我已经为您的代码实现了一个基本服务(可能会有一些小错误,因为我不知道IndexerReadFile 类是如何制作的):

package sample;

import java.util.concurrent.TimeUnit;

import javafx.concurrent.Service;
import javafx.concurrent.Task;

public class IndexService extends Service<Boolean> {
    private String corpusPath;
    private String postFilePath;
    private ReadFile readFile;
    private boolean stemming;

    public IndexService(String cPath, String pfPath, ReadFile rf, boolean stem) {
        corpusPath = cPath;
        postFilePath = pfPath;
        readFile = rf;
        stemming = stem;
    }

    @Override
    protected Task<Boolean> createTask() {
        return new Task<Boolean>() {
            @Override
            protected Boolean call() throws Exception {
                try { 
                    indexer.Start(readFile, corpusPath, stem, postfilePath);
                    return true;
                } catch (IOException e) {
                    e.printStackTrace();
                    return false;
                }
            }
        };
    }
}

在您的控制器中,startIndex 方法将如下所示:

@FXML
public void startIndex(ActionEvent event) throws InterruptedException {
    String corpusPath = tf_corpusPath.textProperty().getValue();
    String postfilePath = tf_postfilePath.textProperty().getValue();

    if (corpusPath.length() > 0 && postfilePath.length() > 0) {
        long startTime = System.currentTimeMillis();
        IndexService service = new IndexService(corpusPath, postfilePath, rf, checkBox_Stemming.isSelected());
        service.start();

        service.stateProperty().addListener((bean_p, old_p, new_p) -> {
            switch (new_p) {
                case SUCCEEDED:
                    long stopTime = System.currentTimeMillis();
                    long elapsedTime = stopTime - startTime;
                    long minutes = TimeUnit.MILLISECONDS.toMinutes(elapsedTime);

                    Alert alert = new Alert(Alert.AlertType.INFORMATION);
                    alert.setTitle("Information");
                    alert.setHeaderText("Look, an Information Dialog");
                    alert.setContentText("Number of documents:" + "main.Indexer.n" + "\n" + "Number of uniq terms:" + "\n" + "Running time:" + minutes + "minutes\n");

                    alert.showAndWait();
                    break;
                default:
                    break;
            }
        });
    } else if (postfilePath.length() > 0) {
        indexer = new Indexer();
        try {
            indexer.createFinalDictionary(postfilePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    } else {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle("Problem");
        alert.setHeaderText("Look, an Information Dialog");
        alert.setContentText("The path are empty or both not. please insert only one path postingfile or corpus");

        alert.showAndWait();
    }
}

更新

如果您想更进一步,您甚至可以管理抛出到您的服务中的异常。抛出错误时,您的 IndexService 会切换到 FAILED 以便您可以显示它。

IndexService 类上,只需删除 try-catch

    @Override
    protected Task<Boolean> createTask() {
        return new Task<Boolean>() {
            @Override
            protected Boolean call() throws Exception {
                indexer.Start(readFile, corpusPath, stem, postfilePath);
                return true;
            }
        };
    }

在您的控制器中,将State.FAILED 的案例添加到您的服务状态侦听器中:

case FAILED:
    Alert alert = new Alert(Alert.AlertType.ERROR);
    alert.setTitle("Error");
    alert.setHeaderText("Look, an Information Dialog");
    alert.setContentText(service.getException().getMessage());

    alert.showAndWait();
    break;

希望对你有帮助:)

【讨论】:

  • 就个人而言,我不会在Task#call() 方法中捕获IOException,而是让异常传播。这样Service 进入FAILED 状态,异常存储在exception 属性中。
  • 是的好主意,我今晚会更新我的答案,以便它可以管理 FAILED 服务状态。
猜你喜欢
  • 2017-12-12
  • 1970-01-01
  • 2012-10-12
  • 2017-04-27
  • 1970-01-01
  • 2014-09-03
  • 1970-01-01
  • 1970-01-01
  • 2012-02-11
相关资源
最近更新 更多