4

从服务端生成Excel电子表格(GcExcel + SpreadJS)

 2 years ago
source link: https://segmentfault.com/a/1190000041377778
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在服务端生成Excel电子表格,除了使用 Node.js + SpreadJS 外,葡萄城官方推荐使用 SpreadJS + GcExcel。该方案不仅能够解决批量绑定数据源并导出Excel、批量修改大量Excel内容及样式、服务端批量打印以及生成PDF文档等需求,还提供了远超行业标准的组件性能。

为了验证SpreadJS + GcExcel的处理性能,本文将就GcExcel for Java和Node.js中运行SpreadJS的各项数据进行对比。由于SpreadJS和GcExcel的组件功能非常丰富,本文仅选择最为常见的两个功能点做对比,分别是设置区域数据和导出Excel文档。

一、本次测试的几个大前提

由于Node.js是基于V8引擎来执行JavaScript的,因此它的js也是基于事件机制的非阻塞单线程运行,其文件的I/O都是异步执行的,而Node.js之所以选择单线程的方式是因为编码简单、开发难度低、对"码农"码农的心智消耗相对较小;而且它的文件I/O是异步执行的,所以不需要像Java那样需要创建、回收线程(Node.js的I/O操作在底层也是线程,这里不做深入讨论),这方面开销较小。
但是,单线程在做复杂运算方面相比多线程则没有任何优势,也无法利用多线程来有效调配多核CPU进行优化,因此在Node.js中运行SpreadJS就只能是单线程JS,这也会影响SpreadJS 的数据处理性能。

所以,为了获得更加准确的测试结果,本篇中设计的测试用例,在两个环境(Java 和 Node.js)中都采用单线程执行,并且选择了与Node.js更加匹配的批量I/O操作作为测试用例。

二、 Node.js 与 SpreadJS 的测试代码和结果:

软件版本CPU内存Node.js 16.10.0Intel(R) Core(TM) i7-9750HQ CPU @ 2.80 GHz32G

测试代码:如下所示,用一个Performance类执行1000次设置数据、导出Excel文档的操作。

const fs = require('fs');

// Initialize the mock browser variables
const mockBrowser = require('mock-browser').mocks.MockBrowser;
global.window = mockBrowser.createWindow();
global.document = window.document;
global.navigator = window.navigator;
global.HTMLCollection = window.HTMLCollection;
global.getComputedStyle = window.getComputedStyle;

const fileReader = require('filereader');
global.FileReader = fileReader;

const GC = require('@grapecity/spread-sheets');
const GCExcel = require('@grapecity/spread-excelio');

GC.Spread.Sheets.LicenseKey = GCExcel.LicenseKey = "Your License";

const dataSource = require('./data');

function runPerformance(times) {

  const timer = `test in ${times} times`;
  console.time(timer);

  for(let t=0; t<times; t++) {
    // const hostDiv = document.createElement('div');
    // hostDiv.id = 'ss';
    // document.body.appendChild(hostDiv);
    const wb = new GC.Spread.Sheets.Workbook()//global.document.getElementById('ss'));
    const sheet = wb.getSheet(0);
    for(let i=0; i<dataSource.length; i++) {
      sheet.setValue(i, 0, dataSource[i]["Film"]);
      sheet.setValue(i, 1, dataSource[i]["Genre"]);
      sheet.setValue(i, 2, dataSource[i]["Lead Studio"]);
      sheet.setValue(i, 3, dataSource[i]["Audience Score %"]);
      sheet.setValue(i, 4, dataSource[i]["Profitability"]);
      sheet.setValue(i, 5, dataSource[i]["Rating"]);
      sheet.setValue(i, 6, dataSource[i]["Worldwide Gross"]);
      sheet.setValue(i, 7, dataSource[i]["Year"]);
    }
    exportExcelFile(wb, times, t);
  }
  
}

function exportExcelFile(wb, times, t) {
    const excelIO = new GCExcel.IO();
    excelIO.save(wb.toJSON(), (data) => {
        fs.appendFile('results/Invoice' + new Date().valueOf() + '_' + t + '.xlsx', new Buffer(data), function (err) {
          if (err) {
            console.log(err);
          }else {
            if(t === times-1) {
              console.log('Export success');
              console.timeEnd(`test in ${times} times`);
            }
          }
        });
    }, (err) => {
        console.log(err);
    }, { useArrayBuffer: true });
}

runPerformance(1000)

完整的测试工程请参考:https://gitee.com/GrapeCity/Node.js-SpreadJS-two.git

测试工程运行方式:

  • npm install
  • node ./app.js

