读取excel慢的原因
这里不说如何打开或生成excel,着重说说如何快速读取excel。
网上搜到用Qt操作excel的方法,读取都是使用类似下面这种方法进行:
-
QVariant ExcelBase::read(int row, int col)
-
{
-
QVariant ret;
-
if (this->sheet != NULL && ! this->sheet->isNull())
-
{
-
QAxObject* range = this->sheet->querySubObject("Cells(int, int)", row, col);
-
//ret = range->property("Value");
-
ret = range->dynamicCall("Value()");
-
delete range;
-
}
-
return ret;
-
}
读取慢的根源就在于sheet->querySubObject("Cells(int, int)", row, col)
试想有10000个单元就得调用10000次querySubObject
,网络上90%的教程都没说这个querySubObject
产生的QAxObject*
最好进行手动删除,虽然在它的父级QAxObject
会管理它的内存,但父级不析构,子对象也不会析构,若调用10000次,就会产生10000个QAxObject
对象 得益于QT快速读取数据量很大的Excel文件此文,下面总结如何快速读写excel
快速读取excel文件
原则是一次调用querySubObject
把所有数据读取到内存中
VBA中可以使用UsedRange
把所有用到的单元格范围返回,并使用属性Value
把这些单元格的所有值获取。
这时,获取到的值是一个table,但Qt把它变为一个变量QVariant来储存,其实实际是一个QList<QList<QVariant> >
,此时要操作里面的内容,需要把这个QVariant
转换为QList<QList<QVariant> >
先看看获取整个单元格的函数示意(这里ExcelBase是一个读写excel的类封装):
-
QVariant ExcelBase::readAll()
-
{
-
QVariant var;
-
if (this->sheet != NULL && ! this->sheet->isNull())
-
{
-
QAxObject *usedRange = this->sheet->querySubObject("UsedRange");
-
if(NULL == usedRange || usedRange->isNull())
-
{
-
return var;
-
}
-
var = usedRange->dynamicCall("Value");
-
delete usedRange;
-
}
-
return var;
-
}
代码中this->sheet
是已经打开的一个sheet,再获取内容时使用this->sheet->querySubObject("UsedRange");
即可把所有范围都获取。
下面这个castVariant2ListListVariant函数把QVariant
转换为QList<QList<QVariant> >
-
///
-
/// \brief 把QVariant转为QList<QList<QVariant> >
-
/// \param var
-
/// \param res
-
///
-
void ExcelBase::castVariant2ListListVariant(const QVariant &var, QList<QList<QVariant> > &res)
-
{
-
QVariantList varRows = var.toList();
-
if(varRows.isEmpty())
-
{
-
return;
-
}
-
const int rowCount = varRows.size();
-
QVariantList rowData;
-
for(int i=0;i<rowCount;++i)
-
{
-
rowData = varRows[i].toList();
-
res.push_back(rowData);
-
}
-
}
这样excel的所有内容都转换为QList<QList<QVariant>>
保存,其中QList<QList<QVariant> >
中QList<QVariant>
为每行的内容,行按顺序放入最外围的QList中。
快速写入excel文件
同理,能通过QAxObject *usedRange = this->sheet->querySubObject("UsedRange");
实现快速读取,也可以实现快速写入
快速写入前需要些获取写入单元格的范围:Range(const QString&)
如excel的A1为第一行第一列,那么A1:B2就是从第一行第一列到第二行第二列的范围。
要写入这个范围,同样也是通过一个与之对应的QList<QList<QVariant> >
,具体见下面代码:
-
///
-
/// \brief 写入一个表格内容
-
/// \param cells
-
/// \return 成功写入返回true
-
/// \see readAllSheet
-
///
-
bool ExcelBase::writeCurrentSheet(const QList<QList<QVariant> > &cells)
-
{
-
if(cells.size() <= 0)
-
return false;
-
if(NULL == this->sheet || this->sheet->isNull())
-
return false;
-
int row = cells.size();
-
int col = cells.at(0).size();
-
QString rangStr;
-
convertToColName(col,rangStr);
-
rangStr += QString::number(row);
-
rangStr = "A1:" + rangStr;
-
qDebug()<<rangStr;
-
QAxObject *range = this->sheet->querySubObject("Range(const QString&)",rangStr);
-
if(NULL == range || range->isNull())
-
{
-
return false;
-
}
-
bool succ = false;
-
QVariant var;
-
castListListVariant2Variant(cells,var);
-
succ = range->setProperty("Value", var);
-
delete range;
-
return succ;
-
}
此函数是把数据从A1开始写
函数中的convertToColName
为把列数,转换为excel中用字母表示的列数,这个函数是用递归来实现的:
-
///
-
/// \brief 把列数转换为excel的字母列号
-
/// \param data 大于0的数
-
/// \return 字母列号,如1->A 26->Z 27 AA
-
///
-
void ExcelBase::convertToColName(int data, QString &res)
-
{
-
Q_ASSERT(data>0 && data<65535);
-
int tempData = data / 26;
-
if(tempData > 0)
-
{
-
int mode = data % 26;
-
convertToColName(mode,res);
-
convertToColName(tempData,res);
-
}
-
else
-
{
-
res=(to26AlphabetString(data)+res);
-
}
-
}
-
///
-
/// \brief 数字转换为26字母
-
///
-
/// 1->A 26->Z
-
/// \param data
-
/// \return
-
///
-
QString ExcelBase::to26AlphabetString(int data)
-
{
-
QChar ch = data + 0x40;//A对应0x41
-
return QString(ch);
-
}
-
void CMainWindow::openExcel(QString fileName)
-
{
-
QAxObject excel("Excel.Application");
-
excel.setProperty("Visible", false);
-
QAxObject *work_books = excel.querySubObject("WorkBooks");
-
work_books->dynamicCall("Open(const QString&)", fileName);
-
QAxObject *work_book = excel.querySubObject("ActiveWorkBook");
-
QAxObject *work_sheets = work_book->querySubObject("Sheets"); //Sheets也可换用WorkSheets
-
int sheet_count = work_sheets->property("Count").toInt(); //获取工作表数目
-
if (sheet_count > 0)
-
{
-
QAxObject *work_sheet = work_book->querySubObject("Sheets(int)", 1);
-
ui.label->setText("文件数据读取中...");
-
QVariant var = readAll(work_sheet);
-
castVariant2ListListVariant(var);
-
}
-
work_book->dynamicCall("Close(Boolean)", false); //关闭文件
-
excel.dynamicCall("Quit(void)"); //退出
-
}
-
QVariant CMainWindow::readAll(QAxObject *sheet)
-
{
-
QVariant var;
-
if (sheet != NULL && !sheet->isNull())
-
{
-
QAxObject *usedRange = sheet->querySubObject("UsedRange");
-
if (NULL == usedRange || usedRange->isNull())
-
{
-
return var;
-
}
-
var = usedRange->dynamicCall("Value");
-
delete usedRange;
-
}
-
return var;
-
}
-
void CMainWindow::castVariant2ListListVariant(const QVariant &var)
-
{
-
QVariantList varRows = var.toList();
-
if (varRows.isEmpty())
-
{
-
return;
-
}
-
const int rowCount = varRows.size();
-
QVariantList rowData;
-
for (int i = 0; i < rowCount; ++i)
-
{
-
rowData = varRows[i].toList();
-
if (i == 0)
-
{
-
QStringList headers;
-
for each (auto item in rowData)
-
{
-
QString value = item.toString();
-
headers.append(value);
-
}
-
ui.tableWidget->setColumnCount(headers.size()); //设置列数
-
ui.tableWidget->setHorizontalHeaderLabels(headers);
-
}
-
else
-
{
-
int row = ui.tableWidget->rowCount();
-
ui.tableWidget->setRowCount(row + 1);
-
for (int j = 0; j < rowData.size(); j++)
-
{
-
QString value = rowData[j].toString();
-
QTableWidgetItem *item = new QTableWidgetItem(value);
-
ui.tableWidget->setItem(row, j, item);
-
}
-
}
-
}
-
}