Ускорьте фильтрацию QSortFilterProxyModel при работе с почти большими наборами данных

Прежде чем я спросил a question о фильтрации нескольких столбцов, которые нам нужны для представления строк, которые соответствуют более чем одному шаблону фильтра.

Теперь при работе с большими таблицами big я имею в виду около 200000 строк и 4 столбца) фильтрация замедляется, если у нас такая большая таблица (обычно это худший результат для первых 2 символов шаблона фильтра).

Итак, что вы предлагаете по этому поводу?

Примечание: у меня есть собственная высокопроизводительная модель данных источника (вместо QStandardItemModel) на основе this пример ведьмы кормить мой взгляд на это количество строк в течение 1 секунды

Редактировать 1

Меняя мой метод от этого:

bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
if (/* filtering is enable*/) {
    bool _res = sourceModel()->data(sourceModel()->index(source_row, 0, source_parent)).toString().contains( /*RegExp for column 0*/);
    for (int col = 0; col < columnCount(); col++) {
        _res &= sourceModel()->data(sourceModel()->index(source_row, col + 1, source_parent)).toString().contains(/*RegExp for column col + 1*/);
    }
    return _res;
}
return true;

}

К этому:

bool DataFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
    if (_enable) {
        return (sourceModel()->index(source_row, 0, source_parent.child(source_row, 0)).data().toString().contains( /*string for column 0*/ ))
            && sourceModel()->index(source_row, 1, source_parent.child(source_row, 1)).data().toString().contains(/*string for column 1*/))
            && sourceModel()->index(source_row, 2, source_parent.child(source_row, 2)).data().toString().contains(/*string for column 2*/))
            && sourceModel()->index(source_row, 3, source_parent.child(source_row, 3)).data().toString().contains(/*string for column 3*/));
    }
    return true;
}

Смотри работает отлично. Теперь фильтрация работает как брелок без задержки

1 ответ

Если количество элементов очень велико, и вы не можете загрузить их за один снимок, вы можете попробовать добавлять элементы в пакетах только тогда, когда они нужны в представлении. Это может быть сделано путем переопределения canFetchMore() а также fetchMore(), Посмотрите на пример Fetch More. Обратите внимание, что это как QSqlQueryModel внутренне загружает большие модели из баз данных, смотрите здесь.

Вот как ваша модель может быть реализована с использованием этого подхода:

#include <QApplication>
#include <QtWidgets>

class MyTableModel : public QAbstractTableModel{
public:
    explicit MyTableModel(int rowCount, QObject* parent=nullptr)
        :QAbstractTableModel(parent),currentRowCount(0),wholeRowCount(rowCount){}
    ~MyTableModel(){}

    int rowCount(const QModelIndex &parent) const override{
        if(parent.isValid()) return 0;
        return currentRowCount;
    }
    int columnCount(const QModelIndex &parent) const override{
        if(parent.isValid()) return 0;
        return 2;
    }

    QVariant data(const QModelIndex &index, int role) const override{
        Q_ASSERT(index.row()<currentRowCount);
        QVariant val;
        if(role== Qt::DisplayRole || role== Qt::EditRole){
            switch(index.column()){
            case 0:
                val= QString("#%1").arg(index.row()+1, 8, 10, QChar('0'));
                break;
            case 1:
                val= rows[index.row()];
                break;
            }
        }
        return val;
    }

    bool canFetchMore(const QModelIndex &parent) const override{
        if(parent.isValid()) return false;
        return (currentRowCount < wholeRowCount);
    }

    void fetchMore(const QModelIndex& /* index */) override{
        int toFetch= qMin(52, wholeRowCount-currentRowCount);
        char ch = 'A';
        beginInsertRows(QModelIndex(), currentRowCount, currentRowCount+toFetch-1);
        for(int i=0; i<toFetch; i++){
            rows+= QString(QChar(ch));
            if(ch == 'Z') ch = 'A';
            else ch++;
        }
        currentRowCount+= toFetch;
        endInsertRows();
    }

private:
    int currentRowCount;
    int wholeRowCount;
    QStringList rows;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QWidget w;
    QVBoxLayout layout(&w);
    QLineEdit filterLineEdit;
    QTableView tableView;
    layout.addWidget(&filterLineEdit);
    layout.addWidget(&tableView);

    MyTableModel model(200000);
    QSortFilterProxyModel proxyModel;
    proxyModel.setSourceModel(&model);
    proxyModel.setFilterKeyColumn(-1);
    tableView.setModel(&proxyModel);

    QObject::connect(&filterLineEdit, &QLineEdit::textChanged, [&](){
        proxyModel.setFilterFixedString(filterLineEdit.text());
    });

    w.show();

    return a.exec();
}

Если вы уверены, что ваше реальное узкое место фильтруется, вы также можете избежать использования регулярных выражений, как отмечает @DmitrySazonov, подкласс. QSortFilterProxyModel переопределить filterAcceptsRow() и предоставьте свой алгоритм вместо использования общего QRegExp фильтры.

Еще одна вещь, которую следует учитывать, - избегать проверки уже отфильтрованных строк, когда фильтр стал уже, взгляните на этот вопрос.

Другие вопросы по тегам