javafx tableview 主动排序很慢

标签 java performance javafx-2 tableview javafx-8

我正在使用 javafx tableview 并创建了一个包含 100,000 行(三列 1 int 2 float)的表。

我开启了主动排序。要插入新行,我首先使用二进制搜索搜索索引,然后使用 table.getItems.add(index,element); 插入它;

但是随着每 20 毫秒添加新行,GUI 有点无响应。

我添加了 table.setSelectionModel(null); 并固定了我的 GUI,因此它似乎是 GUI 缓慢的罪魁祸首。

但我还需要选择行的能力.....

有人建议在这种情况下该怎么做......

P.S. :(在添加行 table.setSelectionModel(null); 之前,我尝试运行 jprofiler,它显示主要时间消耗在 javafx.scene .control.TableCell$2.onChanged )

编辑:

我的用例

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.Comparator;  
import java.util.List;  
import java.util.Random;  
import javafx.animation.Animation;  
import javafx.animation.KeyFrame;  
import javafx.animation.Timeline;  
import javafx.application.Application;  
import javafx.beans.binding.Bindings;  
import javafx.beans.property.SimpleStringProperty;  
import javafx.beans.property.StringProperty;  
import javafx.collections.FXCollections;  
import javafx.collections.ObservableList;  
import javafx.event.ActionEvent;  
import javafx.event.EventHandler;  
import javafx.geometry.HPos;  
import javafx.scene.Scene;  
import javafx.scene.control.Button;  
import javafx.scene.control.Label;  
import javafx.scene.control.SelectionMode;  
import javafx.scene.control.TableColumn;  
import javafx.scene.control.TableColumn.SortType;  
import javafx.scene.control.TableView;  
import javafx.scene.control.TextField;  
import javafx.scene.control.cell.PropertyValueFactory;  
import javafx.scene.layout.BorderPane;  
import javafx.scene.layout.ColumnConstraints;  
import javafx.scene.layout.GridPane;  
import javafx.stage.Stage;  
import javafx.util.Duration;  
public class TableInsertExample extends Application {  
  int count=0;  
    long s,e,mx=0,mn=1000000000;  
    float avg=0;  
  private static final Random RNG = new Random();  
  private Comparator<Person> tableOrderComparator ;  
  @SuppressWarnings("unchecked")  
@Override  
  public void start(Stage primaryStage) {  
    final BorderPane root = new BorderPane();  
    final TableView<Person> table = new TableView<Person>();  
    table.setItems(createData());  
    final TableColumn<Person, String> firstNameColumn = new TableColumn<Person,String>("First Name");  
    final TableColumn<Person, String> lastNameColumn = new TableColumn<Person,String>("Last Name");  
    firstNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));  
    lastNameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));  
    table.getColumns().addAll(firstNameColumn, lastNameColumn);  

    tableOrderComparator = createTableOrderComparator(table);  

   //this line increase speed but then we can not even click on table as it will give someexception  
    table.setSelectionModel(null);  


    final GridPane addPersonPane = new GridPane();  
    final TextField firstNameTF = new TextField();  
    final TextField lastNameTF = new TextField();  
    final Button addButton = new Button("Add");  
    addPersonPane.addRow(0, new Label("First Name:"), firstNameTF);  
    addPersonPane.addRow(1, new Label("Last Name:"), lastNameTF);  
    addPersonPane.addRow(2, addButton);  
    final ColumnConstraints leftColConstraints = new ColumnConstraints();  
    leftColConstraints.setHalignment(HPos.RIGHT);  
    final ColumnConstraints rightColConstraints = new ColumnConstraints();  
    rightColConstraints.setHalignment(HPos.LEFT);  
    addPersonPane.getColumnConstraints().addAll(leftColConstraints, rightColConstraints);  

    addButton.setOnAction(new EventHandler<ActionEvent>() {  

      @Override  
      public void handle(ActionEvent event) {  
        final Person person = new Person(firstNameTF.getText(), lastNameTF.getText());  
        addPersonToTable(table, person);  
      }  
    });  
     table.getSortOrder().addAll(firstNameColumn);  
    Label countLabel = new Label();  
    countLabel.textProperty().bind(Bindings.format("Table has %s entries", Bindings.size(table.getItems())));  
    root.setTop(countLabel);  
    root.setCenter(table);  
    root.setBottom(addPersonPane);  
    primaryStage.setScene(new Scene(root, 400, 600));  
    primaryStage.show();     

    Timeline addRandomPeopleFrequently = new Timeline(new KeyFrame(Duration.millis(20), new EventHandler<ActionEvent>() {  
      @Override  
      public void handle(ActionEvent event) {  
        Person randomPerson = new Person(randomString(), randomString());  
        count++;  
        addPersonToTable(table, randomPerson);  
      }  
    }));  
    addRandomPeopleFrequently.setCycleCount(Animation.INDEFINITE);  
    addRandomPeopleFrequently.play();  
  }  
  private Comparator<Person> createTableOrderComparator(  
      final TableView<Person> table) {  
    return new Comparator<Person>() {  
      @Override  
      public int compare(Person person1, Person person2) {  
        for (TableColumn<Person, ?> col : table.getSortOrder()) {  
          Comparator colComp = col.getComparator();  
          if (colComp == null) {  
            colComp = TableColumn.DEFAULT_COMPARATOR;  
          }  
          final Object o1 = col.getCellData(person1);  
          final Object o2 = col.getCellData(person2);  
          int c = colComp.compare(o1, o2);  
          if (col.getSortType() == SortType.DESCENDING) {  
            c = -c ;  
          }  
          if (c != 0) {  
            return c;  
          }  
        }  
        return 0 ;  
      }  
    };  
  }  
  public static void main(String[] args) {  
    launch(args);  
  }  
  private ObservableList<Person> createData() {  
    List<Person> list = new ArrayList<Person>();  
    for (int i=0; i<100000; i++) {  
      list.add(new Person(randomString(), randomString()));  
    }  
    return FXCollections.observableList(list);  
  }  
  private String randomString() {  
    StringBuilder sb = new StringBuilder();  
    for (int i=0; i<8; i++) {  
      sb.append((char)(RNG.nextInt(26)+'a'));  
    }  
    return sb.toString();  
  }  
  private void addPersonToTable(final TableView<Person> table,  
       final Person person) {  
     int index ;  
     final ObservableList<TableColumn<Person, ?>> tableSortOrder = table.getSortOrder();  
     if (tableSortOrder.size()==0) {  
       index = table.getItems().size();  
     } else {  
       index = Collections.binarySearch(table.getItems(), person, tableOrderComparator);  
       if (index < 0) {  
         index = -index-1 ;  
       }  
     }  
     s=System.currentTimeMillis();  
     List<Person> leftList = table.getItems().subList(0, index);  
     List<Person> rightList = table.getItems().subList(index, table.getItems().size());  
     List<Person> newList = new ArrayList<Person>(table.getItems().size()+1);  
     newList.addAll(leftList);  
     newList.add(person);  
     newList.addAll(rightList);  
   /*  int selectedIndex = table.getSelectionModel().getSelectedIndex(); 
     if (index < selectedIndex) { 
       selectedIndex++; 
     }  */  
     table.getItems().setAll(newList);  
    // table.getSelectionModel().select(selectedIndex);  
     e= System.currentTimeMillis() - s;  
  avg+=e;  
  if(mx<e)  
  mx=e;  
  if(mn>e)  
  mn=e;  
  if(count==1000)  
  {  
  avg=avg/10000;  
  System.out.format("current System time is %f. Max is %d . Min is %d%n",avg,mx,mn);  
  count=0;  
  avg=0;  
  mx=0;  
  mn=100000000;  
  }  
   }  
  public static class Person {  
    private final StringProperty firstName ;  
    private final StringProperty lastName ;  
    Person(String firstName, String lastName) {  
      this.firstName = new SimpleStringProperty(this, "firstName", firstName);  
      this.lastName = new SimpleStringProperty(this, "lastName", lastName);  
    }  
    public String getFirstName() { return firstName.get(); }  
    public void setFirstName(String firstName) { this.firstName.set(firstName);}  
    public StringProperty firstNameProperty() { return firstName ; }  
    public String getLastName() { return lastName.get(); }  
    public void setLastName(String lastName) { this.lastName.set(lastName); }  
    public StringProperty lastNameProperty() { return lastName ; }     
    @Override public String toString() { return firstName.get() + " " + lastName.get() ; }  

  }  
}  

