Github : https://github.com/shps951023/MiniExcel

简介

我尝试做一个.NET简单、高效、避免OOM的Excel工具

目前主流框架大多将资料全载入到记忆体方便操作,但这会导致记忆体消耗问题,MiniExcel 尝试以 Stream 角度写底层算法逻辑,能让原本1000多MB占用降低到几MB,避免记忆体不够情况。适合像是低规格 azure app service 或是读取大档案等情境。

特点

  • 低内存耗用,避免OOM(out of memoery)、频繁 Full GC 情况
  • 支持即时操作每行数据

  • 兼具搭配 LINQ 延迟查询特性,能办到低消耗、快速分页等复杂查询

    图片:与主流框架对比的消耗、效率差

  • 轻量,不依赖任何套件,DLL小于100KB
  • 简便操作的 Dapper API 风格

安装

请查看 from NuGet

更新日志

请查看 Release Notes

TODO

请查看 Project · todo

性能测试

Test1,000,000x10.xlsx 做基准与主流框架做性能测试,总共 1千万笔 "HelloWorld",文件大小 23 MB

Benchmarks 逻辑可以在 MiniExcel.Benchmarks 查看或是提交 PR,运行指令

dotnet run -p .\benchmarks\MiniExcel.Benchmarks\ -c Release -f netcoreapp3.1 -- -f * --join

最后一次运行结果 :

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
Job-ZYYABG : .NET Framework 4.8 (4.8.4341.0), X64 RyuJIT
IterationCount=3 LaunchCount=3 WarmupCount=3
Method 最大内存耗用 平均时间 Gen 0 Gen 1 Gen 2
'MiniExcel QueryFirst' 0.109 MB 726.4 us - - -
'ExcelDataReader QueryFirst' 15.24 MB 10,664,238.2 us 566000.0000 1000.0000 -
'MiniExcel Query' 17.3 MB 14,179,334.8 us 367000.0000 96000.0000 7000.0000
'ExcelDataReader Query' 17.3 MB 22,565,088.7 us 1210000.0000 2000.0000 -
'Epplus QueryFirst' 1,452 MB 18,198,015.4 us 535000.0000 132000.0000 9000.0000
'Epplus Query' 1,451 MB 23,647,471.1 us 1451000.0000 133000.0000 9000.0000
'OpenXmlSDK Query' 1,412 MB 52,003,270.1 us 978000.0000 353000.0000 11000.0000
'OpenXmlSDK QueryFirst' 1,413 MB 52,348,659.1 us 978000.0000 353000.0000 11000.0000
'ClosedXml QueryFirst' 2,158 MB 66,188,979.6 us 2156000.0000 575000.0000 9000.0000
'ClosedXml Query' 2,184 MB 191,434,126.6 us 2165000.0000 577000.0000 10000.0000
Method 最大内存耗用 平均时间 Gen 0 Gen 1 Gen 2
'MiniExcel Create Xlsx' 15 MB 11,531,819.8 us 1020000.0000 - -
'Epplus Create Xlsx' 1,204 MB 22,509,717.7 us 1370000.0000 60000.0000 30000.0000
'OpenXmlSdk Create Xlsx' 2,621 MB 42,473,998.9 us 1370000.0000 460000.0000 50000.0000
'ClosedXml Create Xlsx' 7,141 MB 140,939,928.6 us 5520000.0000 1500000.0000 80000.0000

Query 查询 Excel 返回强型别 IEnumerable 数据 [Try it]

推荐使用 Stream.Query 效率会相对较好。

public class UserAccount
{
public Guid ID { get; set; }
public string Name { get; set; }
public DateTime BoD { get; set; }
public int Age { get; set; }
public bool VIP { get; set; }
public decimal Points { get; set; }
} var rows = MiniExcel.Query<UserAccount>(path); // or using (var stream = File.OpenRead(path))
var rows = stream.Query<UserAccount>();

Query 查询 Excel 返回Dynamic IEnumerable 数据 [Try it]

  • Key 系统预设为 A,B,C,D...Z
MiniExcel 1
Github 2
var rows = MiniExcel.Query(path).ToList();

// or
using (var stream = File.OpenRead(path))
{
var rows = stream.Query().ToList(); Assert.Equal("MiniExcel", rows[0].A);
Assert.Equal(1, rows[0].B);
Assert.Equal("Github", rows[1].A);
Assert.Equal(2, rows[1].B);
}

查询数据以第一行数据当Key [Try it]

note : 同名以右边数据为准

Input Excel :

Column1 Column2
MiniExcel 1
Github 2
var rows = MiniExcel.Query(useHeaderRow:true).ToList();

// or

using (var stream = File.OpenRead(path))
{
var rows = stream.Query(useHeaderRow:true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1);
Assert.Equal(1, rows[0].Column2);
Assert.Equal("Github", rows[1].Column1);
Assert.Equal(2, rows[1].Column2);
}

