最近准备读写传统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;
        }
    }
    

选择建议:

  1. 只需读取 → 选libxls(最稳定)
  2. 需要简单读写 → 选BasicExcel
  3. 已使用Qt → 选QXlsx
  4. 需要完整功能 → 考虑用libreofficepoi的C++封装

注意:现代项目建议优先使用.xlsx格式(可用libxlsxwriterOpenXLSX库)。


以下是实际测试

版本

对我的小功能而言,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快很多。

不同规模读写比较
后记:
使用DeepSeek的办法解决了xlslib和libxls分别定义同名unsigned64_t冲突,即在包含某个头文件时,先将unsigned64_t定义为另一个名字,然后再取消。

// ----------------------------
// libxls 测试 (只读)
// ----------------------------
#ifdef USE_LIBXLS
#define unsigned64_t xls_unsigned64_t
#include <xls.h>
#undef unsigned64_t

这样我们在一次执行中得到了如下结果报告

Library Columns Rows Write Time (s) Read Time (s) File Size (KB)
BasicExcel 5 100 1.149 0.027 88
xlslib 5 100 0.007 0.000 130
libxls 5 100 0.000 0.008 88
BasicExcel 5 300 3.095 0.082 256.5
xlslib 5 300 0.015 0.000 366
libxls 5 300 0.000 0.019 256.5
BasicExcel 10 100 2.433 0.041 151.5
xlslib 10 100 0.010 0.000 230
libxls 10 100 0.000 0.012 151.5
BasicExcel 10 300 5.596 0.159 444
xlslib 10 300 0.028 0.000 665
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