I don't know what's up with the speed of the sort on the TableView when you click on the table header - it gets slow with 100,000 rows as you mention in your question.
If you just provide a button which sorts the underlying collection, it sorts many times quicker and the table updates fine (at least under Java 8). Time to sort a column was well under a second.
Button sortByEmail = new Button("Sort by Email");
sortByEmail.setOnAction(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent event) {
Collections.sort(table.getItems(), new Comparator<Person>()
@Override public int compare(Person o1, Person o2) {
return o1.getEmail().compareTo(o2.getEmail());
}
});
}
});
Or using Java 8 lambdas:
Button sortByEmail = new Button("Sort by Email");
sortByEmail.setOnAction(event ->
Collections.sort(
table.getItems(),
(o1, o2) -> o1.getEmail().compareTo(o2.getEmail())
)
);
So if you have a lot of items in your table, on the column call setSortable(false) and provide buttons for the user to sort table columns if you need to.
Plugging my laptop into the wall and increasing the maximum memory of the JVM also increased sort performance for large data sets (cutting the time to sort by clicking on a column header with the sample below from 20 seconds to about 10 seconds).
File an enhancement request against the JavaFX issue tracker with the following sample code. The code generates 100,000 rows of random data, and you can check the performance of standard column sorting by clicking on a table column header to sort on a table column and compare that with the performance of sorting by Collections.sort
invoked by pressing buttons.
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Random;
public class Test extends Application {
private TableView<Person> table = new TableView<Person>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com"),
new Person("Michael", "Brown", "michael.brown@example.com"));
final HBox hb = new HBox();
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(550);
stage.setHeight(550);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setMinWidth(100);
firstNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("firstName"));
TableColumn lastNameCol = new TableColumn("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("lastName"));
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(200);
emailCol.setCellValueFactory(
new PropertyValueFactory<Person, String>("email"));
Random random = new Random();
table.setItems(data);
for (int i = 0; i < 100000; i++) {
table.getItems().add(new Person(randomString(random), randomString(random), randomString(random)));
}
table.getColumns().addAll(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) {
data.add(new Person(
addFirstName.getText(),
addLastName.getText(),
addEmail.getText()));
addFirstName.clear();
addLastName.clear();
addEmail.clear();
}
});
Button sortByFirstName = new Button("Sort by First Name");
sortByFirstName.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
Collections.sort(table.getItems());
}
});
Button sortByEmail = new Button("Sort by Email");
sortByEmail.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
Collections.sort(table.getItems(), new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getEmail().compareTo(o2.getEmail());
}
});
}
});
hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton, sortByFirstName, sortByEmail);
hb.setSpacing(3);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(label, table, hb);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
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 SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private final SimpleStringProperty 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 String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
public String getEmail() {
return email.get();
}
public void setEmail(String fName) {
email.set(fName);
}
@Override
public int compareTo(Person o) {
return firstName.get().compareTo(o.getFirstName());
}
}
}