├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── Dorae132 │ └── easyutil │ └── easyexcel │ ├── ExcelProperties.java │ ├── ExcelUtils.java │ ├── common │ ├── EasyExcelException.java │ └── Pair.java │ ├── export │ ├── DefaultWorkbookProcessor.java │ ├── ExcelCol.java │ ├── FillSheetModeEnums.java │ ├── IDataSupplier.java │ ├── IFillSheet.java │ └── IWorkbookProcessor.java │ └── read │ ├── ConsumeRowThread.java │ ├── DefaultReadDoneCallBackProcessor.java │ ├── ExcelVersionEnums.java │ ├── IReadDoneCallBack.java │ ├── IReadDoneCallBackProcessor.java │ ├── IRowConsumer.java │ └── event │ ├── IHandlerContext.java │ ├── excel03 │ ├── Default03RecordHandlerContext.java │ ├── IRecordHandlerContext.java │ ├── XlsListener.java │ └── handler │ │ ├── Abstract03RecordHandler.java │ │ ├── BlankRecordHandler.java │ │ ├── BoundSheetRecordHandler.java │ │ ├── EofRecordHandler.java │ │ ├── NumberRecordHandler.java │ │ ├── RowEndRecordHandler.java │ │ ├── SSTRecordHandler.java │ │ ├── StringRecordHandler.java │ │ └── TailRecordHandler.java │ └── excel07 │ ├── Default07RecordHandlerContext.java │ └── XlsxHandler.java └── test └── java └── com └── github └── Dorae132 └── easyutil └── easyexcel └── UtilsTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .settings/ 3 | .classpath 4 | .project -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 你的ExcelUtil简单、高效、易扩展吗 2 | > Author: Dorae 3 | > Date: 2018年10月23日12:30:15 4 | > 转载请注明出处 5 | 6 | ---- 7 | 8 | ## 一、背景 9 | 10 | 最近在看和Excel导出相关的代码,但是: 11 | 12 | 1. 大部分项目中的excelutil工具类会存在安全问题,而且无法扩展; 13 | 2. 一些开源的Excel工具太过重量级,并且不不能完全适合业务定制; 14 | 3. 于是乎产生了**easyExcel**(巧合,和阿里的easeExcel重名了😀)。 15 | 16 | #### HSS、XSS 17 | 18 | 如果数据量较小的话,这种方式并不会存在问题,但是当数据量比较大时,存在非常严重的问题: 19 | 20 | 1. OOM(因为数据并没有被即使的写入磁盘); 21 | 2. 耗时(串行化); 22 | 3. HSS对单sheet的数据量限制(65535),XSS为100万+。 23 | 24 | 25 | HSSFWorkbook workbook = new HSSFWorkbook(); 26 | HSSFSheet sheet = workbook.createSheet(sheetName); 27 | // 创建表头 28 | ... 29 | // 写入数据 30 | for (...) { 31 | HSSFRow textRow = sheet.createRow(rowIndex); 32 | for (...) { 33 | HSSFCell textcell = textRow.createCell(colIndex); 34 | textcell.setCellValue(value); 35 | } 36 | } 37 | 38 | #### SXSS 39 | 40 | 为了改善上出方案的问题,SXSS采用了缓冲的方式,即按照某种策略定期刷盘。从而解决了OOM与耗时太长的问题。使用姿势: 41 | 42 | InputStream inputStream = new FileInputStream(file); 43 | XSSFWorkbook xssfWorkbook = new XSSFWorkbook(inputStream); 44 | SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook,properties.getRowAccessWindowsize()); 45 | Sheet sheet = sxssfWorkbook.getSheet(properties.getSheetName()); 46 | if (sheet == null) { 47 | sheet = sxssfWorkbook.createSheet(properties.getSheetName()); 48 | } 49 | // 创建表头 50 | ... 51 | // 写入数据 52 | for (...) { 53 | Row row = sheet.createRow(row); 54 | row.setValue(value); 55 | } 56 | FileOutputStream outputStream = new FileOutputStream(file); 57 | sxssfWorkbook.write(outputStream); 58 | 59 | ## 二、EasyUtil 60 | 61 | 虽然SXSS解决了OMM与耗时的问题,但是使用起来不太方便,会造成很多重复代码,于是产生了**EasyUtil**,在介绍之前我们先思考几个问题: 62 | 63 | 1. excel操作有哪些公用逻辑可以抽离; 64 | 2. 业务需求是什么,有哪些逻辑可以抽离; 65 | 3. 如何开放较好的扩展点。 66 | 67 | #### 架构 68 | 69 | 按照上述的几个问题,EasyUtil-1.0的架构如图1-1所示,其中绿色部分为扩展点。 70 | 71 | 1. IFillSheet为填充excel的接口,可以扩展自定义格式,目前在FillSheetModeEnums中提供了两种(建议使用APPENDMODE); 72 | 2. AbstractDataSupplier为需要根据业务实现的数据获取接口,类似于stream; 73 | 3. IExcelProcesor生成的file的后处理接口,比如可以将文件上传到某个公共空间。 74 | 75 |
76 | 77 |
78 |
79 | 图 1-1 80 |
81 | 82 | #### 使用姿势 83 | 84 | ##### git 85 | 86 | [戳这里](https://github.com/Dorae132/easyexcel) 87 | 88 | ##### pom 89 | 90 | com.github.Dorae132 91 | easyutil.easyexcel 92 | 1.2.0 93 | 94 | ##### test 95 | 96 | static class TestValue { 97 | @ExcelCol(title = "姓名") 98 | private String name; 99 | @ExcelCol(title = "年龄", order = 1) 100 | private String age; 101 | @ExcelCol(title = "学校", order = 3) 102 | private String school; 103 | @ExcelCol(title = "年级", order = 2) 104 | private String clazz; 105 | 106 | public TestValue(String name, String age, String school, String clazz) { 107 | super(); 108 | this.name = name; 109 | this.age = age; 110 | this.school = school; 111 | this.clazz = clazz; 112 | } 113 | } 114 | 115 | private List getData(int count) { 116 | List dataList = Lists.newArrayListWithCapacity(10000); 117 | for (int i = 0; i < count; i++) { 118 | dataList.add(new TestValue("张三" + i, "age: " + i, null, "clazz: " + i)); 119 | } 120 | return dataList; 121 | } 122 | 123 | @Test 124 | public void testAppend() throws Exception { 125 | List dataList = getData(10000); 126 | long start = System.currentTimeMillis(); 127 | ExcelProperties properties = ExcelProperties.produceAppendProperties("", 128 | "C:\\Users\\Dorae\\Desktop\\ttt\\", "append.xlsx", 0, TestValue.class, 0, null, new AbstractDataSupplier() { 129 | private int i = 0; 130 | 131 | @Override 132 | public Pair, Boolean> getDatas() { 133 | boolean hasNext = i < 10; 134 | i++; 135 | return Pair.of(dataList, hasNext); 136 | } 137 | }); 138 | File file = (File) ExcelUtils.excelExport(properties, FillSheetModeEnums.APPEND_MODE.getValue()); 139 | System.out.println("apendMode: " + (System.currentTimeMillis() - start)); 140 | } 141 | 142 | #### 效果 143 | 144 | 1. 10万行4列数据耗时3s左右(我的渣渣笔记本); 145 | 2. 对内存基本没消耗(当然需要合适的缓冲参数)。 146 | 147 | ## 三、To do 148 | 149 | 1. 导出excel和获取数据的接口并行化;(done, see the PARALLEL_APPEND_MODE) 150 | 2. 完善utils。 151 | 152 | 153 | 154 | # 读取篇 155 | 156 | # 高效读取百万级数据 157 | 158 | 接[上一篇](https://github.com/Dorae132/easyexcel/blob/master/README.md)介绍的高效写文件之后,最近抽时间研究了下Excel文件的读取。概括来讲,poi读取excel有两种方式:**用户模式和事件模式**。 159 | 160 | 然而很多业务场景中的读取Excel仍然采用用户模式,但是这种模式需要创建大量对象,对大文件的支持非常不友好,非常容易OOM。但是对于事件模式而言,往往需要自己实现listener,并且需要根据自己需要解析不同的event,所以用起来比较复杂。 161 | 162 | 基于此,EasyExcel封装了常用的Excel格式文档的事件解析,并且提供了接口供开发小哥**扩展定制化**,实现让你解析Excel不再费神的目的。 163 | 164 | Talk is cheap, show me the code. 165 | 166 | ### 使用姿势 167 | 168 | 169 | #### 普通姿势 170 | 171 | 看看下边的姿势,是不是觉得**只需要关心业务逻辑了**? 172 | 173 | ExcelUtils.excelRead(ExcelProperties.produceReadProperties("C:\\Users\\Dorae\\Desktop\\ttt\\", 174 | "append_0745704108fa42ffb656aef983229955.xlsx"), new IRowConsumer() { 175 | @Override 176 | public void consume(List row) { 177 | System.out.println(row); 178 | count.incrementAndGet(); 179 | try { 180 | TimeUnit.MICROSECONDS.sleep(100); 181 | } catch (InterruptedException e) { 182 | // TODO Auto-generated catch block 183 | e.printStackTrace(); 184 | } 185 | } 186 | }, new IReadDoneCallBack() { 187 | @Override 188 | public Void call() { 189 | System.out.println( 190 | "end, count: " + count.get() + "\ntime: " + (System.currentTimeMillis() - start)); 191 | return null; 192 | } 193 | }, 3, true); 194 | 195 | 196 | #### 定制姿势 197 | 198 | 什么?你想**定制context,添加handler**?请看下边!你只需要实现一个Abstract03RecordHandler然后regist到context(关注ExcelVersionEnums中的factory)就可以了。 199 | 200 | public static void excelRead(IHandlerContext context, IRowConsumer rowConsumer, IReadDoneCallBack callBack, 201 | int threadCount, boolean syncCurrentThread) throws Exception { 202 | // synchronized main thread 203 | CyclicBarrier cyclicBarrier = null; 204 | threadCount = syncCurrentThread ? ++threadCount : threadCount; 205 | if (callBack != null) { 206 | cyclicBarrier = new CyclicBarrier(threadCount, () -> { 207 | callBack.call(); 208 | }); 209 | } else { 210 | cyclicBarrier = new CyclicBarrier(threadCount); 211 | } 212 | for (int i = 0; i < threadCount; i++) { 213 | THREADPOOL.execute(new ConsumeRowThread(context, rowConsumer, cyclicBarrier)); 214 | } 215 | context.process(); 216 | if (syncCurrentThread) { 217 | cyclicBarrier.await(); 218 | } 219 | } 220 | 221 | ### 框架结构 222 | 223 | 如图,为整个EasyExcel的结构,其中(如果了解过设计模式,或者读过相关源码,应该会很容易理解): 224 | 225 | 1. 绿色为可扩展接口, 226 | 2. 上半部分为写文件部分,下办部分为读文件。 227 | 228 | ![图 1-1](http://images.cnblogs.com/cnblogs_com/Dorae/1325782/o_EasyExcel2.jpg) 229 | 230 | ### 总结 231 | 232 | 至此,EasyExcel的基本功能算是完善了,欢迎各路大神提Issue过来。🍗 233 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.github.Dorae132 7 | easyutil.easyexcel 8 | 1.2.0 9 | jar 10 | 11 | easyexcel 12 | excel utils 13 | 14 | 15 | 16 | dorae132 17 | https://github.com/Dorae132/easyexcel 18 | 19 | 20 | 21 | 22 | The Apache Software License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0.txt 24 | 25 | 26 | 27 | scm:git:https://github.com/Dorae132/easyexcel.git 28 | scm:git:https://github.com/Dorae132/easyexcel.git 29 | https://github.com/Dorae132/easyexcel 30 | 31 | https://github.com/Dorae132/easyexcel 32 | 33 | UTF-8 34 | UTF-8 35 | 1.8 36 | 1.5.2.RELEASE 37 | 1.8 38 | 1.8 39 | 1.8 40 | 3.17 41 | 42 | 43 | 44 | 45 | 46 | org.apache.poi 47 | poi 48 | ${poi.version} 49 | 50 | 51 | org.apache.poi 52 | poi-ooxml 53 | ${poi.version} 54 | 55 | 56 | org.apache.poi 57 | poi-ooxml-schemas 58 | ${poi.version} 59 | 60 | 61 | org.bluestemsoftware.open.maven.tparty 62 | xerces-impl 63 | 2.9.0 64 | 65 | 66 | xml-apis 67 | xml-apis 68 | 2.0.2 69 | 70 | 71 | org.apache.xmlbeans 72 | xmlbeans 73 | 2.6.0 74 | 75 | 76 | cglib 77 | cglib 78 | 2.2.2 79 | 80 | 81 | commons-collections 82 | commons-collections 83 | 3.2.1 84 | 85 | 86 | 87 | com.google.guava 88 | guava 89 | 18.0 90 | 91 | 92 | 93 | org.apache.commons 94 | commons-lang3 95 | 3.6 96 | 97 | 98 | 99 | junit 100 | junit 101 | 4.9 102 | test 103 | 104 | 105 | 106 | 107 | 108 | release 109 | 110 | 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-source-plugin 115 | 2.2.1 116 | 117 | 118 | package 119 | 120 | jar-no-fork 121 | 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-javadoc-plugin 129 | 2.9.1 130 | 131 | private 132 | true 133 | UTF-8 134 | UTF-8 135 | UTF-8 136 | -Xdoclint:none 137 | 138 | 139 | 140 | package 141 | 142 | jar 143 | 144 | 145 | 146 | 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-gpg-plugin 151 | 1.5 152 | 153 | 154 | verify 155 | 156 | sign 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | dorae_maven_central 166 | https://oss.sonatype.org/content/repositories/snapshots/ 167 | 168 | 169 | dorae_maven_central 170 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/ExcelProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.Arrays; 5 | import java.util.Comparator; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.UUID; 9 | import java.util.concurrent.ThreadPoolExecutor; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | import org.apache.commons.collections.CollectionUtils; 14 | import org.apache.commons.lang3.StringUtils; 15 | 16 | import com.github.Dorae132.easyutil.easyexcel.export.ExcelCol; 17 | import com.github.Dorae132.easyutil.easyexcel.export.IDataSupplier; 18 | import com.github.Dorae132.easyutil.easyexcel.export.IWorkbookProcessor; 19 | import com.github.Dorae132.easyutil.easyexcel.read.IReadDoneCallBackProcessor; 20 | import com.google.common.collect.Lists; 21 | import com.google.common.collect.Maps; 22 | 23 | /** 24 | * preperties if the excel which would to be extported 25 | * 26 | * @author Dorae 27 | * 28 | * @param the data type 29 | * @param the type of the return value that excepted 30 | */ 31 | public class ExcelProperties { 32 | 33 | // sheet名称和表头值 34 | private String sheetName = "sheet1"; 35 | 36 | // 表头List 37 | private List titles; 38 | 39 | // 对象集合 40 | private List dataList; 41 | 42 | // data类的field 43 | private List fields; 44 | 45 | // fieldName -> field 46 | private Map fieldNameMap; 47 | 48 | // 文件地址 49 | private String filePath = "./easyexcel/"; 50 | 51 | // 文件名 52 | private String fileName = new StringBuilder(UUID.randomUUID().toString()).append(".xlsx").toString().replace("-", ""); 53 | 54 | // 行偏移 55 | private int rowOffset = 0; 56 | 57 | // 列偏移 58 | private int colOffset = 0; 59 | 60 | // 缓冲 61 | private int rowAccessWindowsize = 100; 62 | 63 | // 读模式,线程等待最大时长/秒 64 | private int readThreadWaitTime = 300; 65 | 66 | // 并行读线程池,默认实现异步ExcelUtils 67 | private ThreadPoolExecutor readThreadPool = null; 68 | 69 | // 并行写线程池 70 | private ThreadPoolExecutor writeThreadPool = null; 71 | 72 | private IWorkbookProcessor wbProcessor; 73 | 74 | // 读模式结束回调 75 | private IReadDoneCallBackProcessor readDoneCallBackProcessor; 76 | 77 | private IDataSupplier dataSupplier; 78 | 79 | private Class dataClazz; 80 | 81 | public static ExcelProperties produceCommonProperties(String sheetName, List dataList, String filePath, 82 | String fileName, int colOffset, Class dataClazz, int rowAccessWindowsize, IWorkbookProcessor wbProcessor) throws Exception { 83 | return new ExcelProperties<>(sheetName, dataList, filePath, fileName, 0, colOffset, dataClazz, rowAccessWindowsize, wbProcessor, null); 84 | } 85 | 86 | public static ExcelProperties produceAppendProperties(String sheetName, String filePath, String fileName, 87 | int colOffset, Class dataClazz, int rowAccessWindowsize, IWorkbookProcessor wbProcessor, IDataSupplier dataSupplier) 88 | throws Exception { 89 | return new ExcelProperties<>(sheetName, null, filePath, fileName, 0, colOffset, dataClazz, rowAccessWindowsize, wbProcessor, dataSupplier); 90 | } 91 | 92 | public static ExcelProperties produceReadProperties(String filePath, String fileName) { 93 | ExcelProperties excelProperties = new ExcelProperties(); 94 | excelProperties.setFilePath(filePath); 95 | excelProperties.setFileName(fileName); 96 | return excelProperties; 97 | } 98 | /** 99 | * 100 | * @param sheetName 101 | * @param titleToFieldObjs 表头与字段的对应关系 102 | * @param dataList 数据集 103 | * @param filePath 文件路径(默认./liveunionExcels/) 104 | * @param fileName 文件名(默认随机) 105 | * @param excludeFields 要排除的字段名 106 | * @param rowOffset 行偏移 107 | * @param colOffset 列偏移 108 | * @param processor 结果processor 109 | * @param dataSupplier 数据源 110 | * @param dataClazz 资源类型 111 | * @param rowAccessWindowsize 缓冲大小 112 | * @throws Exception 113 | */ 114 | @SuppressWarnings("unchecked") 115 | private ExcelProperties(String sheetName, List dataList, String filePath, String fileName, int rowOffset, 116 | int colOffset, Class dataClazz, int rowAccessWindowsize, IWorkbookProcessor wbProcessor, 117 | IDataSupplier dataSupplier) throws Exception { 118 | super(); 119 | // 1.check 120 | if (CollectionUtils.isEmpty(dataList) && dataClazz == null) { 121 | // 保障能获取到资源类型 122 | throw new RuntimeException("dataList和资源数据类型不能同时为空!"); 123 | } 124 | // 2.common field 125 | if (StringUtils.isNotBlank(sheetName)) { 126 | this.sheetName = sheetName; 127 | } 128 | this.dataList = dataList; 129 | if (StringUtils.isNotBlank(filePath)) { 130 | this.filePath = filePath; 131 | } 132 | if (StringUtils.isNotBlank(fileName)) { 133 | int sufixIndex = fileName.lastIndexOf("."); 134 | sufixIndex = sufixIndex == -1 ? fileName.length() : sufixIndex; 135 | this.fileName = new StringBuilder(fileName) 136 | .insert(sufixIndex, "_" + UUID.randomUUID().toString().replace("-", "")).toString(); 137 | } 138 | this.rowOffset = rowOffset; 139 | this.colOffset = colOffset; 140 | this.wbProcessor = wbProcessor; 141 | this.dataSupplier = dataSupplier; 142 | this.rowAccessWindowsize = (rowAccessWindowsize > 100 ? rowAccessWindowsize : 100); 143 | // 3.special field 144 | this.dataClazz = dataClazz == null ? dataList.get(0).getClass() : dataClazz; 145 | Field[] declaredFields = this.dataClazz.getDeclaredFields(); 146 | Arrays.sort(declaredFields, new Comparator() { 147 | 148 | @Override 149 | public int compare(Field o1, Field o2) { 150 | ExcelCol o1Annotation = o1.getAnnotation(ExcelCol.class); 151 | ExcelCol o2Annotation = o2.getAnnotation(ExcelCol.class); 152 | if (o1Annotation == null && o2Annotation == null) { 153 | return 0; 154 | } else if (o1Annotation != null 155 | && o2Annotation != null) { 156 | if (o1Annotation.order() == o2Annotation.order()) { 157 | return 0; 158 | } else { 159 | return o1Annotation.order() > o2Annotation.order() ? 1 : -1; 160 | } 161 | } else if (o1Annotation != null 162 | && o2Annotation == null) { 163 | return 1; 164 | } else { 165 | return -1; 166 | } 167 | } 168 | }); 169 | this.fields = Lists.newArrayListWithCapacity(declaredFields.length); 170 | this.fieldNameMap = Maps.newHashMapWithExpectedSize(declaredFields.length); 171 | this.titles = Lists.newArrayListWithCapacity(declaredFields.length); 172 | for (Field declaredField : declaredFields) { 173 | ExcelCol excelCol = declaredField.getAnnotation(ExcelCol.class); 174 | if (excelCol == null) { 175 | continue; 176 | } 177 | this.fieldNameMap.put(declaredField.getName(), declaredField); 178 | titles.add(excelCol.title()); 179 | this.fields.add(declaredField); 180 | } 181 | this.fieldNameMap = Stream.of(declaredFields).collect(Collectors.toMap(Field::getName, e -> e)); 182 | } 183 | 184 | private ExcelProperties() { 185 | super(); 186 | // TODO Auto-generated constructor stub 187 | } 188 | 189 | public int getRowAccessWindowsize() { 190 | return rowAccessWindowsize; 191 | } 192 | 193 | public void setRowAccessWindowsize(int rowAccessWindowsize) { 194 | this.rowAccessWindowsize = rowAccessWindowsize; 195 | } 196 | 197 | public String getSheetName() { 198 | return sheetName; 199 | } 200 | 201 | public void setSheetName(String sheetName) { 202 | this.sheetName = sheetName; 203 | } 204 | 205 | public List getTitles() { 206 | return titles; 207 | } 208 | 209 | public void setTitles(List titles) { 210 | this.titles = titles; 211 | } 212 | 213 | public List getDataList() { 214 | return dataList; 215 | } 216 | 217 | public void setDataList(List dataList) { 218 | this.dataList = dataList; 219 | } 220 | 221 | public List getFields() { 222 | return fields; 223 | } 224 | 225 | public void setFields(List fields) { 226 | this.fields = fields; 227 | } 228 | 229 | public Map getFieldNameMap() { 230 | return fieldNameMap; 231 | } 232 | 233 | public void setFieldNameMap(Map fieldNameMap) { 234 | this.fieldNameMap = fieldNameMap; 235 | } 236 | 237 | public String getFilePath() { 238 | return filePath; 239 | } 240 | 241 | public void setFilePath(String filePath) { 242 | this.filePath = filePath; 243 | } 244 | 245 | public String getFileName() { 246 | return fileName; 247 | } 248 | 249 | public void setFileName(String fileName) { 250 | this.fileName = fileName; 251 | } 252 | 253 | public int getRowOffset() { 254 | return rowOffset; 255 | } 256 | 257 | public void setRowOffset(int rowOffset) { 258 | this.rowOffset = rowOffset; 259 | } 260 | 261 | public int getColOffset() { 262 | return colOffset; 263 | } 264 | 265 | public void setColOffset(int colOffset) { 266 | this.colOffset = colOffset; 267 | } 268 | 269 | public IWorkbookProcessor getWbProcessor() { 270 | return wbProcessor; 271 | } 272 | 273 | public void setWbProcessor(IWorkbookProcessor wbProcessor) { 274 | this.wbProcessor = wbProcessor; 275 | } 276 | 277 | public IDataSupplier getDataSupplier() { 278 | return dataSupplier; 279 | } 280 | 281 | public void setDataSupplier(IDataSupplier dataSupplier) { 282 | this.dataSupplier = dataSupplier; 283 | } 284 | 285 | public Class getDataClazz() { 286 | return dataClazz; 287 | } 288 | 289 | public void setDataClazz(Class dataClazz) { 290 | this.dataClazz = dataClazz; 291 | } 292 | 293 | public int getReadThreadWaitTime() { 294 | return readThreadWaitTime; 295 | } 296 | 297 | public void setReadThreadWaitTime(int readThreadWaitTime) { 298 | this.readThreadWaitTime = readThreadWaitTime; 299 | } 300 | 301 | public ThreadPoolExecutor getReadThreadPool() { 302 | return readThreadPool; 303 | } 304 | 305 | public void setReadThreadPool(ThreadPoolExecutor readThreadPool) { 306 | this.readThreadPool = readThreadPool; 307 | } 308 | 309 | public ThreadPoolExecutor getWriteThreadPool() { 310 | return writeThreadPool; 311 | } 312 | 313 | public void setWriteThreadPool(ThreadPoolExecutor writeThreadPool) { 314 | this.writeThreadPool = writeThreadPool; 315 | } 316 | 317 | public IReadDoneCallBackProcessor getReadDoneCallBackProcessor() { 318 | return readDoneCallBackProcessor; 319 | } 320 | 321 | public void setReadDoneCallBackProcessor(IReadDoneCallBackProcessor readDoneCallBackProcessor) { 322 | this.readDoneCallBackProcessor = readDoneCallBackProcessor; 323 | } 324 | 325 | } 326 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/ExcelUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | import java.util.concurrent.LinkedBlockingQueue; 5 | import java.util.concurrent.ThreadPoolExecutor; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import org.apache.poi.ss.usermodel.Sheet; 9 | import org.apache.poi.xssf.streaming.SXSSFWorkbook; 10 | import org.apache.poi.xssf.usermodel.XSSFWorkbook; 11 | 12 | import com.github.Dorae132.easyutil.easyexcel.common.EasyExcelException; 13 | import com.github.Dorae132.easyutil.easyexcel.common.Pair; 14 | import com.github.Dorae132.easyutil.easyexcel.export.IFillSheet; 15 | import com.github.Dorae132.easyutil.easyexcel.read.ConsumeRowThread; 16 | import com.github.Dorae132.easyutil.easyexcel.read.DefaultReadDoneCallBackProcessor; 17 | import com.github.Dorae132.easyutil.easyexcel.read.ExcelVersionEnums; 18 | import com.github.Dorae132.easyutil.easyexcel.read.IReadDoneCallBack; 19 | import com.github.Dorae132.easyutil.easyexcel.read.IReadDoneCallBackProcessor; 20 | import com.github.Dorae132.easyutil.easyexcel.read.IRowConsumer; 21 | import com.github.Dorae132.easyutil.easyexcel.read.event.IHandlerContext; 22 | 23 | /** 24 | * Excel utils 25 | * 26 | * @author Dorae 27 | * 28 | */ 29 | public class ExcelUtils { 30 | 31 | // 并行读默认线程池 32 | private static final ThreadPoolExecutor READ_THREAD_POOL = new ThreadPoolExecutor(0, 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1024)); 33 | 34 | /** 35 | * support the append strategy, but not rocommend, please focus on 36 | * FillSheetModeEnums.APPEND_MODE 37 | * 38 | * @param properties 39 | * @param iFillSheet 40 | * @return 41 | * @throws Exception 42 | */ 43 | public static R excelExport(ExcelProperties properties, IFillSheet iFillSheet) throws Exception { 44 | // 2.写入文件 45 | SXSSFWorkbook sxssfWorkbook = null; 46 | XSSFWorkbook xssfWorkbook = null; 47 | try { 48 | xssfWorkbook = new XSSFWorkbook();; 49 | sxssfWorkbook = new SXSSFWorkbook(xssfWorkbook, properties.getRowAccessWindowsize()); 50 | Sheet sheet = sxssfWorkbook.getSheet(properties.getSheetName()); 51 | if (sheet == null) { 52 | sheet = sxssfWorkbook.createSheet(properties.getSheetName()); 53 | } 54 | iFillSheet.fill(properties, sheet); 55 | if (properties.getWbProcessor() != null) { 56 | return properties.getWbProcessor().process(sxssfWorkbook, properties); 57 | } else { 58 | return (R)sxssfWorkbook; 59 | } 60 | } catch (Exception e) { 61 | throw new EasyExcelException(e); 62 | } finally { 63 | if (properties.getWbProcessor() != null) { 64 | if (xssfWorkbook != null) { 65 | xssfWorkbook.close(); 66 | } 67 | if (sxssfWorkbook != null) { 68 | sxssfWorkbook.close(); 69 | } 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * read util, enable multi sheet
76 | * when the syncCurrentThread is false and the callBack is not null, not recomand 77 | * 78 | * @param properties 79 | * @param rowConsumer 80 | * the consumer of a rowList 81 | * @param callBack 82 | * if null do nothing 83 | * @param threadCount 84 | * the number of the consume thread 85 | * @param syncCurrentThread 86 | * synchronized the current thread or not 87 | * @throws Exception 88 | */ 89 | public static void excelRead(ExcelProperties properties, IRowConsumer rowConsumer, IReadDoneCallBack callBack, 90 | int threadCount, boolean syncCurrentThread) throws Exception { 91 | IHandlerContext context = ExcelVersionEnums.produceContext(properties); 92 | excelRead(properties, context, rowConsumer, callBack, threadCount, syncCurrentThread); 93 | } 94 | 95 | /** 96 | * You can expand the context use this
97 | * when the syncCurrentThread is false and the callBack is not null, not recomand 98 | * @param context 99 | * @param rowConsumer 100 | * @param callBack 101 | * @param threadCount 102 | * @param syncCurrentThread 103 | * @throws Exception 104 | */ 105 | public static void excelRead(ExcelProperties properties, IHandlerContext context, IRowConsumer rowConsumer, IReadDoneCallBack callBack, 106 | int threadCount, boolean syncCurrentThread) throws Exception { 107 | CountDownLatch latch = new CountDownLatch(threadCount); 108 | try { 109 | for (int i = 0; i < threadCount; i++) { 110 | if (properties.getReadThreadPool() != null) { 111 | properties.getReadThreadPool().execute(new ConsumeRowThread<>(context, rowConsumer, latch)); 112 | } else { 113 | // default impl 114 | READ_THREAD_POOL.execute(new ConsumeRowThread(context, rowConsumer, latch)); 115 | } 116 | } 117 | // main thread get the rows 118 | context.process(); 119 | if (syncCurrentThread) { 120 | latch.await(properties.getReadThreadWaitTime(), TimeUnit.SECONDS); 121 | if (callBack != null) { 122 | callBack.call(); 123 | } 124 | } else if (callBack != null) { 125 | // not recomand, because there will be a new thread for every request. 126 | IReadDoneCallBackProcessor readDoneCallBackProcessor = properties.getReadDoneCallBackProcessor(); 127 | if (readDoneCallBackProcessor == null) { 128 | readDoneCallBackProcessor = new DefaultReadDoneCallBackProcessor(); 129 | } 130 | readDoneCallBackProcessor.process(Pair.of(latch, callBack), properties); 131 | } 132 | } catch (Exception e) { 133 | throw new EasyExcelException(e); 134 | } finally { 135 | context.close(); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/common/EasyExcelException.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.common; 2 | 3 | public class EasyExcelException extends RuntimeException { 4 | private static final long serialVersionUID = -20778884061304669L; 5 | 6 | public EasyExcelException() { 7 | super(); 8 | } 9 | 10 | public EasyExcelException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 11 | super(message, cause, enableSuppression, writableStackTrace); 12 | } 13 | 14 | public EasyExcelException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public EasyExcelException(String message) { 19 | super(message); 20 | } 21 | 22 | public EasyExcelException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/common/Pair.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.common; 2 | 3 | /** 4 | * 5 | * @author Dorae 6 | * 7 | * @param first 8 | * @param second 9 | */ 10 | public class Pair { 11 | 12 | private S first; 13 | private T second; 14 | 15 | private Pair(S first, T second) { 16 | this.first = first; 17 | this.second = second; 18 | } 19 | 20 | public static Pair of(S first, T second) { 21 | return new Pair(first, second); 22 | } 23 | 24 | public S getFirst() { 25 | return first; 26 | } 27 | 28 | public T getSecond() { 29 | return second; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/export/DefaultWorkbookProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.export; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | 6 | import org.apache.poi.ss.usermodel.Workbook; 7 | 8 | import com.github.Dorae132.easyutil.easyexcel.ExcelProperties; 9 | import com.github.Dorae132.easyutil.easyexcel.common.EasyExcelException; 10 | 11 | /** 12 | * the default impl of 13 | * @author Dorae 14 | * 15 | */ 16 | public class DefaultWorkbookProcessor implements IWorkbookProcessor{ 17 | 18 | @Override 19 | public File process(Workbook wb, ExcelProperties properties) throws Exception { 20 | // 1.创建目录 21 | validateFileDir(properties.getFilePath()); 22 | File file = new File(new StringBuilder(properties.getFilePath()).append(properties.getFileName()).toString()); 23 | FileOutputStream out = null; 24 | try { 25 | out = new FileOutputStream(file); 26 | wb.write(out); 27 | } catch (Exception e) { 28 | // do nothing 29 | throw new EasyExcelException(e); 30 | } finally { 31 | if (out != null) { 32 | out.close(); 33 | } 34 | if (wb != null) { 35 | wb.close(); 36 | } 37 | } 38 | return file; 39 | } 40 | 41 | /** 42 | * 校验目录是否存在 43 | * 44 | * @param filePath 45 | */ 46 | private static synchronized void validateFileDir(String filePath) { 47 | File tempDir = new File(filePath); 48 | if (!tempDir.exists() && !tempDir.isDirectory()) { 49 | tempDir.mkdir(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/export/ExcelCol.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.export; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * for the map of the excel and meta 10 | * @author Dorae 11 | * 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.FIELD}) 15 | public @interface ExcelCol { 16 | 17 | // 对应列标题 18 | String title(); 19 | 20 | int order() default 0; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/export/FillSheetModeEnums.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.export; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.LinkedBlockingQueue; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | import org.apache.commons.collections.CollectionUtils; 11 | import org.apache.poi.ss.usermodel.Sheet; 12 | 13 | import com.github.Dorae132.easyutil.easyexcel.ExcelProperties; 14 | import com.github.Dorae132.easyutil.easyexcel.common.EasyExcelException; 15 | import com.github.Dorae132.easyutil.easyexcel.common.Pair; 16 | 17 | /** 18 | * 写入模式枚举 19 | * 20 | * @author Dorae 21 | * 22 | */ 23 | public enum FillSheetModeEnums { 24 | 25 | /** 26 | * 普通模式 27 | */ 28 | COMMON_MODE(new IFillSheet() { 29 | 30 | @Override 31 | public void fill(ExcelProperties excelProperties, Sheet sheet) throws Exception { 32 | createHeadRow(excelProperties, sheet); 33 | fillContentRow(excelProperties, sheet); 34 | } 35 | 36 | }), 37 | /** 38 | * 追加模式 39 | */ 40 | APPEND_MODE(new IFillSheet() { 41 | @Override 42 | public void fill(ExcelProperties properties, Sheet sheet) throws Exception { 43 | int rowOffset = 0; 44 | Pair datasPair = null; 45 | List dataList = null; 46 | boolean hasNext = false; 47 | IDataSupplier dataSupplier = properties.getDataSupplier(); 48 | while (dataSupplier != null) { 49 | datasPair = dataSupplier.getDatas(); 50 | dataList = (List) datasPair.getFirst(); 51 | hasNext = (boolean) datasPair.getSecond(); 52 | if (CollectionUtils.isNotEmpty(dataList)) { 53 | properties.setDataList(dataList); 54 | properties.setRowOffset(rowOffset); 55 | createHeadRow(properties, sheet); 56 | fillContentRow(properties, sheet); 57 | rowOffset += dataList.size(); 58 | } else { 59 | break; 60 | } 61 | if (!hasNext) { 62 | break; 63 | } 64 | } 65 | } 66 | 67 | }), 68 | /** 69 | * 并行追加 70 | */ 71 | PARALLEL_APPEND_MODE(new IFillSheet() { 72 | ExecutorService executorService = new ThreadPoolExecutor(0, 2, 60L, TimeUnit.SECONDS, 73 | new LinkedBlockingQueue<>(1024)); 74 | @Override 75 | public void fill(ExcelProperties properties, Sheet sheet) throws Exception { 76 | int rowOffset = 0; 77 | IDataSupplier dataSupplier = properties.getDataSupplier(); 78 | LinkedBlockingQueue dataPairQueue = new LinkedBlockingQueue<>(); 79 | // more data or not 80 | AtomicBoolean moreData = new AtomicBoolean(true); 81 | // the get data thread 82 | Runnable getDataThread = new Runnable() { 83 | @Override 84 | public void run() { 85 | try { 86 | while (dataSupplier != null) { 87 | Pair pair = dataSupplier.getDatas(); 88 | List dataList = (List) pair.getFirst(); 89 | boolean hasNext = (boolean) pair.getSecond(); 90 | if (CollectionUtils.isNotEmpty(dataList)) { 91 | dataPairQueue.put(dataList); 92 | } else { 93 | break; 94 | } 95 | if (!hasNext) { 96 | break; 97 | } 98 | } 99 | } catch (Exception e) { 100 | // just return 101 | throw new EasyExcelException(e); 102 | } 103 | // the thread that be used to get data return 104 | moreData.set(false); 105 | } 106 | }; 107 | // start 108 | if (properties.getWriteThreadPool() != null) { 109 | properties.getWriteThreadPool().execute(getDataThread); 110 | } else { 111 | executorService.execute(getDataThread); 112 | } 113 | // main thread fill the excel 114 | while (true) { 115 | List dataList = (List) dataPairQueue.poll(); 116 | if (CollectionUtils.isEmpty(dataList)) { 117 | if (moreData.get()) { 118 | // there are more data then wait 119 | TimeUnit.MILLISECONDS.sleep(100); 120 | continue; 121 | } else { 122 | // no more data then return 123 | break; 124 | } 125 | } 126 | properties.setDataList(dataList); 127 | properties.setRowOffset(rowOffset); 128 | createHeadRow(properties, sheet); 129 | fillContentRow(properties, sheet); 130 | rowOffset += dataList.size(); 131 | } 132 | } 133 | 134 | }); 135 | 136 | private IFillSheet iFillSheet; 137 | 138 | private FillSheetModeEnums(IFillSheet iFillSheet) { 139 | this.iFillSheet = iFillSheet; 140 | } 141 | 142 | public IFillSheet getValue() { 143 | return this.iFillSheet; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/export/IDataSupplier.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.export; 2 | 3 | import java.util.List; 4 | 5 | import com.github.Dorae132.easyutil.easyexcel.common.Pair; 6 | 7 | /** 8 | * for the append strategy 9 | * @author Dorae 10 | * 11 | * @param 12 | */ 13 | public interface IDataSupplier { 14 | 15 | public Pair, Boolean> getDatas(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/export/IFillSheet.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.export; 2 | 3 | import java.lang.reflect.Field; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.apache.commons.collections.CollectionUtils; 8 | import org.apache.poi.ss.usermodel.Row; 9 | import org.apache.poi.ss.usermodel.Sheet; 10 | import org.apache.poi.ss.util.CellUtil; 11 | 12 | import com.github.Dorae132.easyutil.easyexcel.ExcelProperties; 13 | 14 | /** 15 | * 填充sheet 16 | * @author Dorae 17 | * 18 | */ 19 | public interface IFillSheet { 20 | 21 | void fill(ExcelProperties excelProperties, Sheet sheet) throws Exception; 22 | 23 | /** 24 | * 填充内容 25 | * 26 | * @param dataList 27 | * @param titleToFieldObjs 28 | * @param sheet 29 | * @throws IllegalAccessException 30 | * @throws IllegalArgumentException 31 | */ 32 | default void fillContentRow(ExcelProperties excelProperties, Sheet sheet) throws Exception { 33 | if (CollectionUtils.isEmpty(excelProperties.getDataList())) { 34 | return; 35 | } 36 | List fields = excelProperties.getFields(); 37 | Map fieldNameMap = excelProperties.getFieldNameMap(); 38 | int row = 1 + excelProperties.getRowOffset(); 39 | for (Object object : excelProperties.getDataList()) { 40 | int col = 0; 41 | Row createRow = sheet.createRow(row); 42 | for (Field field : fields) { 43 | field.setAccessible(true); 44 | Object value = field.get(object); 45 | CellUtil.createCell(createRow, col, value == null ? "" : value.toString()); 46 | col++; 47 | } 48 | row++; 49 | } 50 | } 51 | 52 | /** 53 | * 创建表头行(第0行创建) 54 | * 55 | * @param titleMap 56 | * 对象属性名称->表头显示名称 57 | */ 58 | default void createHeadRow(ExcelProperties properties, Sheet sheet) { 59 | List titles = properties.getTitles(); 60 | // 偏移不为0 61 | if (properties.getRowOffset() != 0) { 62 | return; 63 | } 64 | Row headRow = sheet.createRow(0); 65 | int i = 0; 66 | for (String title : titles) { 67 | headRow.createCell(i++).setCellValue(title); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/export/IWorkbookProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.export; 2 | 3 | import org.apache.poi.ss.usermodel.Workbook; 4 | 5 | import com.github.Dorae132.easyutil.easyexcel.ExcelProperties; 6 | 7 | /** 8 | * process the file that has been created 9 | * @author Dorae 10 | * 11 | * @param 12 | * @param 13 | */ 14 | @FunctionalInterface 15 | public interface IWorkbookProcessor { 16 | R process(Workbook wb, ExcelProperties properties) throws Exception; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/ConsumeRowThread.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.BrokenBarrierException; 5 | import java.util.concurrent.CountDownLatch; 6 | import java.util.concurrent.CyclicBarrier; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import org.apache.commons.collections.CollectionUtils; 10 | 11 | import com.github.Dorae132.easyutil.easyexcel.common.EasyExcelException; 12 | import com.github.Dorae132.easyutil.easyexcel.read.event.IHandlerContext; 13 | 14 | /** 15 | * 消费rowthread 16 | * 17 | * @author Dorae 18 | * 19 | */ 20 | public class ConsumeRowThread implements Runnable { 21 | 22 | private IHandlerContext context; 23 | 24 | private IRowConsumer rowConsumer; 25 | 26 | private CountDownLatch lanch; 27 | 28 | @Override 29 | public void run() { 30 | try { 31 | while (true) { 32 | List row = null; 33 | row = context.getRow(); 34 | if (CollectionUtils.isEmpty(row)) { 35 | if (!context.isFileEnded()) { 36 | // there are more rows 37 | TimeUnit.MILLISECONDS.sleep(100); 38 | continue; 39 | } else { 40 | // there are no more rows 41 | break; 42 | } 43 | } 44 | rowConsumer.consume(row); 45 | } 46 | } catch (Exception e) { 47 | throw new EasyExcelException(e); 48 | } finally { 49 | if (lanch != null) { 50 | lanch.countDown(); 51 | } 52 | } 53 | } 54 | 55 | public ConsumeRowThread(IHandlerContext context, IRowConsumer rowConsumer, CountDownLatch latch) { 56 | super(); 57 | this.context = context; 58 | this.rowConsumer = rowConsumer; 59 | this.lanch = latch; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/DefaultReadDoneCallBackProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import com.github.Dorae132.easyutil.easyexcel.ExcelProperties; 7 | import com.github.Dorae132.easyutil.easyexcel.common.EasyExcelException; 8 | import com.github.Dorae132.easyutil.easyexcel.common.Pair; 9 | 10 | /** 11 | * the default processor
12 | * this implemet will not be recomanded. 13 | * @author Dorae 14 | * 15 | */ 16 | public class DefaultReadDoneCallBackProcessor implements IReadDoneCallBackProcessor { 17 | 18 | @Override 19 | public Void process(Pair callback, ExcelProperties properties) 20 | throws Exception { 21 | new Thread(() -> { 22 | try { 23 | callback.getFirst().await(properties.getReadThreadWaitTime(), TimeUnit.SECONDS); 24 | } catch (Exception e) { 25 | throw new EasyExcelException(e); 26 | } 27 | callback.getSecond().call(); 28 | }).start(); 29 | return null; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/ExcelVersionEnums.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read; 2 | 3 | import java.io.FileInputStream; 4 | 5 | import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; 6 | import org.apache.poi.hssf.eventusermodel.HSSFRequest; 7 | import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener; 8 | import org.apache.poi.poifs.filesystem.POIFSFileSystem; 9 | 10 | import com.github.Dorae132.easyutil.easyexcel.ExcelProperties; 11 | import com.github.Dorae132.easyutil.easyexcel.read.event.IHandlerContext; 12 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.Default03RecordHandlerContext; 13 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel07.Default07RecordHandlerContext; 14 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel07.XlsxHandler; 15 | 16 | /** 17 | * the enums of the excel versions 18 | * 19 | * @author Dorae 20 | * 21 | */ 22 | public enum ExcelVersionEnums { 23 | 24 | V2003("xls"), V2007("xlsx"); 25 | 26 | private String suffix; 27 | 28 | private ExcelVersionEnums(String suffix) { 29 | this.suffix = suffix; 30 | } 31 | 32 | public String getSuffix() { 33 | return suffix; 34 | } 35 | 36 | public static IHandlerContext produceContext(ExcelProperties properties) throws Exception { 37 | StringBuilder fileNameSB = new StringBuilder(properties.getFileName()); 38 | String fileNameSufix = fileNameSB.substring(fileNameSB.lastIndexOf(".") + 1, fileNameSB.length()); 39 | String absolutePath = fileNameSB.insert(0, properties.getFilePath()).toString(); 40 | try { 41 | if (V2003.getSuffix().equals(fileNameSufix)) { 42 | FileInputStream inputStream = new FileInputStream(absolutePath); 43 | HSSFRequest request = new HSSFRequest(); 44 | POIFSFileSystem fileSystem = new POIFSFileSystem(inputStream); 45 | Default03RecordHandlerContext context = Default03RecordHandlerContext.Default03RecordContextFactory 46 | .getContext(request, fileSystem); 47 | MissingRecordAwareHSSFListener missingRecordAwareHSSFListener = new MissingRecordAwareHSSFListener(context); 48 | FormatTrackingHSSFListener formatTrackingHSSFListener = new FormatTrackingHSSFListener( 49 | missingRecordAwareHSSFListener); 50 | request.addListenerForAllRecords(formatTrackingHSSFListener); 51 | // hssfRequest.addListenerForAllRecords(new 52 | // SheetRecordCollectingListener(formatTrackingHSSFListener)); 53 | return context; 54 | } else if (V2007.getSuffix().equals(fileNameSufix)) { 55 | XlsxHandler xlsxHandler = new XlsxHandler(); 56 | Default07RecordHandlerContext context = Default07RecordHandlerContext.Default07RecordContextFactory 57 | .getContext(xlsxHandler, absolutePath); 58 | xlsxHandler.setContext(context); 59 | return context; 60 | } else { 61 | throw new RuntimeException("不支持的文件类型"); 62 | } 63 | } finally { 64 | // the stream will be closed int the context. 65 | } 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/IReadDoneCallBack.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read; 2 | 3 | /** 4 | * this while be called when there are no more rows 5 | * @author Dorae 6 | * @param 7 | */ 8 | public interface IReadDoneCallBack { 9 | 10 | R call(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/IReadDoneCallBackProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | 5 | import com.github.Dorae132.easyutil.easyexcel.ExcelProperties; 6 | import com.github.Dorae132.easyutil.easyexcel.common.Pair; 7 | 8 | /** 9 | * the processor that will be called after the read thread have been done. 10 | * @author Dorae 11 | * 12 | */ 13 | public interface IReadDoneCallBackProcessor { 14 | 15 | R process(Pair callback, ExcelProperties properties) throws Exception; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/IRowConsumer.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * The consumer of the row 7 | * @author Dorae 8 | * @param 9 | */ 10 | public interface IRowConsumer { 11 | void consume(List row); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/IHandlerContext.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event; 2 | 3 | import java.io.Closeable; 4 | import java.util.List; 5 | 6 | /** 7 | * handlerContext顶层接口 8 | * 9 | * @author Dorae 10 | * 11 | * @param 12 | * The type that wanted in the cell 13 | */ 14 | public interface IHandlerContext extends Closeable { 15 | 16 | /** 17 | * produce a new row 18 | * 19 | * @param row 20 | * @throws InterruptedException 21 | */ 22 | void newRow(List row) throws Exception; 23 | 24 | /** 25 | * get a row from the context 26 | * 27 | * @return 28 | * @throws Exception 29 | */ 30 | List getRow() throws Exception; 31 | 32 | /** 33 | * Fire this when the file is ending. 34 | * 35 | * @return 36 | */ 37 | boolean fileEnd(); 38 | 39 | /** 40 | * the processing is end or not 41 | * 42 | * @return 43 | */ 44 | boolean isFileEnded(); 45 | 46 | /** 47 | * process the file 48 | */ 49 | void process() throws Exception; 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/Default03RecordHandlerContext.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import java.util.concurrent.LinkedBlockingQueue; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; 9 | import org.apache.poi.hssf.eventusermodel.HSSFListener; 10 | import org.apache.poi.hssf.eventusermodel.HSSFRequest; 11 | import org.apache.poi.hssf.record.Record; 12 | import org.apache.poi.hssf.record.SSTRecord; 13 | import org.apache.poi.poifs.filesystem.POIFSFileSystem; 14 | 15 | import com.github.Dorae132.easyutil.easyexcel.common.EasyExcelException; 16 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.Abstract03RecordHandler; 17 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.BlankRecordHandler; 18 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.BoundSheetRecordHandler; 19 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.EofRecordHandler; 20 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.NumberRecordHandler; 21 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.RowEndRecordHandler; 22 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.SSTRecordHandler; 23 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.StringRecordHandler; 24 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.TailRecordHandler; 25 | import com.google.common.collect.Lists; 26 | 27 | /** 28 | * The defaut context for the handler 29 | * @author Dorae 30 | * 31 | */ 32 | public class Default03RecordHandlerContext implements IRecordHandlerContext, HSSFListener { 33 | 34 | private int currColNum; 35 | 36 | private SSTRecord sstRecord; 37 | 38 | private List currRowList; 39 | 40 | private LinkedBlockingQueue> rowQueue; 41 | 42 | private Abstract03RecordHandler headRecordHandler; 43 | 44 | private AtomicInteger sheetNumbers = new AtomicInteger(1); 45 | 46 | private HSSFRequest request; 47 | 48 | private POIFSFileSystem fileSystem; 49 | 50 | public static class Default03RecordContextFactory { 51 | public static Default03RecordHandlerContext getContext(HSSFRequest request, POIFSFileSystem fileSystem) { 52 | Default03RecordHandlerContext context = new Default03RecordHandlerContext(); 53 | context.currColNum = 0; 54 | context.currRowList = Lists.newArrayList(); 55 | context.rowQueue = new LinkedBlockingQueue<>(); 56 | context.initHeadHandler(); 57 | context.request = request; 58 | context.fileSystem = fileSystem; 59 | return context; 60 | } 61 | } 62 | 63 | protected void initHeadHandler() { 64 | // The head handler do nothing, just pass 65 | headRecordHandler = new Abstract03RecordHandler(this) { 66 | @Override 67 | public void decode(Record record) throws Exception { 68 | } 69 | @Override 70 | public boolean couldDecode(Record record) { 71 | return false; 72 | } 73 | }; 74 | SSTRecordHandler sstRecordHandler = new SSTRecordHandler(this); 75 | StringRecordHandler stringRecordHandler = new StringRecordHandler(this); 76 | NumberRecordHandler numberRecordHandler = new NumberRecordHandler(this); 77 | BlankRecordHandler blankRecordHandler = new BlankRecordHandler(this); 78 | RowEndRecordHandler rowEndRecordHandler = new RowEndRecordHandler(this); 79 | BoundSheetRecordHandler boundSheetRecordHandler = new BoundSheetRecordHandler(this); 80 | EofRecordHandler eofRecordHandler = new EofRecordHandler(this); 81 | TailRecordHandler tailRecordHandler = new TailRecordHandler(this); 82 | headRecordHandler.setNext(sstRecordHandler).setNext(stringRecordHandler).setNext(numberRecordHandler) 83 | .setNext(blankRecordHandler).setNext(rowEndRecordHandler).setNext(boundSheetRecordHandler) 84 | .setNext(eofRecordHandler).setNext(tailRecordHandler); 85 | } 86 | 87 | private Default03RecordHandlerContext() { 88 | super(); 89 | } 90 | 91 | @Override 92 | public void handle(Record record) throws Exception { 93 | this.headRecordHandler.handle(record); 94 | } 95 | 96 | @Override 97 | public void registRecordHandler(Abstract03RecordHandler recordHandler) { 98 | // Let the SSTRecordHandler has the highest priority. 99 | recordHandler.setNext(headRecordHandler.next.next); 100 | headRecordHandler.next.setNext(recordHandler); 101 | } 102 | 103 | @Override 104 | public void setCurrColNum(int currColNum) { 105 | this.currColNum = currColNum; 106 | } 107 | 108 | @Override 109 | public int getCurrColNum() { 110 | return this.currColNum; 111 | } 112 | 113 | @Override 114 | public SSTRecord getSSTRecord() { 115 | return this.sstRecord; 116 | } 117 | 118 | @Override 119 | public void initCurrRowList(int colNum) { 120 | if (colNum <= 0) { 121 | this.currRowList = Lists.newArrayList(); 122 | } else { 123 | this.currRowList = Lists.newArrayListWithExpectedSize(colNum); 124 | } 125 | } 126 | 127 | @Override 128 | public List getCurrRowList() { 129 | return this.currRowList; 130 | } 131 | 132 | @Override 133 | public void addCol2CurrRowList(String colValue) { 134 | currRowList.add(colValue); 135 | } 136 | 137 | @Override 138 | public void setSSTRecord(SSTRecord sstRecord) { 139 | this.sstRecord = sstRecord; 140 | } 141 | 142 | @Override 143 | public void newRow(List row) throws InterruptedException { 144 | this.rowQueue.put(row); 145 | } 146 | 147 | @Override 148 | public List getRow() throws Exception { 149 | return this.rowQueue.poll(); 150 | } 151 | 152 | @Override 153 | public boolean fileEnd() { 154 | return true; 155 | } 156 | 157 | @Override 158 | public boolean isFileEnded() { 159 | return sheetNumbers.intValue() == 0; 160 | } 161 | 162 | @Override 163 | public void increaseSheetNumbers() { 164 | sheetNumbers.incrementAndGet(); 165 | } 166 | 167 | @Override 168 | public void decreaseSheetNumbers() { 169 | sheetNumbers.decrementAndGet(); 170 | } 171 | 172 | @Override 173 | public void process() throws Exception { 174 | HSSFEventFactory hssfEventFactory = new HSSFEventFactory(); 175 | hssfEventFactory.processWorkbookEvents(this.request, this.fileSystem); 176 | } 177 | 178 | @Override 179 | public void processRecord(Record record) { 180 | try { 181 | this.handle(record); 182 | } catch (Exception e) { 183 | throw new EasyExcelException(e); 184 | } 185 | } 186 | 187 | @Override 188 | public void close() throws IOException { 189 | if (fileSystem != null) { 190 | fileSystem.close(); 191 | } 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/IRecordHandlerContext.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.poi.hssf.record.SSTRecord; 6 | 7 | import com.github.Dorae132.easyutil.easyexcel.read.event.IHandlerContext; 8 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler.Abstract03RecordHandler; 9 | 10 | /** 11 | * The interface for handlerContext 12 | * @author Dorae 13 | * @param The target type for the cell value 14 | */ 15 | public interface IRecordHandlerContext extends IHandlerContext { 16 | 17 | /** 18 | * handle new record 19 | * @param record 20 | * @throws Exception 21 | */ 22 | public void handle(R record) throws Exception; 23 | 24 | /** 25 | * Enable the capacity that could insert the custom handler 26 | * @param recordHandler 27 | */ 28 | void registRecordHandler(Abstract03RecordHandler recordHandler); 29 | 30 | /** 31 | * Set the current col num. 32 | * @param currColNum 33 | */ 34 | void setCurrColNum(int currColNum); 35 | 36 | /** 37 | * Get the current col num. 38 | * @return 39 | */ 40 | int getCurrColNum(); 41 | 42 | /** 43 | * Get the constants table 44 | * @return 45 | */ 46 | SSTRecord getSSTRecord(); 47 | 48 | /** 49 | * Init the current row list, let it be empty. 50 | * @param colNum 51 | */ 52 | void initCurrRowList(int colNum); 53 | 54 | /** 55 | * Get the current row list 56 | * @return 57 | */ 58 | List getCurrRowList(); 59 | 60 | /** 61 | * Add a value of the col to the current row list. 62 | * @param t 63 | */ 64 | void addCol2CurrRowList(C colValue); 65 | 66 | /** 67 | * Set the constants table 68 | * @param sstRecord 69 | */ 70 | void setSSTRecord(SSTRecord sstRecord); 71 | 72 | /** 73 | * set the number of the sheet. 74 | * this is used to judge whither the file has been ended or not. 75 | * @param nums 76 | */ 77 | void increaseSheetNumbers(); 78 | 79 | /** 80 | * decrease the number of the sheet 81 | */ 82 | void decreaseSheetNumbers(); 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/XlsListener.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03; 2 | 3 | import org.apache.poi.hssf.eventusermodel.HSSFListener; 4 | import org.apache.poi.hssf.record.Record; 5 | 6 | /** 7 | * for xls 8 | * 9 | * @author Dorae 10 | * 11 | */ 12 | @Deprecated 13 | public class XlsListener implements HSSFListener { 14 | 15 | private IRecordHandlerContext context = Default03RecordHandlerContext.Default03RecordContextFactory.getContext(null, 16 | null); 17 | 18 | @Override 19 | public void processRecord(Record record) { 20 | try { 21 | context.handle(record); 22 | } catch (Exception e) { 23 | // do nothing 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/Abstract03RecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import org.apache.poi.hssf.record.Record; 4 | 5 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 6 | 7 | /** 8 | * the handler that process the record for the xls file 9 | * @author Dorae 10 | * 11 | * @param The target type of the cell. 12 | */ 13 | public abstract class Abstract03RecordHandler { 14 | 15 | public Abstract03RecordHandler next; 16 | 17 | public IRecordHandlerContext handlerContext; 18 | 19 | private Abstract03RecordHandler() { 20 | super(); 21 | } 22 | 23 | public Abstract03RecordHandler(IRecordHandlerContext handlerContext) { 24 | super(); 25 | this.handlerContext = handlerContext; 26 | } 27 | 28 | public Abstract03RecordHandler setNext(Abstract03RecordHandler next) { 29 | this.next = next; 30 | return next; 31 | } 32 | 33 | /** 34 | * handle 35 | * @param handlerContext 36 | * @param record 37 | */ 38 | public void handle(Record record) throws Exception { 39 | if (this.couldDecode(record)) { 40 | this.decode(record); 41 | } else if (next != null) { 42 | next.handle(record); 43 | } else { 44 | // just do nothing 45 | return; 46 | } 47 | } 48 | 49 | /** 50 | * could decode or not 51 | * @param handlerContext 52 | * @param record 53 | * @return 54 | */ 55 | public abstract boolean couldDecode(Record record); 56 | 57 | /** 58 | * decode 59 | * @param handlerContext 60 | * @param record 61 | */ 62 | public abstract void decode(Record record) throws Exception; 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/BlankRecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import org.apache.poi.hssf.record.BlankRecord; 4 | import org.apache.poi.hssf.record.Record; 5 | 6 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 7 | 8 | /** 9 | * for blank cell 10 | * @author Dorae 11 | * 12 | */ 13 | public class BlankRecordHandler extends Abstract03RecordHandler { 14 | 15 | private final static String BLANK = ""; 16 | 17 | public BlankRecordHandler(IRecordHandlerContext handlerContext) { 18 | super(handlerContext); 19 | } 20 | 21 | @Override 22 | public boolean couldDecode(Record record) { 23 | return BlankRecord.sid == record.getSid(); 24 | } 25 | 26 | @Override 27 | public void decode(Record record) { 28 | BlankRecord blankRecord = (BlankRecord) record; 29 | int currColNum = blankRecord.getColumn(); 30 | handlerContext.addCol2CurrRowList(BLANK); 31 | handlerContext.setCurrColNum(currColNum); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/BoundSheetRecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import org.apache.poi.hssf.record.BoundSheetRecord; 4 | import org.apache.poi.hssf.record.Record; 5 | 6 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 7 | 8 | /** 9 | * the bound of the workbook 10 | * @author Dorae 11 | * 12 | */ 13 | public class BoundSheetRecordHandler extends Abstract03RecordHandler { 14 | 15 | public BoundSheetRecordHandler(IRecordHandlerContext handlerContext) { 16 | super(handlerContext); 17 | } 18 | 19 | @Override 20 | public boolean couldDecode(Record record) { 21 | return BoundSheetRecord.sid == record.getSid(); 22 | } 23 | 24 | @Override 25 | public void decode(Record record) { 26 | handlerContext.increaseSheetNumbers(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/EofRecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import org.apache.poi.hssf.record.EOFRecord; 4 | import org.apache.poi.hssf.record.Record; 5 | 6 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 7 | 8 | /** 9 | * record the end of the file/sheet 10 | * @author Dorae 11 | * 12 | */ 13 | public class EofRecordHandler extends Abstract03RecordHandler { 14 | 15 | public EofRecordHandler(IRecordHandlerContext handlerContext) { 16 | super(handlerContext); 17 | } 18 | 19 | @Override 20 | public boolean couldDecode(Record record) { 21 | return EOFRecord.sid == record.getSid(); 22 | } 23 | 24 | @Override 25 | public void decode(Record record) throws Exception { 26 | handlerContext.decreaseSheetNumbers(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/NumberRecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import java.text.DecimalFormat; 4 | 5 | import org.apache.poi.hssf.record.NumberRecord; 6 | import org.apache.poi.hssf.record.Record; 7 | 8 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 9 | 10 | /** 11 | * The handler for number cell 12 | * @author Dorae 13 | * 14 | */ 15 | public class NumberRecordHandler extends Abstract03RecordHandler { 16 | 17 | private static final DecimalFormat DF = new DecimalFormat("0.00"); 18 | 19 | public NumberRecordHandler(IRecordHandlerContext handlerContext) { 20 | super(handlerContext); 21 | } 22 | 23 | @Override 24 | public boolean couldDecode(Record record) { 25 | return NumberRecord.sid == record.getSid(); 26 | } 27 | 28 | @Override 29 | public void decode(Record record) { 30 | NumberRecord numberRecord = (NumberRecord) record; 31 | int currColNum = numberRecord.getColumn(); 32 | handlerContext.addCol2CurrRowList(DF.format(numberRecord.getValue())); 33 | handlerContext.setCurrColNum(currColNum); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/RowEndRecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord; 4 | import org.apache.poi.hssf.record.Record; 5 | 6 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 7 | 8 | /** 9 | * the row end handler 10 | * @author Dorae 11 | * 12 | */ 13 | public class RowEndRecordHandler extends Abstract03RecordHandler { 14 | 15 | @Override 16 | public boolean couldDecode(Record record) { 17 | return record instanceof LastCellOfRowDummyRecord; 18 | } 19 | 20 | public RowEndRecordHandler(IRecordHandlerContext handlerContext) { 21 | super(handlerContext); 22 | } 23 | 24 | @Override 25 | public void decode(Record record) throws Exception { 26 | // 产生新行,结束当前行 27 | if (handlerContext.getCurrColNum() != 0) { 28 | handlerContext.newRow(handlerContext.getCurrRowList()); 29 | handlerContext.setCurrColNum(0); 30 | } 31 | handlerContext.initCurrRowList(0); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/SSTRecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import org.apache.poi.hssf.record.Record; 4 | import org.apache.poi.hssf.record.SSTRecord; 5 | 6 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 7 | 8 | /** 9 | * the handler for constants 10 | * excel中的常量表 11 | * @author Dorae 12 | * 13 | */ 14 | public class SSTRecordHandler extends Abstract03RecordHandler { 15 | 16 | public SSTRecordHandler(IRecordHandlerContext handlerContext) { 17 | super(handlerContext); 18 | } 19 | 20 | @Override 21 | public boolean couldDecode(Record record) { 22 | return SSTRecord.sid == record.getSid(); 23 | } 24 | 25 | @Override 26 | public void decode(Record record) { 27 | SSTRecord sstRecord = (SSTRecord) record; 28 | handlerContext.setSSTRecord(sstRecord); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/StringRecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import org.apache.poi.hssf.record.LabelSSTRecord; 4 | import org.apache.poi.hssf.record.Record; 5 | 6 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 7 | 8 | /** 9 | * The handler for string cel. 10 | * @author Dorae 11 | * 12 | */ 13 | public class StringRecordHandler extends Abstract03RecordHandler { 14 | 15 | public StringRecordHandler(IRecordHandlerContext handlerContext) { 16 | super(handlerContext); 17 | } 18 | 19 | @Override 20 | public boolean couldDecode(Record record) { 21 | return LabelSSTRecord.sid == record.getSid(); 22 | } 23 | 24 | @Override 25 | public void decode(Record record) { 26 | LabelSSTRecord labelSSTRecord = (LabelSSTRecord) record; 27 | int currColNum = labelSSTRecord.getColumn(); 28 | handlerContext.addCol2CurrRowList(handlerContext.getSSTRecord().getString(labelSSTRecord.getSSTIndex()).toString()); 29 | handlerContext.setCurrColNum(currColNum); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel03/handler/TailRecordHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel03.handler; 2 | 3 | import org.apache.poi.hssf.record.Record; 4 | 5 | import com.github.Dorae132.easyutil.easyexcel.read.event.excel03.IRecordHandlerContext; 6 | 7 | /** 8 | * The tail handler, just in case there are no handler to be useed to resolve the col value. 9 | * @author Dorae 10 | * 11 | */ 12 | public class TailRecordHandler extends Abstract03RecordHandler { 13 | 14 | public TailRecordHandler(IRecordHandlerContext handlerContext) { 15 | super(handlerContext); 16 | } 17 | 18 | @Override 19 | public boolean couldDecode(Record record) { 20 | return true; 21 | } 22 | 23 | @Override 24 | public void decode(Record record) throws Exception { 25 | // just discard the record 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel07/Default07RecordHandlerContext.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel07; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import java.util.concurrent.LinkedBlockingQueue; 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | 8 | import com.github.Dorae132.easyutil.easyexcel.read.event.IHandlerContext; 9 | 10 | /** 11 | * The defaut context for the handler 12 | * @author Dorae 13 | * 14 | */ 15 | public class Default07RecordHandlerContext implements IHandlerContext { 16 | 17 | private LinkedBlockingQueue> rowQueue; 18 | 19 | private XlsxHandler handler; 20 | 21 | private AtomicBoolean fileEndFlag = new AtomicBoolean(false); 22 | 23 | private String fileName; 24 | 25 | public static class Default07RecordContextFactory { 26 | public static Default07RecordHandlerContext getContext(XlsxHandler xlsxHandler, String fileName) { 27 | Default07RecordHandlerContext context = new Default07RecordHandlerContext(); 28 | context.rowQueue = new LinkedBlockingQueue<>(); 29 | context.handler = xlsxHandler; 30 | context.fileName = fileName; 31 | return context; 32 | } 33 | } 34 | 35 | private Default07RecordHandlerContext() { 36 | super(); 37 | } 38 | 39 | @Override 40 | public void newRow(List row) throws Exception { 41 | this.rowQueue.put(row); 42 | } 43 | 44 | @Override 45 | public List getRow() throws Exception { 46 | return this.rowQueue.poll(); 47 | } 48 | 49 | @Override 50 | public boolean fileEnd() { 51 | fileEndFlag.set(true); 52 | return fileEndFlag.get(); 53 | } 54 | 55 | @Override 56 | public boolean isFileEnded() { 57 | return fileEndFlag.get(); 58 | } 59 | 60 | @Override 61 | public void process() throws Exception { 62 | handler.process(fileName); 63 | } 64 | 65 | @Override 66 | public void close() throws IOException { 67 | // just do nothing 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/github/Dorae132/easyutil/easyexcel/read/event/excel07/XlsxHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel.read.event.excel07; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.apache.poi.openxml4j.exceptions.OpenXML4JException; 10 | import org.apache.poi.openxml4j.opc.OPCPackage; 11 | import org.apache.poi.ss.usermodel.BuiltinFormats; 12 | import org.apache.poi.ss.usermodel.DataFormatter; 13 | import org.apache.poi.xssf.eventusermodel.XSSFReader; 14 | import org.apache.poi.xssf.model.SharedStringsTable; 15 | import org.apache.poi.xssf.model.StylesTable; 16 | import org.apache.poi.xssf.usermodel.XSSFCellStyle; 17 | import org.apache.poi.xssf.usermodel.XSSFRichTextString; 18 | import org.xml.sax.Attributes; 19 | import org.xml.sax.InputSource; 20 | import org.xml.sax.SAXException; 21 | import org.xml.sax.XMLReader; 22 | import org.xml.sax.helpers.DefaultHandler; 23 | import org.xml.sax.helpers.XMLReaderFactory; 24 | 25 | import com.github.Dorae132.easyutil.easyexcel.read.event.IHandlerContext; 26 | import com.google.common.collect.Lists; 27 | 28 | /** 29 | * 30 | * @author Dorae 31 | * @see 参考 https://www.cnblogs.com/wshsdlau/p/5643847.html 32 | * 33 | */ 34 | public class XlsxHandler extends DefaultHandler { 35 | 36 | private IHandlerContext context; 37 | 38 | /** 39 | * 共享字符串表 40 | */ 41 | private SharedStringsTable sharedStringsTable; 42 | 43 | /** 44 | * 上一次的内容 45 | */ 46 | private StringBuffer lastContents = new StringBuffer(); 47 | 48 | /** 49 | * 字符串标识 50 | */ 51 | private boolean nextIsString; 52 | 53 | /** 54 | * 工作表索引 55 | */ 56 | private int sheetIndex = -1; 57 | 58 | /** 59 | * 行集合 60 | */ 61 | private List currRowList = Lists.newArrayList(); 62 | 63 | /** 64 | * 当前行 65 | */ 66 | private int currRowIndex = 0; 67 | 68 | /** 69 | * 当前列 70 | */ 71 | private int currColIndex = 0; 72 | 73 | /** 74 | * T元素标识 75 | */ 76 | private boolean isTElement; 77 | 78 | /** 79 | * 单元格数据类型,默认为字符串类型 80 | */ 81 | private CellDataType nextDataType = CellDataType.SSTINDEX; 82 | 83 | private final DataFormatter sdf = new DataFormatter(); 84 | 85 | private short formatIndex; 86 | 87 | private String formatString; 88 | 89 | // 定义前一个元素和当前元素的位置,用来计算其中空的单元格数量,如A6和A8等 90 | private String preRef = null; 91 | 92 | private String ref = null; 93 | 94 | // 定义该文档一行最大的单元格数,用来补全一行最后可能缺失的单元格 95 | private String maxRef = null; 96 | 97 | /** 98 | * 单元格 99 | */ 100 | private StylesTable stylesTable; 101 | 102 | public XlsxHandler() { 103 | super(); 104 | } 105 | 106 | public XlsxHandler(IHandlerContext context) { 107 | super(); 108 | this.context = context; 109 | } 110 | 111 | public void setContext(IHandlerContext context) { 112 | this.context = context; 113 | } 114 | 115 | /** 116 | * 遍历工作簿中所有的电子表格 117 | * 118 | * @param filename 119 | * @throws IOException 120 | * @throws OpenXML4JException 121 | * @throws SAXException 122 | * @throws Exception 123 | */ 124 | public void process(String filename) throws IOException, OpenXML4JException, SAXException { 125 | OPCPackage pkg = null; 126 | try { 127 | pkg = OPCPackage.open(filename); 128 | XSSFReader xssfReader = new XSSFReader(pkg); 129 | stylesTable = xssfReader.getStylesTable(); 130 | SharedStringsTable sst = xssfReader.getSharedStringsTable(); 131 | XMLReader parser = this.fetchSheetParser(sst); 132 | Iterator sheets = xssfReader.getSheetsData(); 133 | while (sheets.hasNext()) { 134 | currRowIndex = 0; 135 | sheetIndex++; 136 | InputStream sheet = null; 137 | try { 138 | sheet = sheets.next(); 139 | InputSource sheetSource = new InputSource(sheet); 140 | parser.parse(sheetSource); 141 | } finally { 142 | sheet.close(); 143 | } 144 | } 145 | while (!context.fileEnd()); 146 | } finally { 147 | pkg.close(); 148 | } 149 | } 150 | 151 | private XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException { 152 | XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser"); 153 | this.sharedStringsTable = sst; 154 | parser.setContentHandler(this); 155 | return parser; 156 | } 157 | 158 | @Override 159 | public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { 160 | // c => 单元格 161 | if ("c".equals(name)) { 162 | // 前一个单元格的位置 163 | if (preRef == null) { 164 | preRef = attributes.getValue("r"); 165 | } else { 166 | preRef = ref; 167 | } 168 | // 当前单元格的位置 169 | ref = attributes.getValue("r"); 170 | // 设定单元格类型 171 | this.setNextDataType(attributes); 172 | // Figure out if the value is an index in the SST 173 | String cellType = attributes.getValue("t"); 174 | if (cellType != null && cellType.equals("s")) { 175 | nextIsString = true; 176 | } else { 177 | nextIsString = false; 178 | } 179 | } 180 | 181 | // 当元素为t时 182 | if ("t".equals(name)) { 183 | isTElement = true; 184 | } else { 185 | isTElement = false; 186 | } 187 | 188 | // 置空 189 | lastContents.delete(0, lastContents.length()); 190 | } 191 | 192 | /** 193 | * 单元格中的数据可能的数据类型 194 | */ 195 | enum CellDataType { 196 | BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER, DATE, NULL 197 | } 198 | 199 | /** 200 | * 处理数据类型 201 | * 202 | * @param attributes 203 | */ 204 | private void setNextDataType(Attributes attributes) { 205 | nextDataType = CellDataType.NUMBER; 206 | formatIndex = -1; 207 | formatString = null; 208 | String cellType = attributes.getValue("t"); 209 | String cellStyleStr = attributes.getValue("s"); 210 | String columData = attributes.getValue("r"); 211 | 212 | if ("b".equals(cellType)) { 213 | nextDataType = CellDataType.BOOL; 214 | } else if ("e".equals(cellType)) { 215 | nextDataType = CellDataType.ERROR; 216 | } else if ("inlineStr".equals(cellType)) { 217 | nextDataType = CellDataType.INLINESTR; 218 | } else if ("s".equals(cellType)) { 219 | nextDataType = CellDataType.SSTINDEX; 220 | } else if ("str".equals(cellType)) { 221 | nextDataType = CellDataType.FORMULA; 222 | } 223 | 224 | if (cellStyleStr != null) { 225 | int styleIndex = Integer.parseInt(cellStyleStr); 226 | XSSFCellStyle style = stylesTable.getStyleAt(styleIndex); 227 | formatIndex = style.getDataFormat(); 228 | formatString = style.getDataFormatString(); 229 | 230 | if ("m/d/yy" == formatString) { 231 | nextDataType = CellDataType.DATE; 232 | formatString = "yyyy-MM-dd hh:mm:ss.SSS"; 233 | } 234 | 235 | if (formatString == null) { 236 | nextDataType = CellDataType.NULL; 237 | formatString = BuiltinFormats.getBuiltinFormat(formatIndex); 238 | } 239 | } 240 | } 241 | 242 | /** 243 | * 对解析出来的数据进行类型处理 244 | * 245 | * @param value 246 | * 单元格的值(这时候是一串数字) 247 | * @param thisStr 248 | * 一个空字符串 249 | * @return 250 | */ 251 | @SuppressWarnings("deprecation") 252 | private String getDataValue(String value, String thisStr) { 253 | switch (nextDataType) { 254 | // 这几个的顺序不能随便交换,交换了很可能会导致数据错误 255 | case BOOL: 256 | char first = value.charAt(0); 257 | thisStr = first == '0' ? "FALSE" : "TRUE"; 258 | break; 259 | case ERROR: 260 | thisStr = "\"ERROR:" + value.toString() + '"'; 261 | break; 262 | case FORMULA: 263 | thisStr = '"' + value.toString() + '"'; 264 | break; 265 | case INLINESTR: 266 | XSSFRichTextString rtsi = new XSSFRichTextString(value.toString()); 267 | thisStr = rtsi.toString(); 268 | rtsi = null; 269 | break; 270 | case SSTINDEX: 271 | String sstIndex = value.toString(); 272 | try { 273 | int idx = Integer.parseInt(sstIndex); 274 | XSSFRichTextString rtss = new XSSFRichTextString(sharedStringsTable.getEntryAt(idx)); 275 | thisStr = rtss.toString(); 276 | rtss = null; 277 | } catch (NumberFormatException ex) { 278 | thisStr = value.toString(); 279 | } 280 | break; 281 | case NUMBER: 282 | if (formatString != null) { 283 | thisStr = sdf.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString).trim(); 284 | } else { 285 | thisStr = value; 286 | } 287 | 288 | thisStr = thisStr.replace("_", "").trim(); 289 | break; 290 | case DATE: 291 | thisStr = sdf.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString); 292 | // 对日期字符串作特殊处理 293 | thisStr = thisStr.replace(" ", "T"); 294 | break; 295 | default: 296 | thisStr = " "; 297 | break; 298 | } 299 | return thisStr; 300 | } 301 | 302 | @Override 303 | public void endElement(String uri, String localName, String name) throws SAXException { 304 | // 根据SST的索引值的到单元格的真正要存储的字符串 305 | // 这时characters()方法可能会被调用多次 306 | if (nextIsString && StringUtils.isNotEmpty(lastContents) && StringUtils.isNumeric(lastContents)) { 307 | int idx = Integer.parseInt(lastContents.toString()); 308 | lastContents.append(new XSSFRichTextString(sharedStringsTable.getEntryAt(idx)).toString()); 309 | } 310 | // t元素也包含字符串 311 | if (isTElement) { 312 | // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符 313 | String value = lastContents.toString().trim(); 314 | currRowList.add(currColIndex, value); 315 | currColIndex++; 316 | isTElement = false; 317 | } else if ("v".equals(name)) { 318 | // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引 319 | String value = this.getDataValue(lastContents.toString().trim(), ""); 320 | // 补全单元格之间的空单元格 321 | if (!ref.equals(preRef)) { 322 | int len = countNullCell(ref, preRef); 323 | for (int i = 0; i < len; i++) { 324 | currRowList.add(currColIndex, ""); 325 | currColIndex++; 326 | } 327 | } 328 | currRowList.add(currColIndex, value); 329 | currColIndex++; 330 | } else { 331 | // 如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法 332 | if (name.equals("row")) { 333 | // 默认第一行为表头,以该行单元格数目为最大数目 334 | if (currRowIndex == 0) { 335 | maxRef = ref; 336 | } 337 | // 补全一行尾部可能缺失的单元格 338 | if (maxRef != null) { 339 | int len = countNullCell(maxRef, ref); 340 | for (int i = 0; i <= len; i++) { 341 | currRowList.add(currColIndex, ""); 342 | currColIndex++; 343 | } 344 | } 345 | try { 346 | context.newRow(currRowList); 347 | } catch (Exception e) { 348 | // do nothing 349 | } 350 | currRowList = Lists.newArrayListWithExpectedSize(currRowList.size()); 351 | currRowIndex++; 352 | currColIndex = 0; 353 | preRef = null; 354 | ref = null; 355 | } 356 | } 357 | } 358 | 359 | /** 360 | * 计算两个单元格之间的单元格数目(同一行) 361 | * 362 | * @param ref 363 | * @param preRef 364 | * @return 365 | */ 366 | private int countNullCell(String ref, String preRef) { 367 | // excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD 368 | String xfd = ref.replaceAll("\\d+", ""); 369 | String xfd_1 = preRef.replaceAll("\\d+", ""); 370 | 371 | xfd = fillChar(xfd, 3, '@', true); 372 | xfd_1 = fillChar(xfd_1, 3, '@', true); 373 | 374 | char[] letter = xfd.toCharArray(); 375 | char[] letter_1 = xfd_1.toCharArray(); 376 | int res = (letter[0] - letter_1[0]) * 26 * 26 + (letter[1] - letter_1[1]) * 26 + (letter[2] - letter_1[2]); 377 | return res - 1; 378 | } 379 | 380 | /** 381 | * 字符串的填充 382 | * 383 | * @param str 384 | * @param len 385 | * @param let 386 | * @param isPre 387 | * @return 388 | */ 389 | private String fillChar(String str, int len, char let, boolean isPre) { 390 | int len_1 = str.length(); 391 | if (len_1 < len) { 392 | if (isPre) { 393 | for (int i = 0; i < (len - len_1); i++) { 394 | str = let + str; 395 | } 396 | } else { 397 | for (int i = 0; i < (len - len_1); i++) { 398 | str = str + let; 399 | } 400 | } 401 | } 402 | return str; 403 | } 404 | 405 | @Override 406 | public void characters(char[] ch, int start, int length) throws SAXException { 407 | // 得到单元格内容的值 408 | lastContents.append(new String(ch, start, length)); 409 | } 410 | } -------------------------------------------------------------------------------- /src/test/java/com/github/Dorae132/easyutil/easyexcel/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.Dorae132.easyutil.easyexcel; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | import org.junit.Test; 9 | 10 | import com.github.Dorae132.easyutil.easyexcel.common.Pair; 11 | import com.github.Dorae132.easyutil.easyexcel.export.DefaultWorkbookProcessor; 12 | import com.github.Dorae132.easyutil.easyexcel.export.ExcelCol; 13 | import com.github.Dorae132.easyutil.easyexcel.export.FillSheetModeEnums; 14 | import com.github.Dorae132.easyutil.easyexcel.export.IDataSupplier; 15 | import com.github.Dorae132.easyutil.easyexcel.read.IReadDoneCallBack; 16 | import com.github.Dorae132.easyutil.easyexcel.read.IRowConsumer; 17 | import com.google.common.collect.Lists; 18 | 19 | @SuppressWarnings("unchecked") 20 | public class UtilsTest { 21 | 22 | static class TestValue { 23 | @ExcelCol(title = "姓名") 24 | private String name; 25 | @ExcelCol(title = "年龄", order = 1) 26 | private String age; 27 | @ExcelCol(title = "学校", order = 3) 28 | private String school; 29 | @ExcelCol(title = "年级", order = 2) 30 | private String clazz; 31 | 32 | public TestValue(String name, String age, String school, String clazz) { 33 | super(); 34 | this.name = name; 35 | this.age = age; 36 | this.school = school; 37 | this.clazz = clazz; 38 | } 39 | } 40 | 41 | private static List getData(int count) { 42 | List dataList = Lists.newArrayListWithCapacity(count); 43 | for (int i = 0; i < count; i++) { 44 | dataList.add(new TestValue("张三" + i, "age: " + i, null, "clazz: " + i)); 45 | } 46 | return dataList; 47 | } 48 | 49 | // @Test 50 | // public void testCommonMode() throws Exception { 51 | // List dataList = getData(100000); 52 | // long start = System.currentTimeMillis(); 53 | // ExcelProperties properties = 54 | // ExcelProperties.produceCommonProperties("", dataList, 55 | // "C:\\Users\\Dorae\\Desktop\\ttt\\", "common.xlsx", 0, null, 0, null); 56 | // File file = (File) ExcelUtils.excelExport(properties, 57 | // FillSheetModeEnums.COMMON_MODE.getValue()); 58 | // System.out.println("commonMode: " + (System.currentTimeMillis() - start)); 59 | // } 60 | 61 | @Test 62 | public static void testAppend() throws Exception { 63 | List dataList = getData(100000); 64 | long start = System.currentTimeMillis(); 65 | ExcelProperties properties = ExcelProperties.produceAppendProperties("", 66 | "C:\\Users\\Dorae\\Desktop\\ttt\\", "append.xlsx", 0, TestValue.class, 0, new DefaultWorkbookProcessor(), 67 | new IDataSupplier() { 68 | private int i = 0; 69 | 70 | @Override 71 | public Pair, Boolean> getDatas() { 72 | boolean hasNext = i < 9; 73 | i++; 74 | return Pair.of(dataList, hasNext); 75 | } 76 | }); 77 | File file = ExcelUtils.excelExport(properties, FillSheetModeEnums.PARALLEL_APPEND_MODE.getValue()); 78 | System.out.println("apendMode: " + (System.currentTimeMillis() - start)); 79 | } 80 | 81 | @Test 82 | public static void testRead() throws Exception { 83 | AtomicInteger count = new AtomicInteger(0); 84 | long start = System.currentTimeMillis(); 85 | ExcelUtils.excelRead(ExcelProperties.produceReadProperties("C:\\Users\\Dorae\\Desktop\\ttt\\", 86 | "append_7f4ef636ff5e47e19504e17d963c4026.xlsx"), new IRowConsumer() { 87 | @Override 88 | public void consume(List row) { 89 | System.out.println(row); 90 | count.incrementAndGet(); 91 | try { 92 | TimeUnit.MICROSECONDS.sleep(100); 93 | } catch (InterruptedException e) { 94 | // TODO Auto-generated catch block 95 | e.printStackTrace(); 96 | } 97 | } 98 | }, new IReadDoneCallBack() { 99 | @Override 100 | public Void call() { 101 | System.out.println( 102 | "end, count: " + count.get() + "\ntime: " + (System.currentTimeMillis() - start)); 103 | return null; 104 | } 105 | }, 3, true); 106 | System.out.println("main end" + count.get()); 107 | } 108 | 109 | public static void main(String[] args) throws Exception { 110 | // testAppend(); 111 | testRead(); 112 | } 113 | 114 | } 115 | --------------------------------------------------------------------------------