欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2017年11月27日 10:43:39   作者:neal  
Apache POI 是用Java編寫(xiě)的免費(fèi)開(kāi)源的跨平臺(tái)的 Java API,Apache POI提供API給Java程式對(duì)Microsoft Office格式檔案讀和寫(xiě)的功能。 下面這篇文章主要給大家介紹了關(guān)于利用POI讀取word、Excel文件的最佳實(shí)踐的相關(guān)資料,需要的朋友可以參考下。

前言

POI是 Apache 旗下一款讀寫(xiě)微軟家文檔聲名顯赫的類(lèi)庫(kù)。應(yīng)該很多人在做報(bào)表的導(dǎo)出,或者創(chuàng)建 word 文檔以及讀取之類(lèi)的都是用過(guò) POI。POI 也的確對(duì)于這些操作帶來(lái)很大的便利性。我最近做的一個(gè)工具就是讀取計(jì)算機(jī)中的 word 以及 excel 文件。

POI結(jié)構(gòu)說(shuō)明

包名稱(chēng)說(shuō)明

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

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

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

HSLF提供讀寫(xiě)Microsoft PowerPoint格式檔案的功能。

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

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

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

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

word 篇

對(duì)于 word 文件,我需要的就是提取文件中正文的文字。所以可以創(chuàng)建一個(gè)方法來(lái)讀取 doc 或者 docx 文件:

 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;
 }

理論上來(lái)說(shuō),這段代碼應(yīng)該對(duì)于讀取大多數(shù) doc 或者 docx 文件都是有效的。但是!!!!我發(fā)現(xiàn)了一個(gè)奇怪的問(wèn)題,就是我的代碼在讀取某些 doc 文件的時(shí)候,經(jīng)常會(huì)給出這樣的一個(gè)異常:

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.

這個(gè)異常的意思是什么呢,通俗的來(lái)講,就是你打開(kāi)的文件并不是一個(gè) doc 文件,你應(yīng)該使用讀取 docx 的方法去讀取。但是我們明明打開(kāi)的就是一個(gè)后綴是 doc 的文件啊!

其實(shí) doc 和 docx 的本質(zhì)不同的,doc 是 OLE2 類(lèi)型,而 docx 而是 OOXML 類(lèi)型。如果你用壓縮文件打開(kāi)一個(gè) docx 文件,你會(huì)發(fā)現(xiàn)一些文件夾:

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

然后我使用壓縮文件打開(kāi)這個(gè) doc 文件,果不其然,其內(nèi)部正是如上圖,所以本質(zhì)上我們可以認(rèn)為它是一個(gè) docx 文件??赡苁且?yàn)樗且阅撤N兼容模式保存從而導(dǎo)致如此坑爹的問(wèn)題。所以,現(xiàn)在我們根據(jù)后綴名來(lái)判斷一個(gè)文件是 doc 或者 docx 就是不可靠的了。

老實(shí)說(shuō),我覺(jué)得這應(yīng)該不是一個(gè)很少見(jiàn)的問(wèn)題。但是我在谷歌上并沒(méi)有找到任何關(guān)于此的信息。how to know whether a file is .docx or .doc format from Apache POI 這個(gè)例子是通過(guò) ZipInputStream 來(lái)判斷文件是否是 docx 文件:

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

但我并不覺(jué)得這是一個(gè)很好的方法,因?yàn)槲业萌?gòu)建一個(gè)ZipInpuStream,這很顯然不好。另外,這個(gè)操作貌似會(huì)影響到 InputStream,所以你在讀取正常的 doc 文件會(huì)有問(wèn)題?;蛘吣闶褂?File 對(duì)象去判斷是否是一個(gè) zip 文件。但這也不是一個(gè)好方法,因?yàn)槲疫€需要在壓縮文件中讀取 doc 或者 docx 文件,所以我的輸入必須是 Inputstream,所以這個(gè)選項(xiàng)也是不可以的。 我在 stackoverflow 上和一幫老外扯了大半天,有時(shí)候我真的很懷疑這幫老外的理解能力,不過(guò)最終還是有一個(gè)大佬給出了一個(gè)讓我欣喜若狂的解決方案,FileMagic。這個(gè)是一個(gè) POI 3.17新增加的一個(gè)特性:

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);
 }
}

在這給出主要的代碼,其主要就是根據(jù) InputStream 前 8 個(gè)字節(jié)來(lái)判斷文件的類(lèi)型,毫無(wú)以為這就是最優(yōu)雅的解決方式。一開(kāi)始,其實(shí)我也是在想對(duì)于壓縮文件的前幾個(gè)字節(jié)似乎是由不同的定義的,magicmumber。因?yàn)?FileMagic 的依賴(lài)和3.16 版本是兼容的,所以我只需要加入這個(gè)類(lèi)就可以了,因此我們現(xiàn)在讀取 word 文件的正確做法是:

 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 篇

對(duì)于 excel 篇,我也就不去找之前的方案和現(xiàn)在的方案的對(duì)比了。就給出我現(xiàn)在的最佳做法了:

 @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;
 }

其實(shí),對(duì)于 excel 讀取,我的工具面臨的最大問(wèn)題就是內(nèi)存溢出。經(jīng)常在讀取某些特別大的 excel 文件的時(shí)候都會(huì)帶來(lái)一個(gè)內(nèi)存溢出的問(wèn)題。后來(lái)我終于找到一個(gè)優(yōu)秀的工具excel-streaming-reader,它可以流式的讀取 xlsx 文件,將一些特別大的文件拆分成小的文件去讀。

另外一個(gè)做的優(yōu)化就是,對(duì)于可以使用 File 對(duì)象的場(chǎng)景下,我是去使用 File 對(duì)象去讀取文件而不是使用 InputStream 去讀取,因?yàn)槭褂?InputStream 需要把它全部加載到內(nèi)存中,所以這樣是非常占用內(nèi)存的。

最后,我的一點(diǎn)小技巧就是使用 cell.getCellType 去減少一些數(shù)據(jù)量,因?yàn)槲抑恍枰@取一些文字以及數(shù)字的字符串內(nèi)容就可以了。

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

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。  

相關(guān)文章

  • SpringMVC的處理器適配器-HandlerAdapter的用法及說(shuō)明

    SpringMVC的處理器適配器-HandlerAdapter的用法及說(shuō)明

    這篇文章主要介紹了SpringMVC的處理器適配器-HandlerAdapter的用法及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 混亂的Java日志體系及集成jar包梳理分析

    混亂的Java日志體系及集成jar包梳理分析

    這篇文章主要詳細(xì)的為大家梳理分析了剪不斷理還亂的Java日志體系,以及日志系統(tǒng)涉及到的繁雜的各種集成?jar?包,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • Java比較兩個(gè)對(duì)象大小的三種方法詳解

    Java比較兩個(gè)對(duì)象大小的三種方法詳解

    在優(yōu)先級(jí)隊(duì)列中插入的元素必須能比較大小,如果不能比較大小,如插入兩個(gè)學(xué)生類(lèi)型的元素,會(huì)報(bào)ClassCastException異常。本文就為大家總結(jié)了Java比較兩個(gè)對(duì)象大小的三種方法,需要的可以參考一下
    2022-07-07
  • Java設(shè)計(jì)模式之責(zé)任鏈模式簡(jiǎn)介

    Java設(shè)計(jì)模式之責(zé)任鏈模式簡(jiǎn)介

    這篇文章主要介紹了Java設(shè)計(jì)模式之責(zé)任鏈模式,需要的朋友可以參考下
    2014-07-07
  • 關(guān)于@GetMapping和@GetMapping(value=““)的區(qū)別

    關(guān)于@GetMapping和@GetMapping(value=““)的區(qū)別

    這篇文章主要介紹了關(guān)于@GetMapping和@GetMapping(value=““)的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • SpringBoot登錄、退出、獲取用戶(hù)信息的session處理方案

    SpringBoot登錄、退出、獲取用戶(hù)信息的session處理方案

    這篇文章主要介紹了SpringBoot登錄、退出、獲取用戶(hù)信息的session處理,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-08-08
  • Java如何遠(yuǎn)程調(diào)用對(duì)方接口

    Java如何遠(yuǎn)程調(diào)用對(duì)方接口

    這篇文章主要介紹了Java如何遠(yuǎn)程調(diào)用對(duì)方接口問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • springboot?log4j2.xml如何讀取application.yml中屬性值

    springboot?log4j2.xml如何讀取application.yml中屬性值

    這篇文章主要介紹了springboot?log4j2.xml如何讀取application.yml中屬性值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • java 字符串截取的實(shí)例詳解

    java 字符串截取的實(shí)例詳解

    這篇文章主要介紹了java 字符串截取的實(shí)例詳解的相關(guān)資料,這里提供了實(shí)例代碼幫助大家實(shí)現(xiàn)這樣的功能,需要的朋友可以參考下
    2017-08-08
  • Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之KMP算法

    Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之KMP算法

    在很多地方也都經(jīng)??吹街v解KMP算法的文章,看久了好像也知道是怎么一回事,但總感覺(jué)有些地方自己還是沒(méi)有完全懂明白。這兩天花了點(diǎn)時(shí)間總結(jié)一下,有點(diǎn)小體會(huì),我希望可以通過(guò)我自己的語(yǔ)言來(lái)把這個(gè)算法的一些細(xì)節(jié)梳理清楚,也算是考驗(yàn)一下自己有真正理解這個(gè)算法
    2022-02-02

最新評(píng)論