背景:
web应用经常需要上传文件,有时候需要解析出excel中的数据,如果excel的格式没有问题,那就可以直接解析数据入库。
工具选择:
目前jxl和poi可以解析excel,jxl很早就停止维护了,只支持excel-2003也就是xls格式的文件;
poi可支持xls和xlsx格式的文件,经过考察,poi的功能强大很多,所以选择这个工具解析excel。文件上传在之前的一个专题有所提及。
需要如下jar包,jar包见附件,也可在官网下载。
注意:
1. 不支持单元格合并的情况,默认表格格式规范,格式规范指:
从第一行第一列开始,第一行为表头,后面全都是数据,每一个列代表一个字段。没有中途断掉的行或者列,允许单元格为空。
2. 代码初步运用了重构中的方法抽取(extract method)和方法对象抽取(extract method object)技术,代码行数前后没有太大的变化,但是结构明显要清楚一些。
只不过初步使用,有些抽取不是特别直观,写文件的两个方法没有使用重构技术,此外在单元格类型匹配,也还有深挖的空间,希望读者根据自己的需要,自行定制。
关于重构技术和解析excel的api,请参考《重构改善既有代码的设计》以及poi的源代码
1 package com.test; 2 import org.apache.poi.hssf.usermodel.HSSFDataFormat; 3 import org.apache.poi.hssf.usermodel.HSSFWorkbook; 4 import org.apache.poi.ss.usermodel.*; 5 import org.apache.poi.xssf.usermodel.XSSFWorkbook; 6 import java.io.*; 7 import java.text.DecimalFormat; 8 import java.text.SimpleDateFormat; 9 import java.util.*; 10 /** 11 * Created by tm on 2016/4/11. 12 * 13 * @author tm 14 * 使用POI解析和生成excel,暂不支持单元格合并情况,默认是标准格式的excel 15 * 实测同等的数据量(3张sheet超过2w条),xlsx的速度要远慢于xls的解析速度。 16 * 同等数据量: 17 * xls的解析,如果只取表头,不超过500毫秒;如果取整个数据,时间不超过2000毫秒; 18 * 而xlsx的解析,如果只取表头,需要2000毫秒左右,如果取整个数据,则在3000毫秒以上。 19 * 以上耗时不同系统不同硬件有不同结果,仅作为相对比较结果。 20 * 这个类提供三个功能: 21 * 1.获得表格的表头所有列,这个功能可以酌情去掉,因为获得表格的所有数据也就获得了所有列. 22 * 2.获得表格的所有数据 23 * 3.传入数据写出来 24 */ 25 public class ExcelUtil { 26 public static final String SUFFIX_XLS = "xls"; 27 public static final String SUFFIX_XLSX = "xlsx"; 28 public static final String NO_FILE_NULL = "NO FILE NULL"; 29 public static final String NO_FILE_EMPTY = "NO FILE EMPTY"; 30 /** 31 * 获得表格的列 32 * @param fileName 33 * @return 34 * @throws RuntimeException 35 */ 36 public static List<Map<String, Object>> getTableSheetCol(String fileName) throws RuntimeException{ 37 if(!valid(fileName)) 38 return null; 39 else{ 40 if(validExcelFormat(fileName,SUFFIX_XLS)){ 41 //是excel2003? 42 return getXlsSheetItem(fileName); 43 } 44 if(validExcelFormat(fileName, SUFFIX_XLSX)){ 45 //是excel2007以后 46 return getXlsxSheetItem(fileName); 47 }else{ 48 print("nonsupport file format"); 49 print("fileFormat : " + getFileNameSuffix(fileName)); 50 throw new RuntimeException("nonsupport file format, please check input fileName again"); 51 } 52 } 53 } 54 /** 55 * getXlsSheetItem : 读取xls格式文件的所有sheet表的所有列名集合。 56 * 57 * @param fileName 文件名 58 * 默认表格格式规范,列名全部为字符串。 59 */ 60 private static List<Map<String, Object>> getXlsSheetItem(String fileName) { 61 TableObject tableObject = new TableObject(fileName).invoke(); 62 HSSFWorkbook book = tableObject.getHssfBook(); 63 List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(); 64 for (int i = 0; i < tableObject.getSheet_number(); i++) { 65 Sheet sheet = book.getSheetAt(i); 66 if (ifSheetNullOrEmpty(sheet)) continue; 67 Row row = sheet.getRow(0); 68 if (ifRowNullOrEmpty(row)) continue; 69 result.add(packageColsWithSheetName(book, i, row)); 70 } 71 return result; 72 } 73 /** 74 * getXlsxSheetItem : 读取xlsx格式文件的所有sheet表的所有列名集合。 75 * 76 * @param fileName 文件名 77 * 默认表格格式规范,列名全部为字符串。 78 * 方法还可以进一步化简,可参考 getExcelData 进一步整合。 79 */ 80 private static List<Map<String, Object>> getXlsxSheetItem(String fileName) { 81 TableObject tableObject = new TableObject(fileName).invoke(); 82 XSSFWorkbook book = tableObject.getXssfBook(); 83 List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(); 84 for (int i = 0; i < tableObject.getSheet_number(); i++) { 85 Sheet sheet = book.getSheetAt(i); 86 if (ifSheetNullOrEmpty(sheet)) continue; 87 Row row = sheet.getRow(0); 88 if (ifRowNullOrEmpty(row)) continue; 89 result.add(packageColsWithSheetName(book, i, row)); 90 } 91 return result; 92 } 93 private static Map<String, Object> packageColsWithSheetName(Workbook book, int i, Row row) { 94 Map<String, Object> map = new HashMap<String, Object>(); 95 String sheet_name = book.getSheetName(i); 96 List<String> cols = getCols(row); 97 map.put("sheet_name", sheet_name); 98 map.put("cols", cols); 99 return map; 100 } 101 private static List<String> getCols(Row row) { 102 List<String> cols = new ArrayList<String>(); 103 System.out.println(row.getLastCellNum()); 104 for (int j = 0; j < row.getLastCellNum(); j++) { 105 Object obj = row.getCell(j); 106 cols.add(obj == null ? "" : obj.toString()); 107 } 108 return cols; 109 } 110 private static boolean ifRowNullOrEmpty(Row row) { 111 if (row == null || row.getLastCellNum() == 0 || row.getCell(0) == null) { 112 return true; 113 } 114 return false; 115 } 116 private static boolean ifSheetNullOrEmpty(Sheet sheet) { 117 if (sheet == null || sheet.getLastRowNum() == 0) { 118 return true; 119 } 120 return false; 121 } 122 /** 123 * 获取表格的全部数据 124 * @param fileName 文件名 125 * @return 126 * @throws RuntimeException 127 */ 128 public static Map<String, List<Map<String, Object>>> getTableSheetData(String fileName) throws RuntimeException{ 129 if(!valid(fileName)) { 130 print("文件名校验失败"); 131 return null; 132 } 133 else{ 134 return getExcelData(fileName); 135 } 136 } 137 /** 138 * 根据文件后缀格式,确定调用哪种Workbook,此处运用了多态,具体的解析操作都用的是接口。 139 * @param fileName 140 * @return 141 */ 142 private static Map<String, List<Map<String, Object>>> getExcelData(String fileName) { 143 TableObject tableObject = new TableObject(fileName).invoke(); 144 Workbook book = null; 145 if(validExcelFormat(fileName,SUFFIX_XLS)){ 146 //是excel2003? 147 book = tableObject.getHssfBook(); 148 } 149 else if(validExcelFormat(fileName, SUFFIX_XLSX)){ 150 //是excel2007以后 151 book = tableObject.getXssfBook(); 152 }else{ 153 print("nonsupport file format"); 154 throw new RuntimeException("nonsupport file format, please check input fileName again"); 155 } 156 Map<String, List<Map<String, Object>>> result = new HashMap<String, List<Map<String, Object>>>(); 157 for (int i = 0; i < tableObject.getSheet_number(); i++) { 158 List<Map<String, Object>> sheet_data = new ArrayList<Map<String, Object>>(); 159 Sheet sheet = book.getSheetAt(i); 160 if (ifSheetNullOrEmpty(sheet)) continue; 161 String sheet_name = book.getSheetName(i); 162 System.out.println(sheet.getLastRowNum()); 163 for (int j = 1; j <= sheet.getLastRowNum(); j++) { 164 Row row = sheet.getRow(j); 165 if (ifRowNullOrEmpty(row)) continue; 166 Map<String, Object> record = new HashMap<String, Object>(); 167 Row first = sheet.getRow(0); 168 getRowData(row, record, first); 169 sheet_data.add(record); 170 } 171 result.put(sheet_name, sheet_data); 172 } 173 return result; 174 } 175 176 /** 177 * 此处有个点要注意,getLastCellNum,下标是从1开始,有多少列,这里就是这个值.而getLastRowNum,下标是从0开始,也就是21行的表格,这里获得的值是20.用户可自行验证. 178 * @param row 该行记录 179 * @param record 返回值 180 * @param first 表头 181 */ 182 private static void getRowData(Row row, Map<String, Object> record, Row first) { 183 for (int k = 0; k < row.getLastCellNum(); k++) { 184 String value; 185 if (row.getCell(k) == null) { 186 value = ""; 187 } else { 188 value = parseDate(row.getCell(k)); 189 if (value.endsWith("00:00:00")) { 190 value = value.substring(0, value.lastIndexOf("00:00:00")); 191 } 192 } 193 record.put(first.getCell(k).toString(), value); 194 } 195 } 196 /** 197 * 判断单元格格式,转化日期格式,日期在poi里保存的是数字,所以这里要转化一下. 198 * @param cell 单元格 199 * @return 200 */ 201 private static String parseDate(Cell cell) { 202 String result = ""; 203 switch (cell.getCellType()) { 204 case Cell.CELL_TYPE_NUMERIC:// 数字类型 205 if (DateUtil.isCellDateFormatted(cell)) {// 处理日期格式、时间格式 206 SimpleDateFormat sdf = null; 207 if (cell.getCellStyle().getDataFormat() == HSSFDataFormat 208 .getBuiltinFormat("yyyy/MM/dd")) { 209 sdf = new SimpleDateFormat("yyyy/MM/dd"); 210 } else {// 日期 211 sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); 212 } 213 Date date = cell.getDateCellValue(); 214 result = sdf.format(date); 215 } else if (cell.getCellStyle().getDataFormat() == 58) { 216 // 处理自定义日期格式:m月d日(通过判断单元格的格式id解决,id的值是58) 217 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 218 double value = cell.getNumericCellValue(); 219 Date date = org.apache.poi.ss.usermodel.DateUtil 220 .getJavaDate(value); 221 result = sdf.format(date); 222 } else { 223 double value = cell.getNumericCellValue(); 224 CellStyle style = cell.getCellStyle(); 225 DecimalFormat format = new DecimalFormat(); 226 String temp = style.getDataFormatString(); 227 // 单元格设置成常规 228 if (temp.equals("General")) { 229 format.applyPattern("#"); 230 } 231 result = format.format(value); 232 } 233 break; 234 case Cell.CELL_TYPE_STRING:// String类型 235 result = cell.getRichStringCellValue().toString(); 236 break; 237 case Cell.CELL_TYPE_BLANK: 238 result = ""; 239 break; 240 default: 241 result = ""; 242 break; 243 } 244 return result; 245 } 246 /** 247 * ifXls:判断是否为 xls 文件 248 * 249 * @param fileName 包含后缀的文件名 250 */ 251 private static boolean validExcelFormat(String fileName,String type) { 252 if(getOS().contains("win")){ 253 return valid(fileName) && getFileNameSuffix(fileName).equalsIgnoreCase(type); 254 }else if(getOS().contains("linux")){ 255 return valid(fileName) && getFileNameSuffix(fileName).equals(type); 256 }else{ 257 print("System OS is not windows or linux"); 258 throw new RuntimeException("System OS is not windows or linux , if you use this class in another sys,please implement yourself"); 259 } 260 } 261 private static String getOS() { return System.getProperty("os.name").toLowerCase(); } 262 private static void print(String msg) { 263 System.out.println(msg); 264 } 265 private static String getFileNameSuffix(String fileName) { 266 return fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()); 267 } 268 /** 269 * valid :判断文件是否存在 270 * 271 * @param fileName fileName 文件名。 272 * 区分为文件名为null,或者为空。 273 */ 274 private static boolean valid(String fileName) { 275 return !ifNull(fileName) && !ifEmpty(fileName); 276 } 277 private static boolean ifEmpty(String fileName) { 278 if (fileName.equals("")) { 279 print(NO_FILE_EMPTY); 280 return true; 281 } 282 return false; 283 } 284 private static boolean ifNull(String fileName) { 285 if (fileName == null) { 286 print(NO_FILE_NULL); 287 return true; 288 } 289 return false; 290 } 291 /** 292 * 将excel文件写出成txt格式 293 * 此处没有指定输出格式 294 * @param filePath 输入文件路径 295 * @param destFilePath 输出文件路径 296 * @throws Exception 297 */ 298 public static void writeExcel2Txt(String filePath,String destFilePath) throws Exception { 299 BufferedWriter bufferedWriter = null; 300 try { 301 FileWriter fw = new FileWriter(destFilePath); 302 bufferedWriter = new BufferedWriter(fw); 303 } catch (IOException e) { 304 e.printStackTrace(); 305 return; 306 } 307 Map<String, List<Map<String, Object>>> data = getTableSheetData(filePath); 308 System.out.println(data.toString()); 309 if(data==null){ 310 throw new Exception("解析数据失败"); 311 } 312 for (int i = 0; i < data.size(); i++) { 313 try { 314 bufferedWriter.write(DataTransformUtil.mapGetKeyList(data).get(i)); 315 System.out.println(DataTransformUtil.mapGetKeyList(data).get(i)); 316 bufferedWriter.newLine(); 317 bufferedWriter.write("==========================================================="); 318 bufferedWriter.newLine(); 319 } catch (IOException e) { 320 e.printStackTrace(); 321 } 322 System.out.println("=========================================="); 323 List<Map<String, Object>> sheet = data.get(DataTransformUtil.mapGetKeyList(data).get(i)); 324 for (Map map : sheet) { 325 System.out.println(map.toString()); 326 bufferedWriter.write(map.toString()); 327 bufferedWriter.newLine(); 328 } 329 System.out.println(); 330 bufferedWriter.newLine(); 331 } 332 bufferedWriter.flush(); 333 bufferedWriter.close(); 334 } 335 /*** 336 * writeXlsxFile:将指定数据集生成为excel文件。 337 * 338 * @param data 一个包含record的List集合 339 * @param name 文件名【带后缀】 340 * @param path 路径名 341 * 只是简单输出,尚未设置样式,单元格格式也未深究。 342 */ 343 public static String writeXlsxFile(List<Map<String, Object>> data, String path, String name) { 344 // 创建Excel的工作书册 Workbook,对应到一个excel文档 345 Workbook book = null; 346 if(validExcelFormat(name,SUFFIX_XLS)){ 347 book = new HSSFWorkbook(); 348 }else if(validExcelFormat(name,SUFFIX_XLSX)){ 349 book = new XSSFWorkbook(); 350 }else { 351 print("nonsupport file format"); 352 throw new RuntimeException("nonsupport file format, please check input fileName again"); 353 } 354 // 创建Excel的工作sheet,对应到一个excel文档的tab 355 Sheet sheet = book.createSheet("sheet1"); 356 sheet.setColumnWidth(0, 4000); 357 sheet.setColumnWidth(1, 3500); 358 // 创建Excel的sheet的一行 359 int c = data.get(0).size(); 360 List cols = DataTransformUtil.mapGetKeyList(data.get(0)); 361 Row head = sheet.createRow(0); 362 for (int j = 0; j < cols.size(); j++) { 363 Cell cell = head.createCell(j, 1); 364 cell.setCellValue(cols.get(j).toString()); 365 } 366 for (int i = 0; i < data.size(); i++) { 367 Row row = sheet.createRow(i + 1); 368 for (int j = 0; j < c; j++) { 369 Cell cell = row.createCell(j, 1); 370 cell.setCellValue(data.get(i).get(cols.get(j).toString()).toString()); 371 } 372 } 373 FileOutputStream os = null; 374 try { 375 os = new FileOutputStream(new File(new File(path), name)); 376 book.write(os); 377 os.close(); 378 } catch (Exception e) { 379 e.printStackTrace(); 380 } 381 return name ; 382 } 383 public static void main(String[] args) throws Exception { 384 // String str="[{'name':'zhang3','age':33,'gender':'f'}," + 385 // "{'name':'li4','age':44,'gender':'m'}," + 386 // "{'name':'wang5','age':55,'gender':'m'}," + 387 // "{'name':'wang5','age':55,'gender':'m'}," + 388 // "{'name':'wang5','age':55,'gender':'m'}]"; 389 // List<Map<String,Object>> list=DataTransformUtil.nestJsonArrayString2List(str); 390 // System.out.println(list.toString()); 391 // writeXlsxFile(list,"F:\","test"); 392 long start = System.currentTimeMillis(); 393 System.out.println("start :" + System.currentTimeMillis()); 394 // writeExcel2Txt("F:\152657884.xls","F:\123.txt"); 395 //System.out.println(getTableSheetCol("F:\需要本地开发功能列表.xlsx")); 396 System.out.println(getTableSheetData("F:\需要本地开发功能列表.xlsx")); 397 } 398 /** 399 * extract method object 400 */ 401 private static class TableObject { 402 private String fileName; 403 private XSSFWorkbook xssfBook; 404 private HSSFWorkbook hssfBook; 405 private int sheet_number; 406 public TableObject(String fileName) { 407 this.fileName = fileName; 408 } 409 public XSSFWorkbook getXssfBook() { 410 return xssfBook; 411 } 412 public HSSFWorkbook getHssfBook() { 413 return hssfBook; 414 } 415 public int getSheet_number() { 416 return sheet_number; 417 } 418 public TableObject invoke() { 419 xssfBook = null; 420 hssfBook = null; 421 sheet_number = 0; 422 try { 423 FileInputStream is = new FileInputStream(new File(fileName)); 424 if(validExcelFormat(fileName,SUFFIX_XLS)){ 425 hssfBook = new HSSFWorkbook(is); 426 sheet_number = hssfBook.getNumberOfSheets(); 427 }else if(validExcelFormat(fileName,SUFFIX_XLSX)){ 428 xssfBook = new XSSFWorkbook(is); 429 sheet_number = xssfBook.getNumberOfSheets(); 430 } 431 } catch (IOException e) { 432 e.printStackTrace(); 433 } 434 return this; 435 } 436 } 437 }
发现有个WorkBookFactory的api方法,可以直接拿到对应的HSSFWorkbook或XSSFWorkbook
所以说,研究清楚api才是正道啊……
大牛博客传送门:http://blog.csdn.net/lovesomnus/article/details/23843549