文档说明:easyexcel-rust 用户指南,涵盖定位、核心能力、格式边界、快速上手、配置和验证。
版本:V1.0.0 最后更新:2026-08-11
easyexcel-rust is a native Rust port of Alibaba EasyExcel 4.0.3. It delivers the Java EasyExcel programming model in idiomatic Rust: builders, typed row mapping, event listeners, converters, streaming reads, constant-memory writes, template filling, and write handlers.
The workspace also exposes reusable format-neutral foundations (easyexcel-model,
easyexcel-formula, easyexcel-io, XLS/XLSX/CSV backends and tabular conversion).
The independent xls-cli product owns its library-first command application layer;
this facade no longer depends on the full xls fork.
中文 README | Usage Guide | API reference | Architecture | xls-cli integration | Capability matrix
- Typed read/write with
#[derive(ExcelRow)]-- compile-time column mapping, 60+ built-in converters - Streaming reads (SAX-based) and constant-memory writes (SXSSF equivalent) for files with millions of rows
- Template filling -- scalar
{key}and list{.field}placeholders for XLSX and XLS - Full Java EasyExcel 4.0.3 parity -- 335 @Test methods mirrored, 88 golden tests, 152 behavioral parity tests
- Facade + foundation crates -- application code depends only on
easyexcel; CSV, I/O, model, formula, markdown, and format backends are reusable building blocks
easyexcel is the user-facing facade. It owns builders, listeners, converters, handlers, and the #[derive(ExcelRow)] macro. All format parsing, encoding, formula evaluation and I/O contracts live in one-way foundation crates (easyexcel-io, easyexcel-model, easyexcel-xls, easyexcel-xlsx, easyexcel-csv, easyexcel-formula, easyexcel-markdown, easyexcel-tabular).
User Code
│
▼
easyexcel (facade) ──► easyexcel-io (format detection, streaming traits, limits)
│ ──► easyexcel-model (Workbook / Sheet / Cell)
│ ──► easyexcel-xlsx (OOXML read/write/encrypt)
│ ──► easyexcel-xls (BIFF8 read/write/encrypt)
│ ──► easyexcel-csv (CSV codec)
│ ──► easyexcel-formula (AST, evaluator, recalc)
│ ──► easyexcel-markdown (GFM projection)
│ ──► easyexcel-tabular (HTML/JSON dispatch)
▼
Output: XLSX / XLS / CSV / Markdown
For a detailed view including web execution, framework adapters, and the xls-cli product, see Architecture.
| Feature | XLSX | XLS | CSV | Markdown |
|---|---|---|---|---|
| Read (typed rows) | ✅ stable | ✅ stable | ✅ stable | -- |
| Read (dynamic / no-model) | ✅ stable | ✅ stable | ✅ stable | -- |
| Read (event listener) | ✅ stable | ✅ stable | ✅ stable | -- |
| Read (password-protected) | ✅ stable | ✅ RC4 | -- | -- |
| Write (typed rows) | ✅ stable | ✅ BIFF8 stable | ✅ stable | -- |
| Write (with password) | ✅ Agile stable | ✅ RC4 stable | -- | -- |
| Write (constant memory / SXSSF) | ✅ stable | -- | -- | -- |
Template fill ({key}) |
✅ stable | ✅ LABEL stable | -- | -- |
Template fill (list {.}) |
✅ stable | ✅ stable | -- | -- |
| Merge cells | ✅ stable | ✅ stable | -- | -- |
| Column width | ✅ stable | ✅ stable | -- | -- |
| Row height | ✅ stable | ✅ stable | -- | -- |
| Styles (font / fill / alignment) | ✅ stable | ✅ basic | -- | -- |
| Comments / Notes | ✅ read+write | ✅ read-only | -- | -- |
| Hyperlinks | ✅ read+write | ✅ read-only | -- | -- |
| Images | ✅ read+write | ✅ write-only | -- | -- |
| Formulas | ✅ read+write | -- | -- | -- |
| Auto-filter | ✅ stable | -- | -- | -- |
| Export (XLS/XLSX/CSV to Markdown) | ✅ stable | ✅ stable | ✅ stable | -- |
| Import (Markdown to XLSX) | -- | -- | -- | ✅ stable |
| Content | Read | Modify | Round-Trip Preserve | Validation |
|---|---|---|---|---|
| Known text / cells / objects | ✅ | ✅ | ✅ | structural assertion |
| Styles and themes | ✅ | partial | partial | golden fixture comparison |
| Unknown extension nodes | passthrough | -- | ✅ | golden fixture |
| Macros, scripts, active content | reject | -- | -- | security test |
read -> writepreserves unmodified content for XLSX (ZIP entry preservation) and XLS (record-preserving template modification).- Markdown export is a semantic projection with a structured loss report, not a lossless round trip.
- Template fill preserves all non-target content including styles, merged cells, and non-target sheets.
- Edit operations use temporary file + atomic replace; the original is preserved on failure.
| Format | Read Engine | Write Engine |
|---|---|---|
| XLSX | Custom SAX parser (quick-xml) |
rust_xlsxwriter |
| XLS | calamine + BIFF record handlers |
Custom BIFF8 encoder |
| CSV | csv crate + encoding_rs |
csv crate |
| Encryption (XLSX) | office-crypto |
ms-offcrypto-writer (Agile) |
| Encryption (XLS) | Custom RC4 (md-5 + getrandom) |
Custom RC4 |
| ZIP (XLSX container) | zip crate |
zip crate |
| OLE (XLS container) | cfb crate |
cfb crate |
ODS support is intentionally outside the Java EasyExcel compatibility contract and can be added later as an opt-in extension.
[dependencies]
easyexcel = "0.1"use easyexcel::{EasyExcel, ExcelRow, PageReadListener};
#[derive(Debug, ExcelRow)]
struct User {
#[excel(name = "Name", index = 0)]
name: String,
#[excel(name = "Age", index = 1)]
age: Option<u32>,
}
fn main() -> easyexcel::Result<()> {
// Event-driven for large files
let listener = PageReadListener::new(1000, |rows, _ctx| {
println!("received {} rows", rows.len());
});
EasyExcel::read::<User, _>("users.xlsx", listener)
.sheet("Users")
.do_read()?;
// Synchronous for small datasets
let users: Vec<User> = EasyExcel::read_sync::<User>("users.xlsx")
.sheet("Users")
.do_read_sync()?;
Ok(())
}use easyexcel::{EasyExcel, ExcelRow};
#[derive(Debug, ExcelRow)]
#[excel(column_width = 18)]
struct User {
#[excel(name = "Name", column_width = 30)]
name: String,
#[excel(name = "Age")]
age: u32,
#[excel(name = "Birthday", format = "yyyy-MM-dd")]
birthday: chrono::NaiveDate,
}
fn main() -> easyexcel::Result<()> {
let users = vec![
User { name: "Alice".into(), age: 28, birthday: chrono::NaiveDate::from_ymd_opt(1996, 5, 20).unwrap() },
User { name: "Bob".into(), age: 32, birthday: chrono::NaiveDate::from_ymd_opt(1992, 3, 15).unwrap() },
];
EasyExcel::write::<User>("users.xlsx")
.sheet("Users")
.do_write(users)?;
Ok(())
}use easyexcel::{EasyExcel, TemplateData, FillWrapper, FillConfig};
// Scalar fill {key}
let data = TemplateData::new()
.with("name", "Alice")
.with("date", "2024-01-15");
EasyExcel::fill_template("template.xlsx", "output.xlsx", &data)?;
// List fill {.field}
let list = FillWrapper::new([
TemplateData::new().with("name", "Alice").with("score", 95),
TemplateData::new().with("name", "Bob").with("score", 88),
]);
EasyExcel::fill_template_list("template.xlsx", "output.xlsx", &list, FillConfig::default())?;Downstream projects only need the easyexcel dependency to access CSV, I/O,
and workbook model APIs through stable namespaces:
use easyexcel::csv::{CsvReadOptions, CsvWriteOptions};
use easyexcel::io::{Format, ResourceLimits};
use easyexcel::model::{Cell, Workbook};These are direct re-exports of the foundation crate types, with no wrapper or conversion overhead.
Use easyexcel::markdown, not an internal engine crate, to convert XLS/XLSX/CSV and GFM tables:
use easyexcel::markdown::{
MarkdownConversionMode, MarkdownFormulaPolicy, MarkdownMergePolicy,
};
use easyexcel::EasyExcel;
let report = EasyExcel::export_markdown("report.xlsx", "report.md")
.all_sheets()
.mode(MarkdownConversionMode::Auto)
.formula_policy(MarkdownFormulaPolicy::CachedValue)
.merge_policy(MarkdownMergePolicy::AnchorWithWarning)
.do_export()?;
for warning in report.warnings {
eprintln!("{:?}: {}", warning.code, warning.message);
}
EasyExcel::import_markdown("tables.md", "generated.xlsx")
.conservative_types()
.apply_header_style(true)
.do_import()?;The default AgentStable profile emits deterministic UTF-8 GFM tables. XLSX and CSV can use Event Mode; XLS, expression output, and merge policies requiring full workbook metadata use Workbook Mode. Markdown is a semantic projection with a structured loss report, not a lossless round trip.
| Java Annotation | Rust Attribute | Purpose |
|---|---|---|
@ExcelProperty |
#[excel(value/head, name, index, order, converter)] |
Column mapping and multi-level heads |
@ExcelIgnore |
#[excel(ignore)] |
Skip field |
@ExcelIgnoreUnannotated |
#[excel(ignore_unannotated)] |
Skip unannotated |
@DateTimeFormat |
#[excel(date_time_format = "...", use_1904_windowing = true)] |
Date format |
@NumberFormat |
#[excel(number_format = "...", rounding_mode = "HALF_UP")] |
Numeric format |
@ColumnWidth |
#[excel(column_width = N)] |
Column width |
@HeadRowHeight |
#[excel(head_row_height = N)] |
Header row height |
@ContentRowHeight |
#[excel(content_row_height = N)] |
Content row height |
@HeadStyle |
#[excel(head_style(...))] |
Header style |
@ContentStyle |
#[excel(content_style(...))] |
Content style |
@HeadFontStyle |
#[excel(head_font_style(...))] |
Header font |
@ContentFontStyle |
#[excel(content_font_style(...))] |
Content font |
@ContentLoopMerge |
#[excel(content_loop_merge(...))] |
Loop merge |
@OnceAbsoluteMerge |
#[excel(once_absolute_merge(...))] |
Absolute merge |
use easyexcel::{Result, WriteHandler, WriteSheetContext};
struct LoggingHandler;
impl WriteHandler for LoggingHandler {
fn order(&self) -> i32 { 100 }
fn after_sheet(&mut self, ctx: &WriteSheetContext) -> Result<()> {
println!("Sheet '{}' written", ctx.sheet_name());
Ok(())
}
}
EasyExcel::write::<User>("output.xlsx")
.register_write_handler(LoggingHandler)
.sheet("Sheet1")
.do_write(data)?;| Mode | Memory Complexity | Temp Space | Use Case | Limitation |
|---|---|---|---|---|
Full model (read_sync) |
O(document) |
low | random access, small files | high memory for large files |
Streaming read (read + listener) |
O(batch) |
low | bulk import of large files | no row backtracking |
| Constant-memory write (SXSSF) | O(window) |
medium | massive export (>1M rows) | cannot modify after write |
| Template fill | O(template) |
low | report generation | template must exist upfront |
- Batch size: configure via
PageReadListener::new(batch_size, ...). Default recommended: 1000 rows. - SXSSF window: XLSX constant-memory write uses a sliding window; rows beyond the window are flushed to temporary files.
- Password-protected files: decryption buffers the full encrypted payload before streaming; memory usage equals the encrypted file size.
- Files under ~10 MB:
read_syncfor simplicity. - Files over ~10 MB or unknown size:
readwithPageReadListenerfor bounded memory. - Need to process all rows at once:
read_syncreturnsVec<T>. - Need to process in batches:
PageReadListenerdelivers chunks ofbatch_sizerows.
| Symptom | Likely Cause | Resolution |
|---|---|---|
SheetNotFound error |
Sheet name mismatch or wrong index | Use .sheet("exact name") or .sheet_index(0) |
Format error on read |
Cell type mismatch with Rust field type | Use Option<T> for nullable fields; add a custom Converter |
| High memory on large XLSX | Using read_sync on a large file |
Switch to read with PageReadListener |
| Template fill missing values | Key mismatch between template and data | Verify template placeholders match TemplateData::with() keys exactly |
| CSV encoding issues | Non-UTF-8 source file | Use CsvReadOptions::charset() to specify encoding |
| Scenario | Java (historical) | Rust (macOS 100K) | Ratio |
|---|---|---|---|
| xlsx event read | 307K-343K rows/s | 618K rows/s | ~2x |
| xlsx stream write | ~105K rows/s (initial) | 277K rows/s | ~2.6x |
| xls event read | — | 70K rows/s | Rust-only optimization |
Honest notes on data sources:
- Java data: Alibaba EasyExcel 4.0.3 historical benchmark (307K-343K rows/s), recorded in
benchmarks/profiles/HOTSPOTS.md. These numbers were measured on a different machine and may not reflect current Java performance. - Rust data: macOS Apple Silicon 100K rows measured median (NIGHTLY_DRYRUN_REPORT.md, 2026-08-11).
- Different environments — a true A/B comparison requires a Linux release baseline (
benchmarks/baselines/release-ubuntu-x64.json). The numbers above are from different machines and should be interpreted as directional, not absolute. - All throughput numbers are medians of 3 measurements, not single-peak values.
xychart-beta
title "Rust vs Java Throughput (rows/s)"
x-axis ["xlsx-event-read", "xlsx-stream-write"]
y-axis "rows/s" 0 --> 700000
bar [307000, 105000]
bar [618000, 277000]
Chart legend: First bar group = Java (historical benchmark, 307K-343K range), Second bar group = Rust (macOS Apple Silicon 100K rows). Java has no historical xls-event-read data; Rust achieves 70K rows/s.
| Scenario | Cold (rows/s) | Steady (rows/s) |
|---|---|---|
| xlsx-stream-write | 277,133 | 243,219 |
| xlsx-event-read | 618,478 | 628,194 |
| xlsx-workbook-read | 558,460 | 576,070 |
| csv-stream-write | 279,913 | 291,230 |
| csv-event-read | 1,227,002 | 1,293,649 |
| xls-event-read | 70,379 | 74,651 |
Source: docs/superpowers/specs/2026-08-12-nightly-dryrun-report-design.md
Event read: 130K → 181K (CompiledExcelFormat) → 205K (integer fast path) → 618K (scratch reuse + typed dispatch + derive primitive)
Stream write: 105K → 257K (Handler Arc + Rc<RefCell> + capability fast path)
xls-event-read: 12K → 70K (LazySst deferred SST decode, 61.8x construction speedup)
pie title "xlsx-event-read Optimization Contribution (rows/s)"
"SAX streaming baseline" : 130
"CompiledExcelFormat (+51K)" : 51
"Integer fast path (+24K)" : 24
"scratch+dispatch+derive (+413K)" : 413
# Build the benchmark runner
cargo build --release -p easyexcel-benchmark-runner
# Run the full benchmark suite
cargo run --release -p easyexcel-benchmark-runner -- --spec benchmarks/spec/benchmark-suite-v1.json --output results.jsonl
# Compare with baseline
python3 benchmarks/scripts/compare_results.py results.jsonl \
--spec benchmarks/spec/benchmark-suite-v1.json \
--profile nightly \
--baseline benchmarks/baselines/nightly-ubuntu-x64.jsonFor detailed performance architecture (read/write path chains, memory model, and all 10 optimization techniques), see Architecture - Performance Architecture.
| Category | Count | Status |
|---|---|---|
| Java @Test methods mirrored | 335 | All pass |
| Golden tests (byte-level Java output comparison) | 88 | All pass |
| Parity tests (behavioral equivalence) | 152 | All pass |
| 1:1 method tests | 78 | All pass |
| Total workspace tests | 1315+ | All pass |
#[ignore] annotations |
0 | Eliminated |
| Crate | Purpose | Java Mirror |
|---|---|---|
easyexcel |
User-facing facade | EasyExcel / EasyExcelFactory |
easyexcel-derive |
#[derive(ExcelRow)] proc-macro |
Annotation processing |
easyexcel-model |
Format-neutral workbook and cell model | Core data model |
easyexcel-io |
Format detection, I/O contracts and resource limits | Read/write infrastructure |
easyexcel-csv |
CSV codec, charset and streaming writer | CSV backend |
easyexcel-xls |
BIFF8 parsing, encoding, encryption and formula tokens | XLS backend |
easyexcel-xlsx |
OOXML streaming, package handling and encryption | XLSX backend |
easyexcel-formula |
Formula AST, parser and evaluator | Formula engine |
easyexcel-markdown |
GFM parsing, streaming export, projection policy and loss reports | Markdown projection |
easyexcel-tabular |
Static HTML, JSON and generic text-format dispatch | Tabular interchange |
easyexcel-web |
Framework-neutral streaming import/export, limits, cancellation and error protocol | Web execution kernel |
Foundation crates are internal engine layers. Application code should depend only on easyexcel:
use easyexcel::csv::{CsvCharset, CsvReadOptions, CsvWriteOptions};
use easyexcel::io::{Format, ResourceLimits};
use easyexcel::model::{Cell, Workbook};
use easyexcel::markdown::{MarkdownConversionMode, MarkdownFormulaPolicy};
use easyexcel::xls;
use easyexcel::xlsx;easyexcel::{csv, io, markdown, model, formula, tabular, xls, xlsx} directly re-export the public engine types without creating a second model. The facade continues to own EasyExcel, builders, listeners, converters, handlers, and #[derive(ExcelRow)].
Markdown is a semantic projection with an explicit loss report, not a lossless Excel round trip. XLS uses Workbook Mode; XLSX and CSV also support real Event Mode. Rust users only need the facade:
let report = EasyExcel::export_markdown("report.xlsx", "report.md")
.mode(MarkdownConversionMode::Auto)
.formula_policy(MarkdownFormulaPolicy::CachedValue)
.do_export()?;
EasyExcel::import_markdown("report.md", "report.xlsx")
.conservative_types()
.do_import()?;Web services additionally depend on easyexcel-web and use ExcelImport<T>, ExcelRows<T>, ExcelExport<T>, ExcelWebPolicy, and an application-level ExcelWebRuntime. Framework crates provide only native extractor/responder adapters; shared buffering, limits, cancellation, cleanup, and error mapping stay in the web kernel.
Axum, Actix Web, Hyper, Poem, Rocket, Salvo, and Warp expose equivalent ExcelRequest<T> and ExcelResponse<T> semantics. Runnable integrations live under examples/<framework> and share the conformance suite in tests/easyexcel-web-conformance.
| Document | Description |
|---|---|
| Usage Guide | Detailed usage guide with examples |
| API Reference | Complete API parameter reference |
| Architecture | Crate layout, data flow, dependency direction |
| Migration Audit | Java-to-Rust test parity report |
| xls-cli Integration Plan | xls-cli product integration details |
| Capability Matrix | xls-cli runtime capability matrix |
Apache-2.0
文档版本:V1.0.0 创建日期:2026-08-11 最后更新:2026-08-11 文档状态:✅ 已评审