• Android修行手册 - 实现POI上万行的大数据量Excel读写操作,解决内存溢出


    👉关于作者

    专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)
    有什么需要欢迎底部卡片私我,交流让学习不再孤单

    在这里插入图片描述

    👉实践过程

    😜问题

    搞过POI的都知道,在处理Excel文件时,POI提供了两种模式:用户模式和SAX事件驱动模式。用户模式API丰富使用起来相对简单,但当遇到大文件、大量数据或复杂格式时,可能会导致内存溢出。因此,官方推荐使用SAX事件驱动模式来解析大型Excel文件。
    开始想解决方法之前,我们要先知道 Excel2003与Excel2007 的区别。

    Excel2003与Excel2007

    主要有两打区别,支持的最大行和最大列不同,当然是2007年以后的支持的更多啦。
    另一个区别就是存储方式的不同,2003版的是二进制存储,2007版的是基于XML的一种文本格式,大大提升性能和减少文件尺寸,而且支持性更广。
    我们重点关注2007以后的Excel功能。

    😜解决

    在这里插入图片描述
    下面将介绍如何使用SAX事件驱动模式读取大型Excel文件。
    在这里插入图片描述
    需要提前预备的三方JAR包,如果读者需要,可随时文章后面V联系我。

    SAX解析读取,xlsx

    首先,我们需要创建一个类,继承自DefaultHandler,并重写process()、startElement()、characters()和endElement()这四个方法。其中,process()方法用于遍历所有的sheet,依次调用startElement()、characters()和endElement()这三个方法。startElement()方法用于设定单元格的数字类型(如日期、数字、字符串等),characters()方法用于获取该单元格对应的索引值或内容值(如果单元格类型是字符串、INLINESTR、数字、日期则获取的是索引值;其他如布尔值、错误、公式则获取的是内容值)。endElement()方法根据startElement()的单元格数字类型和characters()的索引值或内容值,就能得到并打印出单元格内的数据内容。

    package cn.akitaka.bigdatakt
    
    import org.apache.poi.openxml4j.opc.OPCPackage
    import org.apache.poi.ss.usermodel.BuiltinFormats
    import org.apache.poi.ss.usermodel.DataFormatter
    import org.apache.poi.xssf.eventusermodel.XSSFReader
    import org.apache.poi.xssf.eventusermodel.XSSFReader.SheetIterator
    import org.apache.poi.xssf.model.SharedStringsTable
    import org.apache.poi.xssf.model.StylesTable
    import org.apache.poi.xssf.usermodel.XSSFRichTextString
    import org.xml.sax.Attributes
    import org.xml.sax.InputSource
    import org.xml.sax.SAXException
    import org.xml.sax.helpers.DefaultHandler
    import org.xml.sax.helpers.XMLReaderFactory
    /**
     * Created by akitaka on 2023-11-06 960576866@qq.com 
     * @describe 需要用到 三方jar包 xercesImpl
     */
    class ExcelXlsxReader : DefaultHandler() {
        internal enum class CellDataType { //单元格中的数据可能的数据类型
            BOOL, ERROR, FORMULA, INLINESTR, SSTINDEX, NUMBER, DATE, NULL
        }
    
        private var sst: SharedStringsTable? = null //共享字符串表
        private var lastIndex: String? = null //上一次的索引值
        private var filePath = "" //文件的绝对路径
        private var sheetIndex = 0 //工作表索引
        private var sheetName = "" //sheet名
        private var totalRows = 0 //总行数
        private val cellList: MutableList<String?> = ArrayList() //一行内cell集合
        private var flag = false //判断整行是否为空行的标记
        private var curRow = 1 //当前行
        private var curCol = 0 //当前列
        private var isTElement = false //T元素标识
        private var startElementFlag = true //判断上一单元格是否为文本空单元格
        private var endElementFlag = false
        private var charactersFlag = false
        /**
         * @return the exceptionMessage
         */
        val exceptionMessage: String? = null //异常信息,如果为空则表示没有异常
        
        private var nextDataType = CellDataType.SSTINDEX //单元格数据类型,默认为字符串类型
        private val formatter = DataFormatter()
        private var formatIndex: Short = 0 //单元格日期格式的索引
        private var formatString: String? = null //日期格式字符串
    
        //定义前一个元素和当前元素的位置,用来计算其中空的单元格数量,如A6和A8等
        private var prePreRef: String? = "A"
        private var preRef: String? = null
        private var ref: String? = null
    
        //定义该文档一行最大的单元格数,用来补全一行最后可能缺失的单元格
        private var maxRef: String? = null
        private var stylesTable: StylesTable? = null  //单元格
    
        /**
         * 遍历工作簿中所有的电子表格
         * 并缓存在mySheetList中
         *
         * @param filename
         * @throws Exception
         */
        @Throws(Exception::class)
        fun process(filename: String): Int {
            filePath = filename
            val pkg = OPCPackage.open(filename)
            val xssfReader = XSSFReader(pkg)
            stylesTable = xssfReader.stylesTable
            val sst = xssfReader.sharedStringsTable
            val parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser")
            this.sst = sst
            parser.contentHandler = this
            val sheets = xssfReader.sheetsData as SheetIterator
            while (sheets.hasNext()) { //遍历sheet
                curRow = 1 //标记初始行为第一行
                sheetIndex++
                val sheet = sheets.next() //sheets.next()和sheets.getSheetName()不能换位置,否则sheetName报错
                sheetName = sheets.sheetName
                val sheetSource = InputSource(sheet)
                parser.parse(sheetSource) //解析excel的每条记录,在这个过程中startElement()、characters()、endElement()这三个函数会依次执行
                sheet.close()
            }
            return totalRows //返回该excel文件的总行数,不包括首列和空行
        }
    
        /**
         * 第一个执行
         *
         * @param uri
         * @param localName
         * @param name
         * @param attributes
         * @throws SAXException
         */
        @Throws(SAXException::class)
        override fun startElement(uri: String, localName: String, name: String, attributes: Attributes) {
            //c => 单元格
            if ("c" == name) {
    
                //前一个单元格的位置
                if (preRef == null) {
                    preRef = attributes.getValue("r")
                } else {
                    //中部文本空单元格标识 ‘endElementFlag’ 判断前一次是否为文本空字符串,true则表明不是文本空字符串,false表明是文本空字符串跳过把空字符串的位置赋予preRef
                    if (endElementFlag) {
                        preRef = ref
                    }
                }
    
                //当前单元格的位置
                ref = attributes.getValue("r")
                //首部文本空单元格标识 ‘startElementFlag’ 判断前一次,即首部是否为文本空字符串,true则表明不是文本空字符串,false表明是文本空字符串, 且已知当前格,即第二格带“B”标志,则ref赋予preRef
                if (!startElementFlag && !flag) { //上一个单元格为文本空单元格,执行下面的,使ref=preRef;flag为true表明该单元格之前有数据值,即该单元格不是首部空单元格,则跳过
                    // 这里只有上一个单元格为文本空单元格,且之前的几个单元格都没有值才会执行
                    preRef = ref
                }
    
                //设定单元格类型
                setNextDataType(attributes)
                endElementFlag = false
                charactersFlag = false
                startElementFlag = false
            }
            isTElement = "t" == name   //当元素为t时
            lastIndex = ""   //置空
        }
    
        /**
         * 第二个执行
         * 得到单元格对应的索引值或是内容值
         * 如果单元格类型是字符串、INLINESTR、数字、日期,lastIndex则是索引值
         * 如果单元格类型是布尔值、错误、公式,lastIndex则是内容值
         * @param ch
         * @param start
         * @param length
         * @throws SAXException
         */
        @Throws(SAXException::class)
        override fun characters(ch: CharArray, start: Int, length: Int) {
            startElementFlag = true
            charactersFlag = true
            lastIndex += String(ch, start, length)
        }
    
        /**
         * 第三个执行
         *
         * @param uri
         * @param localName
         * @param name
         * @throws SAXException
         */
        @Throws(SAXException::class)
        override fun endElement(uri: String, localName: String, name: String) {
            //t元素也包含字符串
            if (isTElement) { //这个程序没经过
                val value = lastIndex!!.trim { it <= ' ' } //将单元格内容加入rowList中,在这之前先去掉字符串前后的空白符
                cellList.add(curCol, value)
                endElementFlag = true
                curCol++
                isTElement = false
                if ("" != value) { //如果里面某个单元格含有值,则标识该行不为空行
                    flag = true
                }
            } else if ("v" == name) {
                //v => 单元格的值,如果单元格是字符串,则v标签的值为该字符串在SST中的索引
                val value = getDataValue(lastIndex!!.trim { it <= ' ' }, "") //根据索引值获取对应的单元格值
                //补全单元格之间的空单元格
                if (ref != preRef) {
                    val len = countNullCell(ref, preRef)
                    for (i in 0 until len) {
                        cellList.add(curCol, "")
                        curCol++
                    }
                } else if (ref == preRef && !ref!!.startsWith("A")) { //ref等于preRef,且以B或者C...开头,表明首部为空格
                    val len = countNullCell(ref, "A")
                    for (i in 0..len) {
                        cellList.add(curCol, "")
                        curCol++
                    }
                }
                cellList.add(curCol, value)
                curCol++
                endElementFlag = true
                //如果里面某个单元格含有值,则标识该行不为空行
                if ("" != value) {
                    flag = true
                }
            } else {
                //如果标签名称为row,这说明已到行尾,调用optRows()方法
                if ("row" == name) {
                    //默认第一行为表头,以该行单元格数目为最大数目
                    if (curRow == 1) {
                        maxRef = ref
                    }
                    //补全一行尾部可能缺失的单元格
                    if (maxRef != null) {
                        var len = -1
                        //前一单元格,true则不是文本空字符串,false则是文本空字符串
                        len = if (charactersFlag) {
                            countNullCell(maxRef, ref)
                        } else {
                            countNullCell(maxRef, preRef)
                        }
                        for (i in 0..len) {
                            cellList.add(curCol, "")
                            curCol++
                        }
                    }
                    if (flag && curRow != 1) { //该行不为空行且该行不是第一行,则发送(第一行为列名,不需要)
                        ExcelReaderUtil.sendRows(filePath, sheetName, sheetIndex, curRow, cellList)
                        totalRows++
                    }
                    cellList.clear()
                    curRow++
                    curCol = 0
                    preRef = null
                    prePreRef = null
                    ref = null
                    flag = false
                }
            }
        }
    
        /**
         * 处理数据类型
         *
         * @param attributes
         */
        fun setNextDataType(attributes: Attributes) {
            nextDataType = CellDataType.NUMBER //cellType为空,则表示该单元格类型为数字
            formatIndex = -1
            formatString = null
            val cellType = attributes.getValue("t") //单元格类型
            val cellStyleStr = attributes.getValue("s") //
            val columnData = attributes.getValue("r") //获取单元格的位置,如A1,B1
            when (cellType) {
                "b" -> nextDataType = CellDataType.BOOL  //处理布尔值
                "e" -> nextDataType = CellDataType.ERROR   //处理错误
                "inlineStr" -> nextDataType = CellDataType.INLINESTR
                "s" -> nextDataType = CellDataType.SSTINDEX  //处理字符串
                "str" -> nextDataType = CellDataType.FORMULA
            }
            if (cellStyleStr != null) { //处理日期
                val styleIndex = cellStyleStr.toInt()
                val style = stylesTable!!.getStyleAt(styleIndex)
                formatIndex = style.dataFormat
                formatString = style.dataFormatString
                if (formatString == null) {
                    nextDataType = CellDataType.NULL
                    formatString = BuiltinFormats.getBuiltinFormat(formatIndex.toInt())
                } else {
                    if (formatString!!.contains("m/d/yyyy") || formatString!!.contains("yyyy/mm/dd") || formatString!!.contains("yyyy/m/d")) {
                        nextDataType = CellDataType.DATE
                        formatString = "yyyy-MM-dd hh:mm:ss"
                    }
                }
            }
        }
    
        /**
         * 对解析出来的数据进行类型处理
         * @param value   单元格的值,
         * value代表解析:BOOL的为0或1, ERROR的为内容值,FORMULA的为内容值,INLINESTR的为索引值需转换为内容值,
         * SSTINDEX的为索引值需转换为内容值, NUMBER为内容值,DATE为内容值
         * @param thisStr 一个空字符串
         * @return
         */
        fun getDataValue(value: String, thisStr: String): String {
            var thisStr = thisStr
            when (nextDataType) {
                CellDataType.BOOL -> {
                    val first = value[0]
                    thisStr = if (first == '0') "FALSE" else "TRUE"
                }
                CellDataType.ERROR -> thisStr = "\"ERROR:$value\""
                CellDataType.FORMULA -> thisStr = '"'.toString() + value + '"'
                CellDataType.INLINESTR -> {
                    var rtsi: XSSFRichTextString? = XSSFRichTextString(value)
                    thisStr = rtsi.toString()
                    rtsi = null
                }
                CellDataType.SSTINDEX -> {
                    try {
                        val idx = value.toInt()
                        var rtss: XSSFRichTextString? = XSSFRichTextString(sst!!.getEntryAt(idx)) //根据idx索引值获取内容值
                        thisStr = rtss.toString()
                        println(thisStr)
                        //有些字符串是文本格式的,但内容却是日期
                        rtss = null
                    } catch (ex: NumberFormatException) {
                        thisStr = value
                    }
                }
                CellDataType.NUMBER -> {
                    thisStr = if (formatString != null) {
                        formatter.formatRawCellContents(value.toDouble(), formatIndex.toInt(), formatString).trim { it <= ' ' }
                    } else {
                        value
                    }
                    thisStr = thisStr.replace("_", "").trim { it <= ' ' }
                }
                CellDataType.DATE -> {
                    thisStr = formatter.formatRawCellContents(value.toDouble(), formatIndex.toInt(), formatString)
                    // 对日期字符串作特殊处理,去掉T
                    thisStr = thisStr.replace("T", " ")
                }
                else -> thisStr = " "
            }
            return thisStr
        }
    
        fun countNullCell(ref: String?, preRef: String?): Int {
            //excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD
            var xfd = ref!!.replace("\\d+".toRegex(), "")
            var xfd_1 = preRef!!.replace("\\d+".toRegex(), "")
            xfd = fillChar(xfd, 3, '@', true)
            xfd_1 = fillChar(xfd_1, 3, '@', true)
            val letter = xfd.toCharArray()
            val letter_1 = xfd_1.toCharArray()
            val res = (letter[0].code - letter_1[0].code) * 26 * 26 + (letter[1].code - letter_1[1].code) * 26 + (letter[2].code - letter_1[2].code)
            return res - 1
        }
    
        fun fillChar(str: String, len: Int, let: Char, isPre: Boolean): String {
            var str = str
            val len_1 = str.length
            if (len_1 < len) {
                if (isPre) {
                    for (i in 0 until len - len_1) {
                        str = let.toString() + str
                    }
                } else {
                    for (i in 0 until len - len_1) {
                        str += let
                    }
                }
            }
            return str
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343

    继承HSSFListener类来解决Excel2003文件,xls

    我们需要创建一个类,继承自HSSFListener,并重写processRecord()方法。这个方法是核心方法,用于处理sheetName和各种单元格数字类型。

    package cn.akitaka.bigdatakt
    
    import org.apache.poi.hssf.eventusermodel.*
    import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener
    import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord
    import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord
    import org.apache.poi.hssf.model.HSSFFormulaParser
    import org.apache.poi.hssf.record.*
    import org.apache.poi.hssf.usermodel.HSSFDataFormatter
    import org.apache.poi.hssf.usermodel.HSSFWorkbook
    import org.apache.poi.poifs.filesystem.POIFSFileSystem
    import java.io.FileInputStream
    
    /**
     * Created by akitaka on 2023-11-06 960576866@qq.com 
     * @describe 
     */
    class ExcelXlsReader : HSSFListener {
        private val minColums = -1
        private var fs: POIFSFileSystem? = null
        private var totalColums = 0  //把第一行列名的长度作为列的总长
        private var totalRows = 0  //总行数
        private var lastRowNumber = 0  //上一行row的序号
        private var lastColumnNumber = 0  //上一单元格的序号
        private val outputFormulaValues = true  //是否输出formula,还是它对应的值
        private var workbookBuildingListener: SheetRecordCollectingListener? = null  //用于转换formulas
    
        //excel2003工作簿
        private var stubWorkbook: HSSFWorkbook? = null
        private var sstRecord: SSTRecord? = null
        private var formatListener: FormatTrackingHSSFListener? = null
        private val formatter = HSSFDataFormatter()
        private var filePath = ""  //文件的绝对路径
    
        //表索引
        private var sheetIndex = 0
        private var orderedBSRs: Array<BoundSheetRecord>? = null
        private val boundSheetRecords:MutableList<BoundSheetRecord> = ArrayList()
        private var nextRow = 0
        private var nextColumn = 0
        private var outputNextStringRecord = false
    
        //当前行
        private var curRow = 0
    
        //存储一行记录所有单元格的容器
        private val cellList: MutableList<String?> = ArrayList()
        private var flag = false  //判断整行是否为空行的标记
        private var sheetName: String? = null
    
        /**
         * 遍历excel下所有的sheet
         */
        @Throws(Exception::class)
        fun process(fileName: String): Int {
            filePath = fileName
            fs = POIFSFileSystem(FileInputStream(fileName))
            val listener = MissingRecordAwareHSSFListener(this)
            formatListener = FormatTrackingHSSFListener(listener)
            val factory = HSSFEventFactory()
            val request = HSSFRequest()
            if (outputFormulaValues) {
                request.addListenerForAllRecords(formatListener)
            } else {
                workbookBuildingListener = SheetRecordCollectingListener(formatListener)
                request.addListenerForAllRecords(workbookBuildingListener)
            }
            factory.processWorkbookEvents(request, fs)
            return totalRows //返回该excel文件的总行数,不包括首列和空行
        }
    
        /**
         * HSSFListener 监听方法,处理Record
         * 处理每个单元格
         */
        override fun processRecord(record: Record) {
            var thisRow = -1
            var thisColumn = -1
            var thisStr: String? = null
            var value: String? = null
            when (record.sid) {
                BoundSheetRecord.sid -> boundSheetRecords.add(record as BoundSheetRecord)
                BOFRecord.sid -> {
                    val br = record as BOFRecord
                    if (br.type == BOFRecord.TYPE_WORKSHEET) {
                        //如果有需要,则建立子工作簿
                        if (workbookBuildingListener != null && stubWorkbook == null) {
                            stubWorkbook = workbookBuildingListener!!.stubHSSFWorkbook
                        }
                        if (orderedBSRs == null) {
                            orderedBSRs = BoundSheetRecord.orderByBofPosition(boundSheetRecords)
                        }
                        sheetName = orderedBSRs!![sheetIndex].sheetname
                        sheetIndex++
                    }
                }
                SSTRecord.sid -> sstRecord = record as SSTRecord
                BlankRecord.sid -> {
                    val brec = record as BlankRecord
                    thisRow = brec.row
                    thisColumn = brec.column.toInt()
                    thisStr = ""
                    cellList.add(thisColumn, thisStr)
                }
                BoolErrRecord.sid -> {
                    val berec = record as BoolErrRecord
                    thisRow = berec.row
                    thisColumn = berec.column.toInt()
                    thisStr = berec.booleanValue.toString() + ""
                    cellList.add(thisColumn, thisStr)
                    checkRowIsNull(thisStr) //如果里面某个单元格含有值,则标识该行不为空行
                }
                FormulaRecord.sid -> {
                    val frec = record as FormulaRecord
                    thisRow = frec.row
                    thisColumn = frec.column.toInt()
                    if (outputFormulaValues) {
                        if (java.lang.Double.isNaN(frec.value)) {
                            outputNextStringRecord = true
                            nextRow = frec.row
                            nextColumn = frec.column.toInt()
                        } else {
                            thisStr = '"'.toString() + HSSFFormulaParser.toFormulaString(stubWorkbook, frec.parsedExpression) + '"'
                        }
                    } else {
                        thisStr = '"'.toString() + HSSFFormulaParser.toFormulaString(stubWorkbook, frec.parsedExpression) + '"'
                    }
                    cellList.add(thisColumn, thisStr)
                    checkRowIsNull(thisStr) //如果里面某个单元格含有值,则标识该行不为空行
                }
                StringRecord.sid -> if (outputNextStringRecord) {
                    val srec = record as StringRecord
                    thisStr = srec.string
                    thisRow = nextRow
                    thisColumn = nextColumn
                    outputNextStringRecord = false
                }
                LabelRecord.sid -> {
                    val lrec = record as LabelRecord
                    run {
                        thisRow = lrec.row
                        curRow = thisRow
                    }
                    thisColumn = lrec.column.toInt()
                    value = lrec.value.trim { it <= ' ' }
                    value = if (value == "") "" else value
                    cellList.add(thisColumn, value)
                    checkRowIsNull(value) //如果里面某个单元格含有值,则标识该行不为空行
                }
                LabelSSTRecord.sid -> {
                    val lsrec = record as LabelSSTRecord
                    run {
                        thisRow = lsrec.row
                        curRow = thisRow
                    }
                    thisColumn = lsrec.column.toInt()
                    if (sstRecord == null) {
                        cellList.add(thisColumn, "")
                    } else {
                        value = sstRecord!!.getString(lsrec.sstIndex).toString().trim { it <= ' ' }
                        value = if (value == "") "" else value
                        cellList.add(thisColumn, value)
                        checkRowIsNull(value) //如果里面某个单元格含有值,则标识该行不为空行
                    }
                }
                NumberRecord.sid -> {
                    val numrec = record as NumberRecord
                    run {
                        thisRow = numrec.row
                        curRow = thisRow
                    }
                    thisColumn = numrec.column.toInt()
    
                    //第一种方式
                    //value = formatListener.formatNumberDateCell(numrec).trim();//这个被写死,采用的m/d/yy h:mm格式,不符合要求
    
                    //第二种方式,参照formatNumberDateCell里面的实现方法编写
                    val valueDouble = numrec.value
                    var formatString = formatListener!!.getFormatString(numrec)
                    if (formatString.contains("m/d/yy") || formatString.contains("yyyy/mm/dd") || formatString.contains("yyyy/m/d")) {
                        formatString = "yyyy-MM-dd hh:mm:ss"
                    }
                    val formatIndex = formatListener!!.getFormatIndex(numrec)
                    value = formatter.formatRawCellContents(valueDouble, formatIndex, formatString).trim { it <= ' ' }
                    value = if (value == "") "" else value
                    //向容器加入列值
                    cellList.add(thisColumn, value)
                    checkRowIsNull(value) //如果里面某个单元格含有值,则标识该行不为空行
                }
                else -> {}
            }
    
            //遇到新行的操作
            if (thisRow != -1 && thisRow != lastRowNumber) {
                lastColumnNumber = -1
            }
    
            //空值的操作
            if (record is MissingCellDummyRecord) {
                val mc = record
                thisRow = mc.row
                curRow = thisRow
                thisColumn = mc.column
                cellList.add(thisColumn, "")
            }
    
            //更新行和列的值
            if (thisRow > -1) lastRowNumber = thisRow
            if (thisColumn > -1) lastColumnNumber = thisColumn
    
            //行结束时的操作
            if (record is LastCellOfRowDummyRecord) {
                if (minColums > 0) {
                    //列值重新置空
                    if (lastColumnNumber == -1) {
                        lastColumnNumber = 0
                    }
                }
                lastColumnNumber = -1
                if (flag) { //该行不为空行且该行不是第一行,发送(第一行为列名,不需要)
                    if (curRow == 0) {
                        totalColums = cellList.size //获取第一行列名的总数
                    } else {
                        //2003版尾部为空单元格的,xls里面是以该行最后一个有值的单元格为结束标记的,尾部空单元格跳过,故需补全
                        if (cellList.size <= totalColums) { // 其他行如果尾部单元格总数小于totalColums,则补全单元格
                            for (i in cellList.size until totalColums) {
                                cellList.add(i, "")
                            }
                        }
                        ExcelReaderUtil.sendRows(filePath, sheetName, sheetIndex, curRow + 1, cellList) //每行结束时,调用sendRows()方法
                        totalRows++
                    }
                }
                //清空容器
                cellList.clear()
                flag = false
            }
        }
    
        /**
         * 如果里面某个单元格含有值,则标识该行不为空行
         * @param value
         */
        fun checkRowIsNull(value: String?) {
            if (value != null && "" != value) {
                flag = true
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249

    使用帮助类

    package cn.akitaka.bigdatakt
    
    import java.io.File
    import java.io.FileInputStream
    import java.io.FileOutputStream
    
    /**
     * Created by akitaka on 2023-11-06 960576866@qq.com 
     * @describe 
     */
    
    object ExcelReaderUtil {
        //excel2003扩展名
        const val EXCEL03_EXTENSION = ".xls"
    
        //excel2007扩展名
        const val EXCEL07_EXTENSION = ".xlsx"
    
        /**
         * 每获取一条记录,即打印
         * 在flume里每获取一条记录即发送,而不必缓存起来,可以大大减少内存的消耗,这里主要是针对flume读取大数据量excel来说的
         */
        fun sendRows(filePath: String?, sheetName: String?, sheetIndex: Int, curRow: Int, cellList: List<String?>) {
            val oneLineSb = StringBuffer()
            oneLineSb.append(filePath)
            oneLineSb.append("--")
            oneLineSb.append("sheet$sheetIndex")
            oneLineSb.append("::$sheetName") //加上sheet名
            oneLineSb.append("--")
            oneLineSb.append("row$curRow")
            oneLineSb.append("::")
            for (cell in cellList) {
                oneLineSb.append(cell!!.trim { it <= ' ' })
                oneLineSb.append("|")
            }
            var oneLine = oneLineSb.toString()
            if (oneLine.endsWith("|")) {
                oneLine = oneLine.substring(0, oneLine.lastIndexOf("|"))
            } // 去除最后一个分隔符
            println(oneLine)
        }
    
        @Throws(Exception::class)
        fun readExcel(fileName: String) {
            var totalRows = 0
            totalRows = if (fileName.endsWith(EXCEL03_EXTENSION)) { //处理excel2003文件
                val excelXls = ExcelXlsReader()
                excelXls.process(fileName)
            } else if (fileName.endsWith(EXCEL07_EXTENSION)) { //处理excel2007文件
                val excelXlsxReader = ExcelXlsxReader()
                excelXlsxReader.process(fileName)
            } else {
                throw Exception("文件格式错误,fileName的扩展名只能是xls或xlsx。")
            }
            println("发送的总行数:$totalRows")
        }
    
        @Throws(Exception::class)
        fun copyToTemp(file: File?, tmpDir: String?) {
            val fis = FileInputStream(file)
            val file1 = File(tmpDir)
            if (file1.exists()) {
                file1.delete()
            }
            val fos = FileOutputStream(tmpDir)
            val b = ByteArray(1024)
            var n = 0
            while (fis.read(b).also { n = it } != -1) {
                fos.write(b, 0, n)
            }
            fis.close()
            fos.close()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    写入

    对于大数据的Xlsx文件的写入,POI3.8提供了SXSSFSXSSFWorkbook类,采用缓存方式进行大批量写文件。
    详情可以查看poi官网示例:跳转链接

    核心原理便是通过设置SXXFWorkbook的构造参数,可以设置每次在内存中保持的行数,当达到这个值的时候,那么会把这些数据flush到磁盘上,这样就不会出现内存不够的情况。

    因为每人具体写入的内容不同,这个封装工具类麻烦。

    推荐一个开源项目:跳转 里面有 hutool-poi 对 Excel 写入进行了封装
    文档查看

    👉其他

    📢作者:小空和小芝中的小空
    📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
    📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。

    温馨提示点击下方卡片获取更多意想不到的资源。
    空名先生

  • 相关阅读:
    janus-gateway安装(docker方式)(centos7)
    等参单元与数值积分
    【笔记】samba shell 脚本 离线安装 - Ubuntu 20.04
    settings.xml的文件配置大全
    二分查找以及扩展
    iclr 2023 投稿指南
    ABAP语法基础2
    使用jxls excel模板填充生成Excel,多sheet页处理
    7、MySQL Workbench 导出导入数据库
    万物皆可集成系列:低代码对接阿里物流API实现快递跟踪
  • 原文地址:https://blog.csdn.net/qq_27489007/article/details/134189117