Query 查询支援延迟加载(Deferred Execution),能配合LINQ First/Take/Skip办到低消耗、高效率复杂查询

Query First

var row = MiniExcel.Query(path).First();
Assert.Equal("HelloWorld", row.A); // or using (var stream = File.OpenRead(path))
{
var row = stream.Query().First();
Assert.Equal("HelloWorld", row.A);
}

建立 Excel 文件 [Try it]

  1. 必须是 non-abstract 类别有 public parameterless constructor
  2. MiniExcel SaveAs 支援 IEnumerable参数``延迟查询,除非必要请不要使用 ToList 等方法读取全部数据到内存

图片 : 是否呼叫 ToList 的内存差别

Anonymous or strongly type:

var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
MiniExcel.SaveAs(path, new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
});

Datatable:

var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
var table = new DataTable();
{
table.Columns.Add("Column1", typeof(string));
table.Columns.Add("Column2", typeof(decimal));
table.Rows.Add("MiniExcel", 1);
table.Rows.Add("Github", 2);
} MiniExcel.SaveAs(path, table);

Dapper:

using (var connection = GetConnection(connectionString))
{
var rows = connection.Query(@"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2");
MiniExcel.SaveAs(path, rows);
}

IEnumerable<IDictionary<string, object>>

var values = new List<Dictionary<string, object>>()
{
new Dictionary<string,object>{{ "Column1", "MiniExcel" }, { "Column2", 1 } },
new Dictionary<string,object>{{ "Column1", "Github" }, { "Column2", 2 } }
};
MiniExcel.SaveAs(path, values);

output :

Column1 Column2
MiniExcel 1
Github 2

SaveAs 支援 Stream [Try it]

using (var stream = File.Create(path))
{
stream.SaveAs(values);
}

Excel Column Name/Ignore Attribute

e.g

input excel :

Test1 Test2 Test3 Test4 Test5 Test6 Column1 Column2
Test1 Test2 Test3 Test4 Test5 Test6 Column1 Column2
public class ExcelAttributeDemo
{
[ExcelColumnName("Column1")]
public string Test1 { get; set; }
[ExcelColumnName("Column2")]
public string Test2 { get; set; }
[ExcelIgnore]
public string Test3 { get; set; }
public string Test4 { get; set; }
public string Test5 { get; }
public string Test6 { get; private set; }
} var rows = MiniExcel.Query<ExcelAttributeDemo>(path).ToList();
Assert.Equal("Column1", rows[0].Test1);
Assert.Equal("Column2", rows[0].Test2);
Assert.Null(rows[0].Test3);
Assert.Equal("Test4", rows[0].Test4);
Assert.Null(rows[0].Test5);
Assert.Null(rows[0].Test6);

例子 : SQLite & Dapper 读取大数据新增到数据库

note : 请不要呼叫 call ToList/ToArray 等方法,这会将所有数据读到内存内

using (var connection = new SQLiteConnection(connectionString))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
using (var stream = File.OpenRead(path))
{
var rows = stream.Query();
foreach (var row in rows)
connection.Execute("insert into T (A,B) values (@A,@B)", new { row.A, row.B }, transaction: transaction);
transaction.Commit();
}
}

效能:

例子 : ASP.NET Core 3.1 or MVC 5 下载 Excel Xlsx API Demo

public class ExcelController : Controller
{
public IActionResult Download()
{
var values = new[] {
new { Column1 = "MiniExcel", Column2 = 1 },
new { Column1 = "Github", Column2 = 2}
};
var stream = new MemoryStream();
stream.SaveAs(values);
return File(stream,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"demo.xlsx");
}
}

Excel 类别自动判断

MiniExcel 预设会根据扩展名或是 Stream 类别判断是 xlsx 还是 csv,但会有失准时候,请自行指定。

stream.SaveAs(excelType:ExcelType.CSV);
//or
stream.SaveAs(excelType:ExcelType.XLSX);
//or
stream.Query(excelType:ExcelType.CSV);
//or
stream.Query(excelType:ExcelType.XLSX);

Dynamic Query 转换 IDictionary<string,object> 数据

foreach(IDictionary<string,object> row = MiniExcel.Query(path))
{
//..
}

局限与警告

  • 目前不支援 xls (97-2003) 或是加密文件。
  • 不支援样式、字体、宽度等修改,因为 MiniExcel 概念是只专注于值数据,借此降低内存消耗跟提升效率。

参考

