前言
apache poi [1] 是用java编写的免费开源的跨平台的 java api,apache poi提供api给java程式对microsoft office格式档案读和写的功能。poi为“poor obfuscation implementation”的首字母缩写,意为“简洁版的模糊实现”。
做项目时经常有通过程序读取excel数据,或是创建新的excel并写入数据的需求;
网上很多经验教程里使用的poi版本都比较老了,一些api在新版里已经废弃,这里基于最新的apache poi 4.0.1版本来总结一下整个读取和写入excel的过程,希望能帮助到需要的人 ^_^
1. 准备工作
1.1 在项目中引入apache poi相关类库
引入 apache poi 和 apache poi-ooxml 这两个类库,maven坐标如下:
1
2
3
4
5
6
7
8
9
10
|
<dependency>
<groupid>org.apache.poi</groupid>
<artifactid>poi</artifactid>
<version> 4.0 . 1 </version>
</dependency>
<dependency>
<groupid>org.apache.poi</groupid>
<artifactid>poi-ooxml</artifactid>
<version> 4.0 . 1 </version>
</dependency>
|
2. 读取或写入excel数据
2.1 示例程序结构说明
简单说明一下示例程序的整体结构:
- excelreader.java是实现读取excel数据功能的类;
- excelwriter.java是创建新的excel并向其中写入数据的类;
- exceldatavo.java封装了读取或写入时每一“行”的数据;
- maintest.java是示例程序的入口类,其中演示了读取和写入excel数据的整个过程;
2.2 读取数据
示例程序需要从桌面读取 readexample.xlsx 内的数据,readexample.xlsx 的内容如下:
读取excel时主要调用excelreader.java类来读取和解析excel的具体内容,这里以读取系统文件的形式演示读取过程:(兼容 xls 和 xlsx)
2.2.1 主程序入口类代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/**
* author: dreamer-1
* date: 2019-03-01
* time: 10:13
* description: 示例程序入口类
*/
public class maintest {
public static void main(string[] args) {
// 设定excel文件所在路径
string excelfilename = "/users/dreamer-1/desktop/myblog/java解析excel/readexample.xlsx" ;
// 读取excel文件内容
list<exceldatavo> readresult = excelreader.readexcel(excelfilename);
// todo 进行业务操作
}
}
|
读取和写入时封装每一“行”数据的exceldatavo.java代码如下:
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
|
/**
* author: dreamer-1
* date: 2019-03-01
* time: 11:33
* description: 读取excel时,封装读取的每一行的数据
*/
public class exceldatavo {
/**
* 姓名
*/
private string name;
/**
* 年龄
*/
private integer age;
/**
* 居住地
*/
private string location;
/**
* 职业
*/
private string job;
public string getname() {
return name;
}
public void setname(string name) {
this .name = name;
}
public integer getage() {
return age;
}
public void setage(integer age) {
this .age = age;
}
public string getlocation() {
return location;
}
public void setlocation(string location) {
this .location = location;
}
public string getjob() {
return job;
}
public void setjob(string job) {
this .job = job;
}
}
|
2.2.2 excel解析类的代码:
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
|
/**
* author: dreamer-1
* date: 2019-03-01
* time: 10:21
* description: 读取excel内容
*/
public class excelreader {
private static logger logger = logger.getlogger(excelreader. class .getname()); // 日志打印类
private static final string xls = "xls" ;
private static final string xlsx = "xlsx" ;
/**
* 根据文件后缀名类型获取对应的工作簿对象
* @param inputstream 读取文件的输入流
* @param filetype 文件后缀名类型(xls或xlsx)
* @return 包含文件数据的工作簿对象
* @throws ioexception
*/
public static workbook getworkbook(inputstream inputstream, string filetype) throws ioexception {
workbook workbook = null ;
if (filetype.equalsignorecase(xls)) {
workbook = new hssfworkbook(inputstream);
} else if (filetype.equalsignorecase(xlsx)) {
workbook = new xssfworkbook(inputstream);
}
return workbook;
}
/**
* 读取excel文件内容
* @param filename 要读取的excel文件所在路径
* @return 读取结果列表,读取失败时返回null
*/
public static list<exceldatavo> readexcel(string filename) {
workbook workbook = null ;
fileinputstream inputstream = null ;
try {
// 获取excel后缀名
string filetype = filename.substring(filename.lastindexof( "." ) + 1 , filename.length());
// 获取excel文件
file excelfile = new file(filename);
if (!excelfile.exists()) {
logger.warning( "指定的excel文件不存在!" );
return null ;
}
// 获取excel工作簿
inputstream = new fileinputstream(excelfile);
workbook = getworkbook(inputstream, filetype);
// 读取excel中的数据
list<exceldatavo> resultdatalist = parseexcel(workbook);
return resultdatalist;
} catch (exception e) {
logger.warning( "解析excel失败,文件名:" + filename + " 错误信息:" + e.getmessage());
return null ;
} finally {
try {
if ( null != workbook) {
workbook.close();
}
if ( null != inputstream) {
inputstream.close();
}
} catch (exception e) {
logger.warning( "关闭数据流出错!错误信息:" + e.getmessage());
return null ;
}
}
}
/**
* 解析excel数据
* @param workbook excel工作簿对象
* @return 解析结果
*/
private static list<exceldatavo> parseexcel(workbook workbook) {
list<exceldatavo> resultdatalist = new arraylist<>();
// 解析sheet
for ( int sheetnum = 0 ; sheetnum < workbook.getnumberofsheets(); sheetnum++) {
sheet sheet = workbook.getsheetat(sheetnum);
// 校验sheet是否合法
if (sheet == null ) {
continue ;
}
// 获取第一行数据
int firstrownum = sheet.getfirstrownum();
row firstrow = sheet.getrow(firstrownum);
if ( null == firstrow) {
logger.warning( "解析excel失败,在第一行没有读取到任何数据!" );
}
// 解析每一行的数据,构造数据对象
int rowstart = firstrownum + 1 ;
int rowend = sheet.getphysicalnumberofrows();
for ( int rownum = rowstart; rownum < rowend; rownum++) {
row row = sheet.getrow(rownum);
if ( null == row) {
continue ;
}
exceldatavo resultdata = convertrowtodata(row);
if ( null == resultdata) {
logger.warning( "第 " + row.getrownum() + "行数据不合法,已忽略!" );
continue ;
}
resultdatalist.add(resultdata);
}
}
return resultdatalist;
}
/**
* 将单元格内容转换为字符串
* @param cell
* @return
*/
private static string convertcellvaluetostring(cell cell) {
if (cell== null ){
return null ;
}
string returnvalue = null ;
switch (cell.getcelltype()) {
case numeric: //数字
double doublevalue = cell.getnumericcellvalue();
// 格式化科学计数法,取一位整数
decimalformat df = new decimalformat( "0" );
returnvalue = df.format(doublevalue);
break ;
case string: //字符串
returnvalue = cell.getstringcellvalue();
break ;
case boolean : //布尔
boolean booleanvalue = cell.getbooleancellvalue();
returnvalue = booleanvalue.tostring();
break ;
case blank: // 空值
break ;
case formula: // 公式
returnvalue = cell.getcellformula();
break ;
case error: // 故障
break ;
default :
break ;
}
return returnvalue;
}
/**
* 提取每一行中需要的数据,构造成为一个结果数据对象
*
* 当该行中有单元格的数据为空或不合法时,忽略该行的数据
*
* @param row 行数据
* @return 解析后的行数据对象,行数据错误时返回null
*/
private static exceldatavo convertrowtodata(row row) {
exceldatavo resultdata = new exceldatavo();
cell cell;
int cellnum = 0 ;
// 获取姓名
cell = row.getcell(cellnum++);
string name = convertcellvaluetostring(cell);
resultdata.setname(name);
// 获取年龄
cell = row.getcell(cellnum++);
string agestr = convertcellvaluetostring(cell);
if ( null == agestr || "" .equals(agestr)) {
// 年龄为空
resultdata.setage( null );
} else {
resultdata.setage(integer.parseint(agestr));
}
// 获取居住地
cell = row.getcell(cellnum++);
string location = convertcellvaluetostring(cell);
resultdata.setlocation(location);
// 获取职业
cell = row.getcell(cellnum++);
string job = convertcellvaluetostring(cell);
resultdata.setjob(job);
return resultdata;
}
}
|
2.2.3 应用场景补充
一般我们会有这样的应用场景,即:在前台页面的文件上传入口上传本地的excel文件到后台,后台收到excel文件后进行解析并做对应的业务操作;
这里假设前台已经有了上传文件的入口,再简单贴一下后台的解析代码;
后台接收前台数据的controller层代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@postmapping ( "/uploadexcel" )
public responseentity<?> uploadimage(multipartfile file) {
// 检查前台数据合法性
if ( null == file || file.isempty()) {
logger.warning( "上传的excel商品数据文件为空!上传时间:" + new date());
return new responseentity<>(httpstatus.bad_request);
}
try {
// 解析excel
list<exceldatavo> parsedresult = excelreader.readexcel(file);
// todo 进行业务操作
return new responseentity<>(httpstatus.ok);
} catch (exception e) {
logger.warning( "上传的excel商品数据文件为空!上传时间:" + new date());
return new responseentity<>(httpstatus.bad_request);
}
}
|
excelreader.java中的 readexcel() 方法需要做一定的修改,代码如下:
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
|
/**
* 读取excel文件内容
* @param file 上传的excel文件
* @return 读取结果列表,读取失败时返回null
*/
public static list<exceldatavo> readexcel(multipartfile file) {
workbook workbook = null ;
try {
// 获取excel后缀名
string filename = file.getoriginalfilename();
if (filename == null || filename.isempty() || filename.lastindexof( "." ) < 0 ) {
logger.warning( "解析excel失败,因为获取到的excel文件名非法!" );
return null ;
}
string filetype = filename.substring(filename.lastindexof( "." ) + 1 , filename.length());
// 获取excel工作簿
workbook = getworkbook(file.getinputstream(), filetype);
// 读取excel中的数据
list<exceldatavo> resultdatalist = parseexcel(workbook);
return resultdatalist;
} catch (exception e) {
logger.warning( "解析excel失败,文件名:" + file.getoriginalfilename() + " 错误信息:" + e.getmessage());
return null ;
} finally {
try {
if ( null != workbook) {
workbook.close();
}
} catch (exception e) {
logger.warning( "关闭数据流出错!错误信息:" + e.getmessage());
return null ;
}
}
}
|
2.3 写入数据
写入数据时主要调用excelwriter.java来创建excel工作簿对象并写入数据,这里以写入系统文件数据为例演示写入的过程:
2.3.1 主程序入口类代码
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
|
/**
* author: dreamer-1
* date: 2019-03-01
* time: 10:13
* description: 示例程序入口类
*/
public class maintest {
private static logger logger = logger.getlogger(maintest. class .getname());
public static void main(string[] args) {
// 创建需要写入的数据列表
list<exceldatavo> datavolist = new arraylist<>( 2 );
exceldatavo datavo = new exceldatavo();
datavo.setname( "小明" );
datavo.setage( 18 );
datavo.setlocation( "广州" );
datavo.setjob( "大学生" );
exceldatavo datavo2 = new exceldatavo();
datavo2.setname( "小花" );
datavo2.setage( 19 );
datavo2.setlocation( "深圳" );
datavo2.setjob( "大学生" );
datavolist.add(datavo);
datavolist.add(datavo2);
// 写入数据到工作簿对象内
workbook workbook = excelwriter.exportdata(datavolist);
// 以文件的形式输出工作簿对象
fileoutputstream fileout = null ;
try {
string exportfilepath = "/users/dreamer-1/desktop/myblog/java解析excel/writeexample.xlsx" ;
file exportfile = new file(exportfilepath);
if (!exportfile.exists()) {
exportfile.createnewfile();
}
fileout = new fileoutputstream(exportfilepath);
workbook.write(fileout);
fileout.flush();
} catch (exception e) {
logger.warning( "输出excel时发生错误,错误原因:" + e.getmessage());
} finally {
try {
if ( null != fileout) {
fileout.close();
}
if ( null != workbook) {
workbook.close();
}
} catch (ioexception e) {
logger.warning( "关闭输出流时发生错误,错误原因:" + e.getmessage());
}
}
}
}
|
2.3.2 写入excel类的代码
excelwriter.java类中,你可以根据实际需要替换 cell_heads 列头的信息,然后重写 convertdatatorow 方法,转换你自己的行数据;
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
|
/**
* author: dreamer-1
* date: 2019-03-01
* time: 11:09
* description: 生成excel并写入数据
*/
public class excelwriter {
private static list<string> cell_heads; //列头
static {
// 类装载时就载入指定好的列头信息,如有需要,可以考虑做成动态生成的列头
cell_heads = new arraylist<>();
cell_heads.add( "姓名" );
cell_heads.add( "年龄" );
cell_heads.add( "居住城市" );
cell_heads.add( "职业" );
}
/**
* 生成excel并写入数据信息
* @param datalist 数据列表
* @return 写入数据后的工作簿对象
*/
public static workbook exportdata(list<exceldatavo> datalist){
// 生成xlsx的excel
workbook workbook = new sxssfworkbook();
// 如需生成xls的excel,请使用下面的工作簿对象,注意后续输出时文件后缀名也需更改为xls
//workbook workbook = new hssfworkbook();
// 生成sheet表,写入第一行的列头
sheet sheet = builddatasheet(workbook);
//构建每行的数据内容
int rownum = 1 ;
for (iterator<exceldatavo> it = datalist.iterator(); it.hasnext(); ) {
exceldatavo data = it.next();
if (data == null ) {
continue ;
}
//输出行数据
row row = sheet.createrow(rownum++);
convertdatatorow(data, row);
}
return workbook;
}
/**
* 生成sheet表,并写入第一行数据(列头)
* @param workbook 工作簿对象
* @return 已经写入列头的sheet
*/
private static sheet builddatasheet(workbook workbook) {
sheet sheet = workbook.createsheet();
// 设置列头宽度
for ( int i= 0 ; i<cell_heads.size(); i++) {
sheet.setcolumnwidth(i, 4000 );
}
// 设置默认行高
sheet.setdefaultrowheight(( short ) 400 );
// 构建头单元格样式
cellstyle cellstyle = buildheadcellstyle(sheet.getworkbook());
// 写入第一行各列的数据
row head = sheet.createrow( 0 );
for ( int i = 0 ; i < cell_heads.size(); i++) {
cell cell = head.createcell(i);
cell.setcellvalue(cell_heads.get(i));
cell.setcellstyle(cellstyle);
}
return sheet;
}
/**
* 设置第一行列头的样式
* @param workbook 工作簿对象
* @return 单元格样式对象
*/
private static cellstyle buildheadcellstyle(workbook workbook) {
cellstyle style = workbook.createcellstyle();
//对齐方式设置
style.setalignment(horizontalalignment.center);
//边框颜色和宽度设置
style.setborderbottom(borderstyle.thin);
style.setbottombordercolor(indexedcolors.black.getindex()); // 下边框
style.setborderleft(borderstyle.thin);
style.setleftbordercolor(indexedcolors.black.getindex()); // 左边框
style.setborderright(borderstyle.thin);
style.setrightbordercolor(indexedcolors.black.getindex()); // 右边框
style.setbordertop(borderstyle.thin);
style.settopbordercolor(indexedcolors.black.getindex()); // 上边框
//设置背景颜色
style.setfillforegroundcolor(indexedcolors.grey_25_percent.getindex());
style.setfillpattern(fillpatterntype.solid_foreground);
//粗体字设置
font font = workbook.createfont();
font.setbold( true );
style.setfont(font);
return style;
}
/**
* 将数据转换成行
* @param data 源数据
* @param row 行对象
* @return
*/
private static void convertdatatorow(exceldatavo data, row row){
int cellnum = 0 ;
cell cell;
// 姓名
cell = row.createcell(cellnum++);
cell.setcellvalue( null == data.getname() ? "" : data.getname());
// 年龄
cell = row.createcell(cellnum++);
if ( null != data.getage()) {
cell.setcellvalue(data.getage());
} else {
cell.setcellvalue( "" );
}
// 所在城市
cell = row.createcell(cellnum++);
cell.setcellvalue( null == data.getlocation() ? "" : data.getlocation());
// 职业
cell = row.createcell(cellnum++);
cell.setcellvalue( null == data.getjob() ? "" : data.getjob());
}
}
|
示例程序运行后将会在指定的系统路径下生成 writeexample.xlsx文件,其内容如下:
2.3.3 应用场景补充
一般写入excel时会有这样的场景:前台页面上有一个导出按钮,点击后将后台某张表里的数据以excel的形式导出,导出的excel文件通过浏览器下载到用户系统中;
这里默认前台页面已经有相应的按钮功能,给出对应的controller层代码供参考:
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
|
@getmapping ( "/exportexcel" )
public void exportexcel(httpservletrequest request, httpservletresponse response) {
workbook workbook = null ;
outputstream out = null ;
try {
// todo 根据业务需求获取需要写入excel的数据列表 datalist
// 生成excel工作簿对象并写入数据
workbook = excelwriter.exportdata(datalist);
// 写入excel文件到前端
if ( null != workbook){
string excelname = "示例excel导出" ;
string filename = excelname + dateutil.format( new date(), dateutil.short_date) + ".xlsx" ;
filename = new string(filename.getbytes( "utf-8" ), "iso8859-1" );
response.setheader( "content-disposition" , "attachment;filename=" + filename);
response.setcontenttype( "application/x-download" );
response.setcharacterencoding( "utf-8" );
response.addheader( "pargam" , "no-cache" );
response.addheader( "cache-control" , "no-cache" );
response.flushbuffer();
out = response.getoutputstream();
workbook.write(out);
out.flush();
}
} catch (exception e) {
logger.warning( "写入excel过程出错!错误原因:" + e.getmessage());
} finally {
try {
if ( null != workbook) {
workbook.close();
}
if ( null != out) {
out.close();
}
} catch (ioexception e) {
logger.warning( "关闭workbook或outputstream出错!" );
}
}
}
|
1
2
3
4
|
// 前台页面发送请求到后台controller时的js代码可参考:
var url = "/exportexcel" ;
window.location=url;
|
3. 源码下载
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://www.cnblogs.com/Dreamer-1/p/10469430.html