当前位置: 首页 > news >正文

利用zip格式文件的更新功能来对xlsx文件做更改

前文说到,市面上同时对xlsx文件读写的第三方库很少见,即使有,也是将全部的内容读入内存,修改后全部写入,导致对多sheet大文件单个sheet少量的修改, 保存文件也需要很长的时间。

而xlsx文件本质上是ZIP 文件,有可能利用ZIP 文件的特点,减少多余的编码工作量,从而提升保存的速度。

ZIP 文件格式基本结构

ZIP 文件是一种常见的归档文件格式,其基本结构如下:

1. 核心组成部分

[Local File Header 1]
[File Data 1]
[Local File Header 2]
[File Data 2]
...
[Central Directory]
[End of Central Directory Record]

2. 关键结构说明

  • Local File Header:每个文件的本地头,包含文件名、压缩方法等元数据

    • 签名:0x04034b50 (PK\x03\x04)
    • 结构:30字节固定部分 + 文件名 + 额外字段
  • File Data:实际压缩后的文件内容

  • Central Directory:中央目录,包含所有文件的索引信息

    • 签名:0x02014b50 (PK\x01\x02)
    • 包含所有文件的完整路径、压缩信息、偏移量等
  • End of Central Directory Record:目录结束标记

    • 签名:0x06054b50 (PK\x05\x06)
    • 包含中央目录的位置和文件总数

注意到zip文件中的每个被压缩文件都有一个文件的元数据以及压缩后的数据,对于未修改的文件,这个部分是不变的,而重新写入的时候,这部分无需重新压缩, 只要把旧文件中不变的文件元数据以及压缩后的数据整体写入新文件,再压缩变更的部分,添加这部分以及中央目录即可。
实验也证明了这点,zip文件的更新比原始压缩快很多,不管是更新一个文件,删除一个文件,添加一个文件,均如此。如下所示