运行结果:平均每次花费 18.1 ms

三、 GcExcel 的测试代码和结果

软件版本CPU内存GcExcel V5.0Intel(R) Core(TM) i7-9750HQ CPU @ 2.80 GHz32G

测试代码如下所示:

public class Performance {

        public static void main(String[] args) {
                System.out.println(System.getProperty("user.dir") + "/sources/jsonData");
                String jsonStr = readTxtFileIntoStringArrList(System.getProperty("user.dir") + "/sources/jsonData");
                JSONArray jsonArr = JSON.parseArray(jsonStr);
                //JSONObject jsonObj = (JSONObject) jsonArr.get(0);
                //System.out.println(jsonObj.get("Film"));
                run(1000, jsonArr);
        }

        public static void run(int times, JSONArray dataArr) {
                String path = System.getProperty("user.dir") + "/results/";
                System.out.println(path + "result.xlsx");
                long start = new Date().getTime();
                for (int i = 0; i < times; i++) {
                        Workbook workbook = new Workbook();
                        IWorksheet worksheet = workbook.getWorksheets().get(0);
                        for (int j = 0; j < dataArr.size(); j++) {
                                JSONObject jsonObj = (JSONObject) dataArr.get(j);
                                worksheet.getRange(j, 0, 1, 8).get(0).setValue(jsonObj.get("Film"));
                                worksheet.getRange(j, 0, 1, 8).get(1).setValue(jsonObj.get("Genre"));
                                worksheet.getRange(j, 0, 1, 8).get(2).setValue(jsonObj.get("Lead Studio"));
                                worksheet.getRange(j, 0, 1, 8).get(3).setValue(jsonObj.get("Audience Score %"));
                                worksheet.getRange(j, 0, 1, 8).get(4).setValue(jsonObj.get("Profitability"));
                                worksheet.getRange(j, 0, 1, 8).get(5).setValue(jsonObj.get("Rating"));
                                worksheet.getRange(j, 0, 1, 8).get(6).setValue(jsonObj.get("Worldwide Gross"));
                                worksheet.getRange(j, 0, 1, 8).get(7).setValue(jsonObj.get("Year"));
                        }
                        workbook.save(path + "result" + i + ".xlsx");
                }
                System.out.println("运行"+times+"次花费时常(ms): " + (new Date().getTime() - start));

        }

        public static String readTxtFileIntoStringArrList(String filePath) {
                StringBuilder list = new StringBuilder();
                try {
                        String encoding = "GBK";
                        File file = new File(filePath);
                        if (file.isFile() && file.exists()) {
                                InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);// 考虑到编码格式
                                BufferedReader bufferedReader = new BufferedReader(read);
                                String lineTxt = null;

                                while ((lineTxt = bufferedReader.readLine()) != null) {
                                        list.append(lineTxt);
                                }
                                bufferedReader.close();
                                read.close();
                        } else {
                                System.out.println("找不到指定的文件");
                        }
                } catch (Exception e) {
                        System.out.println("读取文件内容出错");
                        e.printStackTrace();
                }
                return list.toString();
        }

}

完整的测试工程zip请参考附件:https://gitee.com/GrapeCity/Node.js-SpreadJS-two.git

测试脚本运行方式:导入Eclipse后直接run as Application

运行结果如下所示:平均每次花费 8.4 ms

四、总结分析:

1 、测试结果分析:node.js平均每次花费 18.1 ms,GcExcel平均每次花费 8.4 ms,两者同时执行1000次设置数据、导出Excel文档的操作,性能相差2倍。

2、处理性能的对比分析:

即便对于单线程的批量I/O操作,SpreadJS 在 Node.js的运行性能仍不如SpreadJS 在GcExcel for Java中运行,一方面是由于GcExcel性能的确非常优秀,它在Java平台上运用了很多优秀、成熟的解决方案,做到了同类产品中最一流的性能表现,另一方面是由于GcExcel对Excel和SpreadJS有更加全面的功能支持。目前,GcExcel已经作为行业内服务器端处理Excel文档的首选方案。

3 、技术选型的分析:

除了性能、编码难度外,对于技术选型而言,有一点也不容忽视,即平台。如果项目本身采用的是Java Web或 .Net Web架构,那么对于提供双平台支持的GcExcel(GcExcel for javaGcExcel for .NET)来说显然更加合适。

以上就是本篇的全部内容,结合本文的测试结果,对于批量处理、修改、导出Excel,以及服务端批量打印和生成PDF文档的需求, SpreadJS + GcExcel都能提供更加优秀的性能和稳定性表现,可以放心将其作为未来项目的首选方案。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK