Reputation: 1982
I am working on javafx tableview and have created a table with 100,000 rows (three columns one int two float).
I have active sorting on. To insert a new row am first searching index using binary search and then inserting it using table.getItems.add(index,element);
But with addition of new row every 20 ms gui is litttle bit non responsive.
I added table.setSelectionModel(null); and it fasten my GUI so it seems to be culprit behind slow GUI.
But I also need ability to select rows.....
an anyone suggest what to do in this case....
P.S. :(Before adding line table.setSelectionModel(null); I tried to run jprofiler and it was showing major time is consumed in javafx.scene.control.TableCell$2.onChanged )
Edit:
My use case
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 in start
//this line increase speed but then we can not even click on table as it will give someexception
table.setSelectionModel(null);
helps me increasing speed of insertion to 0.2 ms on average (code for computing average is included in code)
but it disable any select (code in addPersonToTable is commented because of this reason )
I want ability to select a row but with some speed efficiency of this code. (I used Jprofiler and it shows main time is spent in TableCell.onChanged )
Note: This code is written by James_D I just modified it a little (added line table.setSelectionModel(null); and comment lines in addPersonToTable)
Upvotes: 1
Views: 1704
Reputation: 159386
I can't replicate your issue.
Adding new rows to a sorted position in a TableView with 100,000 rows in it is virtually instantaneous to me.
I used a modification of James answer to your previous question: JavaFx tableview sort is really slow how to improve sort speed as in java swing.
The modification performs the following algorithm on pressing the Add button:
If you use Java 7 there is a bug in the TableView scrollTo routine which prevents the table from scrolling to the right location in all situations.
Output using 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());
}
}
}
I have added my use case in question
I have no idea why you require this behavior.
Some advice:
table.getItems.add(index,element)
as you mention first in your question rather than creating a completely new list and invoking table.getItems().setAll(newList)
.System.currentTimeMillis()
, otherwise your measurements are pretty inaccurate when you report an average of 0.2ms because you are just averaging 0 millisecond and 1 millisecond values.As I do not believe this question is broadly useful, so I will not spend further time on it.
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)
The flickering of alternate rows which I see in Java 8 early access releases is a problem, which I will try to replicate in a simpler program and file against the JavaFX issue tracker.
I don't believe there is any issue to be filed here with regards to performance. Yes, the selection model adds some (small) overhead in Java 7, but in Java 8, the overhead is barely perceptible. For the operation, I measure the same 0.2ms in JavaFX for Java8 as you measured for Swing. So, there has already been some performance tweaking in the platform implementation of the selection model processing for Java 8 and I don't believe any further tweaking is required.
also can you suggest me something on filtering
Best to ask new questions in new questions rather than comments.
However, take a look at what Java 8 has to offer for this.
There was a FilteredList added to JavaFX for Java 8.
Also look at Panemu's TiwulFX which includes table filtering capabilities (among many other useful things) and see if it suits your application.
General Approach Suggestion
Don't add rows so rapidly to the table, instead batch your incoming row inserts up and add them to the table less often (e.g. every quarter second). The user isn't going to care if the table is updated four times a second instead of 60 times a second.
Minor Observation
If you want to make very frequent updates to a scene, instead of a timeline which has a KeyFrame and an event handler that fires every 20 milliseconds, use an AnimationTimer that will be fired whenever the system receives a pulse for processing (by default pulses occur on regular intervals 60 times a second; e.g. every 16.666 milliseconds). This is going to end up processing stuff more smoothly because the 20 millisecond keyframe of the timeline could miss a pulse and end up slightly uneven (though the eye likely might not perceive to unevenness).
Upvotes: 4