1

我正在研究 javafx tableview 并创建了一个包含 100,000 行的表(三列一 int 两个浮点数)。

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

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

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

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

任何人都建议在这种情况下该怎么做....

PS :(在添加行之前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毫秒(用于计算平均值的代码包含在代码中)

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

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

注意:这段代码是 James_D 写的,我只是稍微修改了一下(添加行 table.setSelectionModel(null); 和 addPersonToTable 中的注释行)

4

1 回答 1

4

我无法复制您的问题。

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

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

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

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

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

使用 Java 8b93、Win7 输出:

排序样本

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());
    }
  }
} 

我已经添加了有问题的用例

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

一些忠告:

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

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

如果这真的是一个问题,我要求你做(归档)它......而且我正在做的是因为在基于摆动的表中(我不能发布它的代码)它要快得多(平均为 0.2 毫秒)

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

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

你也可以给我一些关于过滤的建议吗

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

但是,看看 Java 8 为此提供了什么。
Java 8 的 JavaFX 中添加了一个FilteredList。还可以查看Panemu 的 TiwulFX,它包括表过滤功能(以及许多其他有用的功能),看看它是否适合您的应用程序。

一般方法建议

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

小观察

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

于 2013-06-18T22:24:57.073 回答