time zip -3 -r wps-3 x/*adding: x/[Content_Types].xml (deflated 71%)adding: x/_rels/ (stored 0%)adding: x/_rels/.rels (deflated 66%)
...adding: x/xl/worksheets/sheet1.xml (deflated 70%)adding: x/xl/_rels/ (stored 0%)adding: x/xl/_rels/workbook.xml.rels (deflated 61%)real    0m7.750s
user    0m5.491s
sys     0m0.210s
time zip -3 -u wps-3 x/[Content_Types].xml
updating: x/[Content_Types].xml (deflated 71%)real    0m3.003s
user    0m0.045s
sys     0m0.163s
root@DESKTOP-59T6U68:/mnt/c/d/10# time zip -3 -d wps-3 x/[Content_Types].xml
deleting: x/[Content_Types].xmlreal    0m1.594s
user    0m0.022s
sys     0m0.187s
time zip -3 -u wps-3 x/[Content_Types].xmladding: x/[Content_Types].xml (deflated 71%)real    0m2.943s
user    0m0.045s
sys     0m0.161s

再来观察xlsx文件增加一个sheet, 受影响的后台文件是哪些,
用两个简单的例子来说明,示例文件onesheet.xlsx是单sheet, 示例文件twosheet.xlsx是两个sheet,它们的xml文件的清单如下:

unzip -l onesheet.xlsx
Archive:  onesheet.xlsxLength      Date    Time    Name
---------  ---------- -----   ----1530  1980-01-01 00:00   xl/worksheets/sheet1.xml823  2025-08-23 09:19   [Content_Types].xml298  2025-08-23 09:19   _rels/.rels700  2025-08-23 09:19   xl/workbook.xml1247  2025-08-23 09:19   xl/styles.xml567  2025-08-23 09:19   xl/_rels/workbook.xml.rels421  1980-01-01 00:00   xl/sharedStrings.xml
---------                     -------5586                     7 files
unzip -l twosheet.xlsx
Archive:  twosheet.xlsxLength      Date    Time    Name
---------  ---------- -----   ----1530  1980-01-01 00:00   xl/worksheets/sheet1.xml671  1980-01-01 00:00   xl/worksheets/sheet2.xml959  2025-08-23 09:19   [Content_Types].xml298  2025-08-23 09:19   _rels/.rels767  2025-08-23 09:19   xl/workbook.xml1247  2025-08-23 09:19   xl/styles.xml708  2025-08-23 09:19   xl/_rels/workbook.xml.rels458  1980-01-01 00:00   xl/sharedStrings.xml
---------                     -------6638                     8 files

可见,除了新增的xl/worksheets/sheet2.xml, 其余7个文件中, xl/worksheets/sheet1.xml、_rels/.rels、xl/styles.xml这三个文件大小未变,用cmp比较以确认内容。

unzip onesheet.xlsx -d 1shunzip twosheet.xlsx -d 2shcmp -l 1sh/_rels/.rels 2sh/_rels/.rels
cmp -l 1sh/xl/styles.xml 2sh/xl/styles.xml 
cmp 1sh/xl/worksheets/sheet1.xml 2sh/xl/worksheets/sheet1.xml
1sh/xl/worksheets/sheet1.xml 2sh/xl/worksheets/sheet1.xml differ: byte 1046, line 1
cmp -l 1sh/xl/worksheets/sheet1.xml 2sh/xl/worksheets/sheet1.xml
1046  64  63
1047  61  70
1048  66  71
1049  64  67
1050  63  67
1052  60  66
1053  63  66

结果后两个确实一致,因此它们无需重新压缩。而sheet1.xml不同,经核对,这是由于产生数据用了datetime.datetime.today()函数,这个函数并不是字面上的返回日期,而是返回带毫秒精度的日期和时间,

>>> import datetime
>>> print(datetime.datetime.today())
2025-08-23 10:06:42.466294>>> print(datetime.datetime.today())
2025-08-23 10:07:36.159872>>> print(datetime.datetime.strptime('2000-10-10','%Y-%m-%d'))
2000-10-10 00:00:00

将它改成常数后,两个xlsx文件中的两个sheet1.xml文件就完全一致了。

然后,重新分别解压缩,我们用2sh目录下的文件去更新onesheet.xlsx。

zip -3 -u -r ../onesheet.xlsx * -x xl/worksheets/sheet1.xml _rels/.rels xl/styles.xml
updating: [Content_Types].xml (deflated 69%)
updating: xl/workbook.xml (deflated 44%)
updating: xl/_rels/workbook.xml.rels (deflated 67%)adding: _rels/ (stored 0%)adding: xl/ (stored 0%)adding: xl/worksheets/ (stored 0%)adding: xl/worksheets/sheet2.xml (deflated 43%)adding: xl/_rels/ (stored 0%)unzip -l ../onesheet.xlsx
Archive:  ../onesheet.xlsxLength      Date    Time    Name
---------  ---------- -----   ----1520  1980-01-01 00:00   xl/worksheets/sheet1.xml959  2025-08-23 10:19   [Content_Types].xml298  2025-08-23 10:19   _rels/.rels767  2025-08-23 10:19   xl/workbook.xml1247  2025-08-23 10:19   xl/styles.xml708  2025-08-23 10:19   xl/_rels/workbook.xml.rels421  1980-01-01 00:00   xl/sharedStrings.xml0  2025-08-23 10:19   _rels/0  2025-08-23 10:19   xl/0  2025-08-23 10:19   xl/worksheets/671  1980-01-01 00:00   xl/worksheets/sheet2.xml0  2025-08-23 10:19   xl/_rels/
---------                     -------6591                     12 files

这个命令把空目录也当作文件添加进去了,需要排除,方法是添加-D选项,如下所示:

zip -3 -u -D -r ../onesheet.xlsx * -x xl/worksheets/sheet1.xml _rels/.rels xl/styles.xml
updating: [Content_Types].xml (deflated 69%)
updating: xl/workbook.xml (deflated 44%)
updating: xl/_rels/workbook.xml.rels (deflated 67%)adding: xl/worksheets/sheet2.xml (deflated 43%)
unzip -l ../onesheet.xlsx
Archive:  ../onesheet.xlsxLength      Date    Time    Name
---------  ---------- -----   ----1520  1980-01-01 00:00   xl/worksheets/sheet1.xml959  2025-08-23 10:19   [Content_Types].xml298  2025-08-23 10:19   _rels/.rels767  2025-08-23 10:19   xl/workbook.xml1247  2025-08-23 10:19   xl/styles.xml708  2025-08-23 10:19   xl/_rels/workbook.xml.rels421  1980-01-01 00:00   xl/sharedStrings.xml671  1980-01-01 00:00   xl/worksheets/sheet2.xml
---------                     -------6591                     8 filesunzip -l ../twosheet.xlsx
Archive:  ../twosheet.xlsxLength      Date    Time    Name
---------  ---------- -----   ----1520  1980-01-01 00:00   xl/worksheets/sheet1.xml671  1980-01-01 00:00   xl/worksheets/sheet2.xml959  2025-08-23 10:19   [Content_Types].xml298  2025-08-23 10:19   _rels/.rels767  2025-08-23 10:19   xl/workbook.xml1247  2025-08-23 10:19   xl/styles.xml708  2025-08-23 10:19   xl/_rels/workbook.xml.rels458  1980-01-01 00:00   xl/sharedStrings.xml
---------                     -------6628                     8 files

除了xl/sharedStrings.xml,都已经成功更新了。由于共享字符串不正确,sheet2无法读取自己的字符串,显示空白。
这个文件无法更新的原因在于,它的修改日期不正常,而xl/styles.xml是正常的。

stat xl/sharedStrings.xmlFile: xl/sharedStrings.xmlSize: 458             Blocks: 0          IO Block: 4096   regular file
Device: 52h/82d Inode: 2814749767192658  Links: 1
Access: (0777/-rwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2025-08-23 10:54:46.357751400 +0800
Modify: 1980-01-01 00:00:00.000000000 +0800
Change: 2025-08-23 10:19:43.671838700 +0800Birth: -
stat xl/styles.xmlFile: xl/styles.xmlSize: 1247            Blocks: 8          IO Block: 4096   regular file
Device: 52h/82d Inode: 9570149208233981  Links: 1
Access: (0777/-rwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2025-08-23 10:19:12.000000000 +0800
Modify: 2025-08-23 10:19:12.000000000 +0800
Change: 2025-08-23 10:19:43.649838700 +0800Birth: -

用touch命令修改文件日期后,就可以更新了

touch 2sh/xl/sharedStrings.xml
stat 2sh/xl/sharedStrings.xmlFile: 2sh/xl/sharedStrings.xmlSize: 458             Blocks: 0          IO Block: 4096   regular file
Device: 52h/82d Inode: 5910974510945701  Links: 1
Access: (0777/-rwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2025-08-23 11:32:13.877740100 +0800
Modify: 2025-08-23 11:32:13.877740100 +0800
Change: 2025-08-23 11:32:13.877740100 +0800Birth: -
cd 2sh
zip -3 -u -D -r ../onesheet.xlsx * -x xl/worksheets/sheet1.xml _rels/.rels xl/styles.xml
updating: xl/sharedStrings.xml (deflated 50%)

sheet2显示正确了。
在实际操作中,新的xl/sharedStrings.xml 是程序动态生成,而不是从xlsx中解压出来的,应该不存在日期不对的问题。

http://www.dtcms.com/a/347325.html

相关文章:

  • Claude Code赋能企业级开发:外卖平台核心系统的智能化重构
  • LDP标签分发协议
  • 疯狂星期四文案网第48天运营日记
  • 深度学习中主流激活函数的数学原理与PyTorch实现综述
  • Spring AI开发指导-对话模型
  • WiFi有网络但是电脑连不上网是怎么回事?该怎么解决?
  • TDengine IDMP 应用场景:工业锅炉监控
  • 【前端面试题✨】HTML 篇(一)
  • 二叉树的经典算法与应用
  • MERGE 语句在 Delta Lake 中的原子更新原理
  • C++ + Boost + MySQL 项目完整教程
  • Python reduce / map / filter 函数区别
  • Spring Boot Redis 入门
  • 注意力机制中除以Dk的方差归一化
  • 博客系统接口自动化练习
  • (nice!!!)(LeetCode 面试经典 150 题) 173. 二叉搜索树迭代器 (栈)
  • portswigger labs XXE漏洞利用实战
  • 一次转向:从 当前讨论到 拼PIN语言的拼块语言理论体系
  • 嵌入式软件/硬件工程师面试题集
  • 从观众席到股东席,何猷君成NBA凯尔特人新Co-owner
  • 网址账号正确,密码错误返回的状态码是多少
  • Java基础面试题(04)—Java(Java中String StringBuffer 和 StringBuilder的区别)
  • 山西某焦化厂炼焦区电气维护系统无线传输解决方案实施案例
  • Mangio RVC Fork 本地部署(Cuda12.9)
  • 蓝牙aoa仓库管理系统功能介绍
  • 有哪些Spring Boot微服务架构成功落地的案例?
  • GitHub发布革命性工具:GitHub Spark,用自然语言打造全栈智能应用
  • yolo命令行-训练篇(三)
  • Android安卓学习日志1 聊一聊安卓的历史和笔者的想法
  • 微服务统一入口——Gateway