最近准备读写传统xls格式文件,而不是较新的xlsx,询问DeepSeek有哪些开源库,他给出了如下的简介和建议,还给出了相应链接,不过有的链接已失效。最后还不忘提醒,现在该用xlsx格式了。
以下是几个可以处理传统 .xls
(Excel 97-2003) 格式的 C++ 库推荐:
1. libxls (推荐)
- 特点:
- 纯C库,但可轻松用于C++项目
- 轻量级,只读操作
- 专门解析二进制
.xls
格式
- 安装:
# Ubuntu sudo apt-get install libxls-dev
- 示例代码:
#include <xls.h> void read_xls(const char* filename) { xlsWorkBook* pWB = xls_open(filename, "UTF-8"); xlsWorkSheet* pWS = xls_getWorkSheet(pWB, 0); xls_parseWorkSheet(pWS); for (int row=0; row<pWS->rows.lastrow; row++) { for (int col=0; col<pWS->rows.lastcol; col++) { std::cout << pWS->rows.sheet[row].cells[col].str << "\t"; } std::cout << std::endl; } xls_close_WS(pWS); xls_close_WB(pWB); }
2. BasicExcel
- 特点:
- 纯头文件库
- 支持读写操作
- 仅支持基本功能(无图表、公式等)
- GitHub: https://github.com/stephenquan/BasicExcel错误链接
- 示例代码:
#include "BasicExcel.hpp" void read_xls(const char* filename) { YExcel::BasicExcel excel; excel.Load(filename); YExcel::BasicExcelWorksheet* sheet = excel.GetWorksheet(0); for (size_t row=0; row<sheet->GetTotalRows(); row++) { for (size_t col=0; col<sheet->GetTotalCols(); col++) { YExcel::BasicExcelCell* cell = sheet->Cell(row, col); std::cout << cell->GetString() << "\t"; } std::cout << std::endl; } }
3. xlslib (已停止维护)
- 特点:
- 支持读写
- 跨平台
- 官网: http://xlslib.sourceforge.net
- 示例:
#include "xlslib.h" using namespace xlslib_core; void read_xls(const char* filename) { workbook wb; wb.Load(filename); worksheet* ws = wb.GetSheet(0); for (unsigned row=0; row<ws->LastRow(); row++) { for (unsigned col=0; col<ws->LastCol(); col++) { cell_t* cell = ws->FindCell(row, col); std::cout << cell->GetString() << "\t"; } std::cout << std::endl; } }
4. Qt XLS (QXlsx)
- 特点:
- 需要Qt框架
- 支持读写
- GitHub: https://github.com/QtExcel/QXlsx
- 示例:
#include <QXlsx/Workbook.h> void read_xls(const QString& filename) { QXlsx::Document xls(filename); for (int row=1; row<=xls.dimension().lastRow(); row++) { for (int col=1; col<=xls.dimension().lastColumn(); col++) { std::cout << xls.cellAt(row, col)->value().toString().toStdString() << "\t"; } std::cout << std::endl; } }
选择建议:
- 只需读取 → 选
libxls
(最稳定) - 需要简单读写 → 选
BasicExcel
- 已使用Qt → 选
QXlsx
- 需要完整功能 → 考虑用
libreoffice
或poi
的C++封装
注意:现代项目建议优先使用.xlsx
格式(可用libxlsxwriter
或OpenXLSX
库)。
以下是实际测试
版本
对我的小功能而言,Qt太重了,而且项目网页说“QXlsx is excel file(*.xlsx) reader/writer library”,也就是它其实不支持xls格式文件。所以下面比较前三种。
经过一番搜索,libxls原主页https://sourceforge.net/projects/libxls/,迁移到https://github.com/libxls/libxls了。最新的版本是libxls-1.6.3.tar.gz。
xlslib主页https://sourceforge.net/projects/xlslib/,还在,能正常下载。最新的版本是xlslib-package-2.5.0.zip。
BasicExcel我没找到软件网页,只找到作者Yap Chun Wei在2006年发表的一篇文章,但源代码链接已失效,于是又一通搜索,找到了存有该代码的的个人github项目,https://github.com/haorenhl007/BasicExcel,源码包。版本号是Version 1.14 (6 August 2006)。
libxls虽然也有apt安装和pkgs.org上可下载的软件包,但考虑公平比较,也顺便编译一遍源代码。
编译
libxls编译很简单,gnu软件的常规做法,解压、configure、make、make install即可。如果aarch64平台遇到编译器的问题,在安卓手机的termux+prooot环境中也能编译成功,然后这个so文件可拷贝到其他linux系统使用。
xlslib编译在x64平台相对顺利,参照libxls步骤即可。在aarch64平台编译会遇到问题,解决方法见前文。
BasicExcel无需单独编译,当然为了减少编译时间,也可以将它编译成动态链接库。方法如下:
g++ -std=c++14 -fPIC -c BasicExcel.cpp -o BasicExcel.o -O3
g++ -shared -o libbasic_excel.so BasicExcel.o
注意编译时加-O3参数,这样动态链接库的执行速度最快。调用它的主程序,因为没有大量实际操作,是否加-O3参数反而不重要了。
调用
将上述软件的头文件所在目录记录下来,在编译时用-I 参数提供。
将上述软件的so文件所在目录加入LIBRARY_PATH和LD_LIBRARY_PATH环境变量,前者是链接自己的程序时用,后者是运行时用。
如果用make install安装,则两者都在搜索路径中了。
功能
DeepSeek的简介已经写得很明白了,libxls只读、xlslib只写,功能全面,BasicExcel可读写,但只有基本功能。作者自己写的BasicExcel局限性如下:
它不支持格式化
它不支持公式
它不支持图表
它不支持Unicode UTF-32
它支持的东西如下:
读写数字(整数,实数)和字符串(ANSI,UTF16)
添加工作表
重命名工作表
删除工作表
获取工作表的名称
其实对我而言,这些功能也够用了。所以我的想法是,将它们做一个性能比较,最后各留下读性能和写性能最好的一个。
性能比较
我让DeepSeek编写了一个测试程序,提示词如下,
请把你列举的几个库用一个整合的main函数来测试读写(如果有)功能的计时,各种库写的示例xls文件,9个sheet,每个sheet分别是5列、10列、15列,分别是1百行,3百行,6百行,各种组合中都包括文本列、数字列、日期列。最后用BasicExcel输出一个存储比较结果,比较文件大小和读取或写入时间的excel表格
值得一提的是,它忘记了xlslib是只写的工具,所以还提交了测试读的代码,当然编译出错,现已删除。
#include <iostream>
#include <chrono>
#include <vector>
#include <string>
#include <iomanip>
#include <fstream>
// 测试配置
const std::vector<int> COLUMNS = {5,10};
const std::vector<int> ROWS = {8000};
const int SHEET_COUNT = 9;
// 测试数据生成
struct TestData {
std::string text;
double number;
std::string date;
};
TestData generate_data(int row, int col) {
return {
"Text_" + std::to_string(row) + "_" + std::to_string(col),
row * 0.1 + col,
"2023-" + std::to_string(1 + col % 12) + "-" + std::to_string(1 + row % 28)
};
}
// 测试结果存储
struct BenchmarkResult {
std::string library;
int columns;
int rows;
double write_time;
double read_time;
size_t file_size;
};
std::vector<BenchmarkResult> results;
// ----------------------------
// libxls 测试 (只读)
// ----------------------------
#ifdef USE_LIBXLS
#include <xls.h>
using namespace xls;
void test_libxls_read(const std::string& filename, int cols, int rows) {
auto start = std::chrono::high_resolution_clock::now();
xlsWorkBook* pWB = xls_open(filename.c_str(), "UTF-8");
if (!pWB) {
std::cerr << "Failed to open " << filename << std::endl;
return;
}
for (int sheet = 0; sheet < SHEET_COUNT; ++sheet) {
xlsWorkSheet* pWS = xls_getWorkSheet(pWB, sheet);
xls_parseWorkSheet(pWS);
// 简单验证数据
if (pWS->rows.lastrow != rows || pWS->rows.lastcol != cols -1) {
std::cerr << "Data mismatch in sheet " << sheet << pWS->rows.lastrow <<" "<< rows <<" "<< pWS->rows.lastcol <<" "<< cols <<std::endl;
}
xls_close_WS(pWS);
}
xls_close_WB(pWB);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::ifstream file(filename, std::ios::binary | std::ios::ate);
size_t size = file.tellg();
results.push_back({
"libxls", cols, rows,
0.0, // 无写入测试
elapsed.count(),
size
});
}
#endif
// ----------------------------
// BasicExcel 测试
// ----------------------------
#ifdef USE_BASICEXCEL
#include "BasicExcel.hpp"
void test_basic_excel(const std::string& filename, int cols, int rows) {
// 写入测试
auto write_start = std::chrono::high_resolution_clock::now();
YExcel::BasicExcel excel;
for (int sheet = 0; sheet < SHEET_COUNT; ++sheet) {
YExcel::BasicExcelWorksheet* ws = excel.AddWorksheet(("Sheet" + std::to_string(sheet)).c_str());
// 写入标题
for (int col = 0; col < cols; ++col) {
ws->Cell(0, col)->SetString(("Col_" + std::to_string(col)).c_str());
}
// 写入数据
for (int row = 1; row <= rows; ++row) {
TestData data = generate_data(row, cols);
ws->Cell(row, 0)->SetString(data.text.c_str());
ws->Cell(row, 1)->SetDouble(data.number);
ws->Cell(row, 2)->SetString(data.date.c_str());
// 填充剩余列
for (int col = 3; col < cols; ++col) {
ws->Cell(row, col)->SetString(("Extra_" + std::to_string(col)).c_str());
}
}
}
excel.SaveAs(filename.c_str());
auto write_end = std::chrono::high_resolution_clock::now();
// 读取测试
auto read_start = std::chrono::high_resolution_clock::now();
YExcel::BasicExcel excel2;
if (!excel2.Load(filename.c_str())) {
std::cerr << "Failed to load " << filename << std::endl;
return;
}
for (int sheet = 0; sheet < SHEET_COUNT; ++sheet) {
YExcel::BasicExcelWorksheet* ws = excel2.GetWorksheet(sheet);
if (!ws) continue;
// 简单验证数据
if (ws->GetTotalRows() != rows + 1 || ws->GetTotalCols() != cols) {
std::cerr << "Data mismatch in sheet " << sheet << ws->GetTotalRows() <<" "<< rows + 1 <<" "<< ws->GetTotalCols() <<" "<< cols <<std::endl;
}
}
auto read_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> write_elapsed = write_end - write_start;
std::chrono::duration<double> read_elapsed = read_end - read_start;
std::ifstream file(filename, std::ios::binary | std::ios::ate);
size_t size = file.tellg();
results.push_back({
"BasicExcel", cols, rows,
write_elapsed.count(),
read_elapsed.count(),
size
});
}
// ----------------------------
// 生成比较报告
// ----------------------------
void generate_report() {
YExcel::BasicExcel report;
YExcel::BasicExcelWorksheet* ws = report.AddWorksheet("Results");
// 标题行
ws->Cell(0, 0)->SetString("Library");
ws->Cell(0, 1)->SetString("Columns");
ws->Cell(0, 2)->SetString("Rows");
ws->Cell(0, 3)->SetString("Write Time (s)");
ws->Cell(0, 4)->SetString("Read Time (s)");
ws->Cell(0, 5)->SetString("File Size (KB)");
// 数据行
for (size_t i = 0; i < results.size(); ++i) {
const auto& res = results[i];
int row = i + 1;
ws->Cell(row, 0)->SetString(res.library.c_str());
ws->Cell(row, 1)->SetDouble(res.columns);
ws->Cell(row, 2)->SetDouble(res.rows);
ws->Cell(row, 3)->SetDouble(res.write_time);
ws->Cell(row, 4)->SetDouble(res.read_time);
ws->Cell(row, 5)->SetDouble(res.file_size / 1024.0);
}
report.SaveAs("benchmark_results.xls");
std::cout << "Benchmark report saved to benchmark_results.xls" << std::endl;
}
#endif
// ----------------------------
// xlslib 测试
// ----------------------------
#ifdef USE_XLSLIB
#include "xlslib.h"
void test_xlslib(const std::string& filename, int cols, int rows) {
// 写入测试
auto write_start = std::chrono::high_resolution_clock::now();
xlslib_core::workbook wb;
for (int sheet = 0; sheet < SHEET_COUNT; ++sheet) {
xlslib_core::worksheet* ws = wb.sheet(("Sheet" + std::to_string(sheet)).c_str());
// 写入标题
for (int col = 0; col < cols; ++col) {
ws->label(0, col, ("Col_" + std::to_string(col)).c_str());
}
// 写入数据
for (int row = 1; row <= rows; ++row) {
TestData data = generate_data(row, cols);
ws->label(row, 0, data.text.c_str());
ws->number(row, 1, data.number);
ws->label(row, 2, data.date.c_str());
// 填充剩余列
for (int col = 3; col < cols; ++col) {
ws->label(row, col, ("Extra_" + std::to_string(col)).c_str());
}
}
}
wb.Dump(filename.c_str());
auto write_end = std::chrono::high_resolution_clock::now();
// 读取测试
auto read_start = std::chrono::high_resolution_clock::now();
/* 删除读取代码
*/
auto read_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> write_elapsed = write_end - write_start;
std::chrono::duration<double> read_elapsed = read_end - read_start;
std::ifstream file(filename, std::ios::binary | std::ios::ate);
size_t size = file.tellg();
results.push_back({
"xlslib", cols, rows,
write_elapsed.count(),
read_elapsed.count(),
size
});
}
#endif
// ----------------------------
// 主函数
// ----------------------------
int main() {
// 测试所有组合
for (int cols : COLUMNS) {
for (int rows : ROWS) {
std::string base_name = "test_" + std::to_string(cols) + "x" + std::to_string(rows);
#ifdef USE_BASICEXCEL
test_basic_excel(base_name + "_basic.xls", cols, rows);
#endif
#ifdef USE_XLSLIB
test_xlslib(base_name + "_xlslib.xls", cols, rows);
#endif
#ifdef USE_LIBXLS
// 需要先用其他库生成文件
test_libxls_read(base_name + "_basic.xls", cols, rows);
#endif
}
}
// 生成比较报告
#ifdef USE_BASICEXCEL
generate_report();
#endif
return 0;
}
将测试程序命名为excel_benchmark.cpp,编译脚本如下:
#1.只测试BasicExcel
#如果提前将BasicExcel编译成动态链接库
g++ -std=c++14 -c excel_benchmark.cpp -o excel_benchmark.o -DUSE_BASICEXCEL -O3
g++ -o excel_benchmark excel_benchmark.o -L ./ -lbasic_excel
#如果一次性编译
g++ -o excel_benchmark excel_benchmark.cpp -DUSE_BASICEXCEL -O3
#2.只测试xlslib,因为数据量稍大,超过5000行,BasicExcel就执行出错
g++ -std=c++14 excel_benchmark.cpp -o excel_benchmark2 -DUSE_XLSLIB -I /par/xlslib/src -O3 -lxls
#3.测试BasicExcel+libxls
g++ -std=c++14 -c excel_benchmark.cpp -o excel_benchmark3.o -DUSE_BASICEXCEL -DUSE_LIBXLS -I /usr/local/include/xlslib -O3
g++ -o excel_benchmark3 excel_benchmark3.o -L ./ -lbasic_excel -lxlsreader
#4.测试BasicExcel+xlslib
g++ -std=c++14 -c excel_benchmark.cpp -o excel_benchmark4.o -DUSE_BASICEXCEL -DUSE_XLSLIB -I /usr/local/include/xlslib -O3
g++ -o excel_benchmark4 excel_benchmark4.o -L ./ -lbasic_excel -lxls
因为两个软件都定义了 ‘unsigned64_t’,编译冲突,没有测试xlslib+libxls的组合
各种规模的xls文件读写测试如下表所示,因为x64和arm配置不同不可比, 除了一开始显示BasicExcel过慢的写速度时跨平台比较外,其余均只测大家都正常的arm平台。
xlslib写2个8000行文件测试结果:
time ./excel_benchmark2
real 0m2.223s
user 0m2.116s
sys 0m0.076s
一个问题有待解决,BasicExcel在x64平台上的速度离奇地慢,1.3MB的文件写入用时13秒,约为aarch64上的百倍。更大的问题是,当文件稍大,比如10列5000行,就会发生内存分配错误,而代码中对这类错误并没有异常捕获机制,导致不能自动退出,而是耗尽系统资源。
所以结论就是, 各种库的兼容性都不错,都能跨平台使用。
xlslib在功能多很多的情况下,用时与BasicExcel相当或略慢,而且支持更大的文件,缺点是生成的文件较大。libxls的读速比BasicExcel快很多。