开头这一行

//this line increase speed but then we can not even click on table as it will give someexception  
    table.setSelectionModel(null);

帮助我增加平均插入速度到0.2 ms(计算平均值的代码包含在代码中)

但它禁用任何选择(由于这个原因,addPersonToTable 中的代码被注释)

我想要选择一行的能力,但这段代码具有一定的速度效率。 (我使用了 Jprofiler,它显示主要时间花在 TableCell.onChanged )

注意:此代码由 James_D 编写,我只是对其进行了一些修改(添加行 table.setSelectionModel(null); 和 addPersonToTable 中的注释行)

最佳答案

我无法重现您的问题。

将新行添加到具有 100,000 行的 TableView 中的排序位置对我来说几乎是即时的。

我使用了对 James 对您之前问题的回答的修改:JavaFx tableview sort is really slow how to improve sort speed as in java swing .

修改在按下添加按钮时执行以下算法:

  1. 如果没有输入新的个人详细信息,则只生成一些新的随机个人详细信息。
  2. 对表项进行二分查找以找到插入索引。
  3. 在适当的索引处插入项目,选择新添加的行。
  4. 滚动表格以显示它。

如果您使用 Java 7,则 TableView 中存在错误 scrollTo阻止表格在所有情况下滚动到正确位置的例程。

