一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 利用POI讀取word、Excel文件的最佳實踐教程

利用POI讀取word、Excel文件的最佳實踐教程

2021-02-22 10:53neal Java教程

Apache POI 是用Java編寫的免費開源的跨平臺的 Java API,Apache POI提供API給Java程式對Microsoft Office格式檔案讀和寫的功能。 下面這篇文章主要給大家介紹了關于利用POI讀取word、Excel文件的最佳實踐的相關資料,需要的朋友可以參考下。

前言

POI是 Apache 旗下一款讀寫微軟家文檔聲名顯赫的類庫。應該很多人在做報表的導出,或者創建 word 文檔以及讀取之類的都是用過 POI。POI 也的確對于這些操作帶來很大的便利性。我最近做的一個工具就是讀取計算機中的 word 以及 excel 文件。

POI結構說明

包名稱說明

HSSF提供讀寫Microsoft Excel XLS格式檔案的功能。

XSSF提供讀寫Microsoft Excel OOXML XLSX格式檔案的功能。

HWPF提供讀寫Microsoft Word DOC格式檔案的功能。

HSLF提供讀寫Microsoft PowerPoint格式檔案的功能。

HDGF提供讀Microsoft Visio格式檔案的功能。

HPBF提供讀Microsoft Publisher格式檔案的功能。

HSMF提供讀Microsoft Outlook格式檔案的功能。

下面就word和excel兩方面講解以下遇到的一些坑:

word 篇

對于 word 文件,我需要的就是提取文件中正文的文字。所以可以創建一個方法來讀取 doc 或者 docx 文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static String readDoc(String filePath, InputStream is) {
 String text= "";
 try {
  if (filePath.endsWith("doc")) {
   WordExtractor ex = new WordExtractor(is);
   text = ex.getText();
   ex.close();
   is.close();
  } else if(filePath.endsWith("docx")) {
   XWPFDocument doc = new XWPFDocument(is);
   XWPFWordExtractor extractor = new XWPFWordExtractor(doc);
   text = extractor.getText();
   extractor.close();
   is.close();
  }
 } catch (Exception e) {
  logger.error(filePath, e);
 } finally {
  if (is != null) {
   is.close();
  }
 }
 return text;
}

理論上來說,這段代碼應該對于讀取大多數 doc 或者 docx 文件都是有效的。但是!!!!我發現了一個奇怪的問題,就是我的代碼在讀取某些 doc 文件的時候,經常會給出這樣的一個異常:

?
1
org.apache.poi.poifs.filesystem.OfficeXmlFileException: The supplied data appears to be in the Office 2007+ XML. You are calling the part of POI that deals with OLE2 Office Documents.

這個異常的意思是什么呢,通俗的來講,就是你打開的文件并不是一個 doc 文件,你應該使用讀取 docx 的方法去讀取。但是我們明明打開的就是一個后綴是 doc 的文件啊!

其實 doc 和 docx 的本質不同的,doc 是 OLE2 類型,而 docx 而是 OOXML 類型。如果你用壓縮文件打開一個 docx 文件,你會發現一些文件夾:

利用POI讀取word、Excel文件的最佳實踐教程

本質上 docx 文件就是一個 zip 文件,里面包含了一些 xml 文件。所以,一些 docx 文件雖然大小不大,但是其內部的 xml 文件確實比較大的,這也是為什么在讀取某些看起來不是很大的 docx 文件的時候卻耗費了大量的內存。

然后我使用壓縮文件打開這個 doc 文件,果不其然,其內部正是如上圖,所以本質上我們可以認為它是一個 docx 文件。可能是因為它是以某種兼容模式保存從而導致如此坑爹的問題。所以,現在我們根據后綴名來判斷一個文件是 doc 或者 docx 就是不可靠的了。

老實說,我覺得這應該不是一個很少見的問題。但是我在谷歌上并沒有找到任何關于此的信息。how to know whether a file is .docx or .doc format from Apache POI 這個例子是通過 ZipInputStream 來判斷文件是否是 docx 文件:

?
1
boolean isZip = new ZipInputStream( fileStream ).getNextEntry() != null;

但我并不覺得這是一個很好的方法,因為我得去構建一個ZipInpuStream,這很顯然不好。另外,這個操作貌似會影響到 InputStream,所以你在讀取正常的 doc 文件會有問題。或者你使用 File 對象去判斷是否是一個 zip 文件。但這也不是一個好方法,因為我還需要在壓縮文件中讀取 doc 或者 docx 文件,所以我的輸入必須是 Inputstream,所以這個選項也是不可以的。 我在 stackoverflow 上和一幫老外扯了大半天,有時候我真的很懷疑這幫老外的理解能力,不過最終還是有一個大佬給出了一個讓我欣喜若狂的解決方案,FileMagic。這個是一個 POI 3.17新增加的一個特性:

?
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
public enum FileMagic {
 /** OLE2 / BIFF8+ stream used for Office 97 and higher documents */
 OLE2(HeaderBlockConstants._signature),
 /** OOXML / ZIP stream */
 OOXML(OOXML_FILE_HEADER),
 /** XML file */
 XML(RAW_XML_FILE_HEADER),
 /** BIFF2 raw stream - for Excel 2 */
 BIFF2(new byte[]{
   0x09, 0x00, // sid=0x0009
   0x04, 0x00, // size=0x0004
   0x00, 0x00, // unused
   0x70, 0x00 // 0x70 = multiple values
 }),
 /** BIFF3 raw stream - for Excel 3 */
 BIFF3(new byte[]{
   0x09, 0x02, // sid=0x0209
   0x06, 0x00, // size=0x0006
   0x00, 0x00, // unused
   0x70, 0x00 // 0x70 = multiple values
 }),
 /** BIFF4 raw stream - for Excel 4 */
 BIFF4(new byte[]{
   0x09, 0x04, // sid=0x0409
   0x06, 0x00, // size=0x0006
   0x00, 0x00, // unused
   0x70, 0x00 // 0x70 = multiple values
 },new byte[]{
   0x09, 0x04, // sid=0x0409
   0x06, 0x00, // size=0x0006
   0x00, 0x00, // unused
   0x00, 0x01
 }),
 /** Old MS Write raw stream */
 MSWRITE(
   new byte[]{0x31, (byte)0xbe, 0x00, 0x00 },
   new byte[]{0x32, (byte)0xbe, 0x00, 0x00 }),
 /** RTF document */
 RTF("{\\rtf"),
 /** PDF document */
 PDF("%PDF"),
 // keep UNKNOWN always as last enum!
 /** UNKNOWN magic */
 UNKNOWN(new byte[0]);
 
 final byte[][] magic;
 
 FileMagic(long magic) {
  this.magic = new byte[1][8];
  LittleEndian.putLong(this.magic[0], 0, magic);
 }
 
 FileMagic(byte[]... magic) {
  this.magic = magic;
 }
 
 FileMagic(String magic) {
  this(magic.getBytes(LocaleUtil.CHARSET_1252));
 }
 
 public static FileMagic valueOf(byte[] magic) {
  for (FileMagic fm : values()) {
   int i=0;
   boolean found = true;
   for (byte[] ma : fm.magic) {
    for (byte m : ma) {
     byte d = magic[i++];
     if (!(d == m || (m == 0x70 && (d == 0x10 || d == 0x20 || d == 0x40)))) {
      found = false;
      break;
     }
    }
    if (found) {
     return fm;
    }
   }
  }
  return UNKNOWN;
 }
 
 /**
  * Get the file magic of the supplied InputStream (which MUST
  * support mark and reset).<p>
  *
  * If unsure if your InputStream does support mark / reset,
  * use {@link #prepareToCheckMagic(InputStream)} to wrap it and make
  * sure to always use that, and not the original!<p>
  *
  * Even if this method returns {@link FileMagic#UNKNOWN} it could potentially mean,
  * that the ZIP stream has leading junk bytes
  *
  * @param inp An InputStream which supports either mark/reset
  */
 public static FileMagic valueOf(InputStream inp) throws IOException {
  if (!inp.markSupported()) {
   throw new IOException("getFileMagic() only operates on streams which support mark(int)");
  }
 
  // Grab the first 8 bytes
  byte[] data = IOUtils.peekFirst8Bytes(inp);
 
  return FileMagic.valueOf(data);
 }
 
 
 /**
  * Checks if an {@link InputStream} can be reseted (i.e. used for checking the header magic) and wraps it if not
  *
  * @param stream stream to be checked for wrapping
  * @return a mark enabled stream
  */
 public static InputStream prepareToCheckMagic(InputStream stream) {
  if (stream.markSupported()) {
   return stream;
  }
  // we used to process the data via a PushbackInputStream, but user code could provide a too small one
  // so we use a BufferedInputStream instead now
  return new BufferedInputStream(stream);
 }
}

在這給出主要的代碼,其主要就是根據 InputStream 前 8 個字節來判斷文件的類型,毫無以為這就是最優雅的解決方式。一開始,其實我也是在想對于壓縮文件的前幾個字節似乎是由不同的定義的,magicmumber。因為 FileMagic 的依賴和3.16 版本是兼容的,所以我只需要加入這個類就可以了,因此我們現在讀取 word 文件的正確做法是:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static String readDoc (String filePath, InputStream is) {
 String text= "";
 is = FileMagic.prepareToCheckMagic(is);
 try {
  if (FileMagic.valueOf(is) == FileMagic.OLE2) {
   WordExtractor ex = new WordExtractor(is);
   text = ex.getText();
   ex.close();
  } else if(FileMagic.valueOf(is) == FileMagic.OOXML) {
   XWPFDocument doc = new XWPFDocument(is);
   XWPFWordExtractor extractor = new XWPFWordExtractor(doc);
   text = extractor.getText();
   extractor.close();
  }
 } catch (Exception e) {
  logger.error("for file " + filePath, e);
 } finally {
  if (is != null) {
   is.close();
  }
 }
 return text;
}