尝试做一个.NET简单、高效、避免OOM的Excel工具的更多相关文章

  1. ConstraintLayout+radioGroup做一个tab.简单好用。

    主页tab是必须会有的,各种实现也很多.各有千秋.但目标都是简单.可控.今天用ConstraintLayout+radioGroup做一个tab.简单性可控性都还可以.本文目的把ConstraintL ...

  2. 尝试做一个.NET模板填充导出Excel工具

    园友好,最近晚辈延续上篇后尝试进阶做成Excel模板填充数据生成工具 MiniExcel Template. 主要特点 同样以Stream流.延迟查询避免全部数据载入内存情况,做到1GB内存降低到只需 ...

  3. 一听就懂:用Python做一个超简单的小游戏

    写它会用到 while 循环random 模块if 语句输入输出函数

  4. Watir: 右键点击实例(某些如果应用AutoIt来做会更加简单高效)

    require 'watir' module Watir class Element def top_edge assert_exists assert_enabled ole_object.getB ...

  5. [初学Python]编写一个最简单判断SQL注入的检测工具

    0x01 背景 15年那会,几乎可以说是渗透最火的一年,各种教程各种文章,本人也是有幸在那几年学到了一些皮毛,中间因学业问题将其荒废至今.当初最早学的便是,and 1=1 和 and 1=2 这最简单 ...

  6. Android高效的应用程序开发工具集1---ant构建一个简单的Android工程

    在java编译那些事通过提到ant编译Java工程,如今扩大到用它来构建Android目,事实上道理是相通的.变化的仅仅是使用的形式.ant构建相比IDE的优点是多个子项目使用自己定义jar包时,an ...

  7. 程序猿修仙之路--数据结构之你是否真的懂数组? c#socket TCP同步网络通信 用lambda表达式树替代反射 ASP.NET MVC如何做一个简单的非法登录拦截

    程序猿修仙之路--数据结构之你是否真的懂数组?   数据结构 但凡IT江湖侠士,算法与数据结构为必修之课.早有前辈已经明确指出:程序=算法+数据结构  .要想在之后的江湖历练中通关,数据结构必不可少. ...

  8. MUI框架-05-用MUI做一个简单App

    MUI框架-05-用MUI做一个简单App MUI 是一个前端框架,前端框架就像 Bootstrap,EasyUI,Vue ,为了做 app 呢,就有了更加高效的 MUI,我觉得前端框架有很多,也没有 ...

  9. 第四章 .net core做一个简单的登录

    项目目标部署环境:CentOS 7+ 项目技术点:.netcore2.0 + Autofac +webAPI + NHibernate5.1 + mysql5.6 + nginx 开源地址:https ...

随机推荐

  1. Koa & WebSocket inAction

    Koa & WebSocket inAction node.js https://koajs.com/ ping / pong socket.io client send 2 as ping ...

  2. SEO & JSON-LD & structured-data

    SEO & JSON-LD & structured-data script type="application/ld+json" script type=&quo ...

  3. Deno 1.0 & Node.js

    Deno 1.0 & Node.js A secure runtime for JavaScript and TypeScript. https://deno.land/v1 https:// ...

  4. 聊聊CacheLine

    本文转载自聊聊CacheLine 导语 文章聊聊缓存一致性协议中我们提到过,缓存里面最小的单位是缓存行/缓存条目,但是缓存中的具体存储结构是什么样的,缓存行中有存放的是什么?在缓存中是如何寻找指定是还 ...

  5. 死磕Spring之IoC篇 - 调试环境的搭建

    该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读 Spring 版本:5.1. ...

  6. hadoop环境搭建:高可用

    目录 1.硬件配置 2.软件版本 3.准备工作 3.1.配置网络环境 3.2.安装JDK 3.3.安装ZOOKEEPER 4.安装Hadoop 5.启动 6.问题 7.配置文件 1.硬件配置 采用3台 ...

  7. spring-ioc心得

    1.创建spring容器,严格的来说就是创建ClassPathXmlApplicationContext对象, 该对象属于ApplicationContext类型(是一个接口)该接口下有很多实现类, ...

  8. 小公举comm,快速比较两个排序文件

    前言 我们经常会有需求比较一个文件里的内容是否在另一个文件存在.假如我有一份监控列表的IP写入在了file1,我所有的机器IP写入在了file2,我要找出还有哪些机器没有在监控列表.以前的做法是写个两 ...

  9. 授权认证登录之 Cookie、Session、Token、JWT 详解

    一.先了解几个基础概念 什么是认证(Authentication) 通俗地讲就是验证当前用户的身份. 互联网中的认证: 用户名密码登录 邮箱发送登录链接 手机号接收验证码 只要你能收到邮箱/验证码,就 ...

  10. 小心你的个人信息——GitHub 热点速览 v.21.09

    作者:HelloGitHub-小鱼干 浏览过必有痕迹,有什么可以抹去社交痕迹的方法呢?social-analyzer 是一个可在 350+ 网站分析特定用户资料的工具,你可以用它来"人肉&q ...