使用 Java 8b93、Win7 的输出:

sortsample

TableSortPerformanceTest.java

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Random;

public class TableSortPerformanceTest extends Application {

    public static final int INIT_LIST_SIZE = 100_000;

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new StackPane());
        stage.setTitle("Table View Sample");
        stage.setWidth(550);
        stage.setHeight(550);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        final TableView<Person> table = new TableView<Person>();
        table.setEditable(true);

        TableColumn<Person, String> firstNameCol = new TableColumn<Person, String>("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("firstName"));
        firstNameCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Person,String>, ObservableValue<String>>() {
          @Override
          public ObservableValue<String> call(CellDataFeatures<Person, String> cdf) {
            return cdf.getValue().firstNameProperty();
          }
        });

        TableColumn<Person, String> lastNameCol = new TableColumn<Person, String>("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("lastName"));



        TableColumn<Person, String> emailCol = new TableColumn<Person, String>("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<Person, String>("email"));


        final Random random = new Random();
        for (int i = 0; i < INIT_LIST_SIZE; i++) {
          table.getItems().add(new Person(randomString(random), randomString(random), randomString(random)));
        }
        table.getColumns().addAll(Arrays.asList(firstNameCol, lastNameCol, emailCol));

        long start = new Date().getTime();
        Collections.sort(table.getItems());
        long end   = new Date().getTime();
        System.out.println("Took: " + (end - start));


        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");

        final Button addButton = new Button("Add");
        addButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent e) {
                String firstName = isEmpty(addFirstName.getText()) ? randomString(random) : addFirstName.getText();
                String lastName  = isEmpty(addLastName.getText())  ? randomString(random) : addLastName.getText();
                String email     = isEmpty(addEmail.getText())     ? randomString(random) : addEmail.getText();
                Person person = new Person(firstName, lastName, email);
                int idx = Collections.binarySearch(table.getItems(), person);
                if (idx < 0) {
                    idx = -idx - 1;
                }
                table.getItems().add(idx, person);
                table.getSelectionModel().select(idx);
                table.scrollTo(idx);

                addFirstName.clear();
                addLastName.clear();
                addEmail.clear();
            }
        });

        final HBox hb = new HBox(3);
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10));
        vbox.getChildren().addAll(label, table, hb);

        ((StackPane) scene.getRoot()).getChildren().addAll(vbox);

        stage.setScene(scene);
        stage.show();
    }

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

    private boolean isEmpty(String string) {
        return (string == null || string.isEmpty());
    }

    private String randomString(Random random) {
      char[] chars = new char[20];
      for (int i = 0; i < 20; i++) {
        int nextInt = random.nextInt(26);
        nextInt += random.nextBoolean() ? 65 : 97;
        chars[i] = (char) nextInt;
      }
      return new String(chars);
    }

    public static class Person implements Comparable<Person> {

        private final StringProperty firstName;
        private final StringProperty lastName;
        private final StringProperty email;

        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String fName) {
            firstName.set(fName);
        }

        public StringProperty firstNameProperty() {
          return firstName ;
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String fName) {
            lastName.set(fName);
        }

        public StringProperty lastNameProperty() {
          return lastName ;
        }

        public String getEmail() {
            return email.get();
        }

        public void setEmail(String fName) {
            email.set(fName);
        }

        public StringProperty emailProperty() {
          return email ;
        }

    @Override
    public int compareTo(Person o) {
      return firstName.get().compareToIgnoreCase(o.getFirstName());
    }
  }
} 

I have added my use case in question

我不知道您为什么需要这种行为。

一些建议:

  1. 试试 Java 8 early access . Java 8 的性能有了很大的提高,而且无论您是否使用选择模型,我都没有注意到时间上的差异。 (但是请注意,当针对您的示例运行时,Java 8 的行高亮显示在 build 94 中似乎已损坏,因此您可能需要 file an issue 以显示损坏的高亮显示)。
  2. 使用 table.getItems.add(index,element) 正如您在问题中首先提到的那样,而不是创建一个全新的列表并调用 table.getItems().setAll(newList)
  3. 使用System.nanoTime而不是 System.currentTimeMillis(),否则当您报告平均 0.2 毫秒时,您的测量值非常不准确,因为您只是平均 0 毫秒和 1 毫秒值。

因为我不认为这个问题有广泛的用处,所以我不会在这上面花更多的时间。

If it is really a issue I request you to do (file) it.... And am doing is because in swing based table (I can't post its code) it is much faster (in order of 0.2 ms in average)

我在 Java 8 早期访问版本中看到的交替行的闪烁是一个问题,我将尝试在一个更简单的程序中复制这个问题,并根据 JavaFX 问题跟踪器提交文件。

我认为这里没有任何关于性能的问题。是的,选择模型在 Java 7 中增加了一些(小的)开销,但在 Java 8 中,开销几乎察觉不到。对于该操作,我在 JavaFX for Java8 中测量的时间与您在 Swing 中测量的时间相同,为 0.2ms。因此,Java 8 的选择模型处理的平台实现已经进行了一些性能调整,我认为不需要进行任何进一步的调整。

also can you suggest me something on filtering

最好在新问题而不是评论中提出新问题。

不过,看看 Java 8 为此提供了什么。
有一个 FilteredList添加到 JavaFX for Java 8。 另请参阅 Panemu's TiwulFX其中包括表格过滤功能(以及许多其他有用的功能),看看它是否适合您的应用程序。

一般方法建议

不要如此快速地向表中添加行,而是将传入的行插入分批处理并减少将它们添加到表中的频率(例如每四分之一秒)。用户不会关心表是否每秒更新四次而不是每秒更新 60 次。

次要观察

如果您想对场景进行非常频繁的更新,而不是使用具有关键帧和每 20 毫秒触发一次的事件处理程序的时间轴,请使用 AnimationTimer每当系统接收到处理脉冲时就会触发(默认情况下,脉冲以每秒 60 次的固定间隔发生;例如,每 16.666 毫秒)。这将使处理过程更加顺利,因为时间轴的 20 毫秒关键帧可能会错过一个脉冲并最终略微不均匀(尽管眼睛可能不会察觉到不均匀)。

关于javafx tableview 主动排序很慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17165536/

相关文章:

java - 如何使用一个 Controller 来实现两种形式?

java - 使用静态方法泛型时不兼容的类型

java - 通过命令行上传 Java Elastic Beanstalk 应用程序

c++ - 新运算符中分配器的平均时间

php - 循环内的 SQL 查询

c++ - 我正在尝试做的最有效的树结构

drag-and-drop - JavaFX : Changing cursor during drag and drop

java - 使用 GSON 解析 Youtube JSONC

java - 类似的 jUnit 测试用例是否应该驻留在同一个测试中?

java - 图像容器