excel 篇

對于 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
@SuppressWarnings("deprecation" )
private static String readExcel(String filePath, InputStream inp) throws Exception {
 Workbook wb;
 StringBuilder sb = new StringBuilder();
 try {
  if (filePath.endsWith(".xls")) {
   wb = new HSSFWorkbook(inp);
  } else {
   wb = StreamingReader.builder()
     .rowCacheSize(1000) // number of rows to keep in memory (defaults to 10)
     .bufferSize(4096// buffer size to use when reading InputStream to file (defaults to 1024)
     .open(inp);   // InputStream or File for XLSX file (required)
  }
  sb = readSheet(wb, sb, filePath.endsWith(".xls"));
  wb.close();
 } catch (OLE2NotOfficeXmlFileException e) {
  logger.error(filePath, e);
 } finally {
  if (inp != null) {
   inp.close();
  }
 }
 return sb.toString();
}
 
private static String readExcelByFile(String filepath, File file) {
 Workbook wb;
 StringBuilder sb = new StringBuilder();
 try {
  if (filepath.endsWith(".xls")) {
   wb = WorkbookFactory.create(file);
  } else {
   wb = StreamingReader.builder()
     .rowCacheSize(1000) // number of rows to keep in memory (defaults to 10)
     .bufferSize(4096// buffer size to use when reading InputStream to file (defaults to 1024)
     .open(file);   // InputStream or File for XLSX file (required)
  }
  sb = readSheet(wb, sb, filepath.endsWith(".xls"));
  wb.close();
 } catch (Exception e) {
  logger.error(filepath, e);
 }
 return sb.toString();
}
 
private static StringBuilder readSheet(Workbook wb, StringBuilder sb, boolean isXls) throws Exception {
 for (Sheet sheet: wb) {
  for (Row r: sheet) {
   for (Cell cell: r) {
    if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
     sb.append(cell.getStringCellValue());
     sb.append(" ");
    } else if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
     if (isXls) {
      DataFormatter formatter = new DataFormatter();
      sb.append(formatter.formatCellValue(cell));
     } else {
      sb.append(cell.getStringCellValue());
     }
     sb.append(" ");
    }
   }
  }
 }
 return sb;
}

其實,對于 excel 讀取,我的工具面臨的最大問題就是內存溢出。經常在讀取某些特別大的 excel 文件的時候都會帶來一個內存溢出的問題。后來我終于找到一個優秀的工具excel-streaming-reader,它可以流式的讀取 xlsx 文件,將一些特別大的文件拆分成小的文件去讀。

另外一個做的優化就是,對于可以使用 File 對象的場景下,我是去使用 File 對象去讀取文件而不是使用 InputStream 去讀取,因為使用 InputStream 需要把它全部加載到內存中,所以這樣是非常占用內存的。

最后,我的一點小技巧就是使用 cell.getCellType 去減少一些數據量,因為我只需要獲取一些文字以及數字的字符串內容就可以了。

以上,就是我在使用 POI 讀取文件的一些探索和發現,希望對你能有所幫助。上面的這些例子也是在我的一款工具 everywhere 中的應用(這款工具主要是可以幫助你在電腦中進行內容的全文搜索),感興趣的可以看看,歡迎 star 或者 pr。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。  

原文鏈接:https://segmentfault.com/a/1190000012165530

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 日本中文字幕在线精品 | 国产视频一区在线观看 | a天堂在线视频 | 32pao强力打造免费高速高清 | 不卡日本| 国产成人精品免费大全 | 按摩院已婚妇女中文字幕 | juy_661佐佐木明希在线播放 | 欧美视频久久 | 麻豆网| melody中文字幕 | 国产成人咱精品视频免费网站 | 女人狂吮男人命根gif视频 | 久久性综合亚洲精品电影网 | 2018成年动漫在线观看 | avtt手机版| 村上里沙40分钟在线观看 | 99r视频| 国内精品91最新在线观看 | 亚洲成人影院在线 | 无码人妻精品一区二区蜜桃在线看 | 东北疯狂xxxxbbbb中国 | 热99在线视频 | 日韩欧美一区二区三区中文精品 | 国产永久免费视频m3u8 | 国产视频自拍一区 | 双性肉文高h | 亚洲国产成人精品无码区99 | 国产亚洲精品看片在线观看 | 2020年最新国产精品视频免费 | 全肉一女n男np高h双龙养成 | 青草福利在线 | 俺去俺来也在线www色官网 | 香蕉久久一区二区三区 | 亚洲国产成人精品无码区99 | 国产精品视频第一页 | 日本福利视频网站 | 国产高清dvd | 日本高清在线看免费观看 | 精品美女国产互换人妻 | 无限资源在线观看高清 |