- 点击跳转=>Unity3D特效百例
- 点击跳转=>案例项目实战源码
- 点击跳转=>游戏脚本-辅助自动化
- 点击跳转=>Android控件全解手册
- 点击跳转=>Scratch编程案例
- 点击跳转=>软考全系列
👉关于作者
专注于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
}
}
继承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
}
}
}
使用帮助类
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()
}
}
写入
对于大数据的Xlsx文件的写入,POI3.8提供了SXSSFSXSSFWorkbook类,采用缓存方式进行大批量写文件。
详情可以查看poi官网示例:跳转链接
核心原理便是通过设置SXXFWorkbook的构造参数,可以设置每次在内存中保持的行数,当达到这个值的时候,那么会把这些数据flush到磁盘上,这样就不会出现内存不够的情况。
因为每人具体写入的内容不同,这个封装工具类麻烦。
推荐一个开源项目:跳转 里面有 hutool-poi 对 Excel 写入进行了封装
文档查看
👉其他
📢作者:小空和小芝中的小空
📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。
温馨提示:点击下方卡片获取更多意想不到的资源。