前言:
在一些具体的业务实现中经常要使用Excel作为数据的来源,或者导出一些Excel。
一般我们都是使用POI(Java)/NPOI(.Net)来读取或写入相关的数据。
这个月接手别人的模块,在模块中需要频繁的导入导出Excel操作,在给那些‘测试’对接时,经常出现导入失败的情况,非常烦人。
这大概率是POI/NPOI的行数和实际导入的Excel有效行数不一样造成的
问题再现
这里以下面的一个Excel为例
这个Excel没有改格式也没有加空格,它的行数统计如下
XSSFWorkbook book = new XSSFWorkbook(new FileInputStream("src/Test.xlsx"));
//结果为8,sheet最后的一行有数据行的行数(从0开始算)
System.out.println(book.getSheetAt(0).getLastRowNum());
//结果为9,sheet实际有数据的总行数
System.out.println(book.getSheetAt(0).getPhysicalNumberOfRows());
这里随便插入一行再运行便可再次测试
XSSFWorkbook book = new XSSFWorkbook(new FileInputStream("src/Test.xlsx"));
//结果为12
System.out.println(book.getSheetAt(0).getLastRowNum());
//结果为10
System.out.println(book.getSheetAt(0).getPhysicalNumberOfRows());
1.空格造成的行数错误
导入者不仔细造成的多余空格行通常是这种错误的原因。
模拟:我在黑框处添加了空格
这次会得到
XSSFWorkbook book = new XSSFWorkbook(new FileInputStream("src/Test.xlsx"));
//15
System.out.println(book.getSheetAt(0).getLastRowNum());
//11
System.out.println(book.getSheetAt(0).getPhysicalNumberOfRows());
这样,当在程序中使用判断行数的方法时便会出现偏差,导致异常及错误
像是
- 获取没有值的单元格的值-------------------------------空指针
- 获取一些空格的值进入Sql参数------------------------Sql语句错误
2.格式造成的行数错误
请注意,有些情况下,导入的Excel的Sheet中存在不同的格式,并不是所有的导入都是默认的通用格式,
当我选定一片区域改成别的格式的时候,POI判断的长度也会改变
SSFWorkbook book = new XSSFWorkbook(new FileInputStream("src/Test.xlsx"));
//18
System.out.println(book.getSheetAt(0).getLastRowNum());
//19
System.out.println(book.getSheetAt(0).getPhysicalNumberOfRows());
有些时候对某些行列改变格式,也会造成POI的行数错误。
(之前在工作中就有过经历,约定以文本的格式导入Excel,结果不知道什么原因,有人用文本格式拉了几千行,
DEBUG的时候看到NPOI的获取Sheet的行数上千就非常的懵逼)
结论:
故决定不用POI/NPO提供的获取行数的API,必须自己手动计算一下,不然保不齐对接的时候一堆奇奇怪怪的导入出错
手动计算行数
POI
手动计算Sheet的长度要做到一定正确也需要一定的规则
我们一般
默认导入Excel的Sheet里的数据不要有中断的空行,即Excel的数据行从首行(一般是列名)开始,一直连续
所以我们只需要判断单元格的值是否为Null或者是空格(多个空格)且一直连续了多少行,
下面给出测试代码
/***
* 前言:如果你有自己常用的String处理方法,或者框架里的字符串工具,你完全可以不使用下面的流操作判断是否为空字符串
*
* @param sheet 表
* @param StartNum 数据开始的行号(即除去你的表头占的行数,真正的数据从哪行开始的)
* @param RequireCol_index 必要数据列的列号(导入Excel中任意一个不为空列的列号即可)
* @return Excel的sheet的有效行数
*/
public static int GetRealRowNum(Sheet sheet,int StartNum,int RequireCol_index) {
int Count = 0;
//这里第一个条件可以判断单元格非空,第二个则利用流操作去判断是否其中所有的字符全是空字符或空格字符。
while(sheet.getRow(StartNum).getCell(RequireCol_index)!=null&&
!ToList(sheet.getRow(StartNum).getCell(RequireCol_index).toString().toCharArray())
.stream().allMatch((Character item)->{
if(item.equals(' ')||item.toString().equals(""))
return true;
else
return false;
})
)
{
//用Count计算有效长度
StartNum++;Count++;
}
}catch (Exception e) {
}
return Count;
}
public static List<Character> ToList(char[] charArray){
List<Character> chars = new ArrayList<Character>();
for(int i=0;i<charArray.length;i++)
chars.add(charArray[i]);
return chars;
}
测试一下,
还是上面那张被修改了格式的Excel
在里面随机埋了写空格,并选中一块敌法更改格式,得到的结果如下
public static void main(String[] args) throws IOException {
XSSFWorkbook book = new XSSFWorkbook(new FileInputStream("src/Test.xlsx"));
//28
System.out.println(book.getSheetAt(0).getLastRowNum());
//24
System.out.println(book.getSheetAt(0).getPhysicalNumberOfRows());
//8
System.out.println(GetRealRowNum(book.getSheetAt(0), 1, 0));
}
大多数情况下是可以用的,但实际的工作中肯定是使用字符串工具来判断是否为空格字符串或空字符串。
//直接用字符串工具久非常的简单明了
while(sheet.getRow(StartNum).getCell(RequireCol_index)!=null&&
!StringUtil.isBlank(sheet.getRow(StartNum).getCell(RequireCol_index).toString()))
{
StartNum++;Count++;
}
结果也是上面一样。
注:(StringUtils/StringUtils有非常多的同名类,且方法也会各有不同。例子用到的是org.jsoup.internal.StringUtil)
NPOI
NPOI的使用方法也类似,
int start = 1,
count_row = 0;
try
{
while (sheet.GetRow(start).GetCell(0) != null && !string.IsNullOrWhiteSpace((sheet.GetRow(start).GetCell(0).ToString()))
{
count_row++;start++;
}
}catch(Exception e)
{
}
return count_row ;