Prefácio
O POI é uma biblioteca bem conhecida para ler e escrever documentos da Microsoft no Apache. Muitas pessoas deveriam ter usado o POI ao exportar relatórios, criar documentos de palavras e ler -os. A POI traz grande conveniência a essas operações. Uma das ferramentas que fiz recentemente é ler arquivos Word e Excel no meu computador.
Descrição da estrutura PoI
Nome do pacote Descrição
O HSSF fornece a capacidade de ler e escrever arquivos de formato do Microsoft Excel XLS.
O XSSF fornece a capacidade de ler e escrever arquivos de formato do Microsoft Excel Ooxml XLSX.
O HWPF fornece a capacidade de ler e escrever arquivos de formato do Microsoft Word Doc.
O HSLF fornece a capacidade de ler e escrever arquivos do formato do Microsoft PowerPoint.
O HDGF fornece a capacidade de ler arquivos de formato Microsoft Visio.
O HPBF fornece a capacidade de ler arquivos no formato do Microsoft Publisher.
O HSMF fornece a função de ler arquivos do formato do Microsoft Outlook.
Aqui estão algumas das armadilhas encontradas no Word e Excel:
Palavra
Para arquivos do Word, tudo o que preciso é extrair o texto no texto principal no arquivo. Assim, você pode criar um método para ler o arquivo DOC ou DOCX:
String estática privada readdoc (string filepath, inputStream is) {string text = ""; tente {if (filepath.endswith ("doc")) {wordextractor ex = new wordExtractor (is); texto = ex.getText (); ex.Close (); is.close (); } else if (filepath.endswith ("docx")) {xwpfdocument doc = new xwpfdocument (is); XwpfwordExtractor extrator = new XWPFWordExtractor (doc); texto = extrator.getText (); extrator.close (); is.close (); }} catch (Exceção e) {Logger.error (filepath, e); } finalmente {if (is! = null) {is.close (); }} retornar texto; }Em teoria, esse código deve ser válido para a leitura da maioria dos arquivos DOC ou DOCX. Mas!!! Encontrei um problema estranho, ou seja, quando meu código lê certos arquivos do documento, geralmente dá essa exceção:
org.apache.poi.poifs.filesystem.officexmlfileException: Os dados fornecidos parecem estar no Office 2007+ XML. Você está fazendo a parte do POI que lida com documentos do Ole2 Office.
O que essa exceção significa? Em termos simples, o arquivo que você abriu não é um arquivo do documento e você deve usar o método de ler DOCX para lê -lo. Mas o que estamos abrindo claramente é um arquivo com o documento do sufixo!
De fato, Doc e Docx são essencialmente diferentes. Doc é o tipo ole2, enquanto o docx é o tipo de ooxml. Se você abrir um arquivo DOCX com um arquivo compactado, encontrará algumas pastas:
Em essência, o arquivo DOCX é um arquivo zip que contém alguns arquivos XML. Portanto, embora alguns arquivos DOCX não sejam de tamanho grande, os arquivos XML internos são realmente relativamente grandes, e é por isso que consome muita memória ao ler alguns arquivos DOCX que não parecem ser muito grandes.
Então eu abri esse arquivo DOC usando um arquivo compactado. Como esperado, seus internos são como mostrado na figura acima, então, essencialmente, podemos pensar nisso como um arquivo DOCX. Talvez seja porque é salvo em algum modo de compatibilidade, o que leva a esse problema de golpe. Portanto, agora podemos julgar se um arquivo é doc ou docx com base no nome do sufixo, que não é confiável.
Para ser sincero, não acho que esse seja um problema raro. Mas não encontrei nada sobre isso no Google. Como saber se um arquivo é .docx ou .doc formato do apache poi Este exemplo é usar o ZipinputStream para determinar se um arquivo é um arquivo docx:
boolean iSzip = new ZipinputStream (filestream) .getNextEntry ()! = null;
Mas não acho que seja uma boa maneira, porque tenho que construir um zipinpustream, o que obviamente não é bom. Além disso, esta operação parece afetar o InputStream, portanto, você terá problemas para ler arquivos doces normais. Ou você usa o objeto de arquivo para determinar se é um arquivo zip. Mas essa também não é uma boa maneira, porque eu também preciso ler o arquivo DOC ou DOCX no arquivo compactado, para que minha entrada deve ser InputStream, para que essa opção também não esteja bem. Conversei com um grupo de estrangeiros no Stackoverflow durante a maior parte do tempo. Às vezes, eu realmente duvidava da capacidade desses estrangeiros de entender, mas no final, um figurão me deu uma solução que me deixou em êxtase, Filemagic. Este é um novo recurso adicionado ao POI 3.17:
public enum filemagic { / ** ole2 / biff8+ fluxo usado para o Office 97 e documentos superiores* / ole2 (headerblockconstants._signature), / ** ooxml / zip stream* / ooxml (ooxml_file_header), / ** xml* / xml (_xml (rawml_ml), / ** ** / ** /* / **) / ** / **) / **) / **) / **) / **) / **) / ** / **) / ** / ** / ** / **) / **) / **) 2 */ biff2 (novo byte [] {0x09, 0x00, // sid = 0x0009 0x04, 0x00, // size = 0x0004 0x00, 0x00, // não utilizado 0x70, 0x00 // 0x70 = valores múltiplos),/ ** biff3), para excel 3 *// 0x70 = valores múltiplos),/ ** biff3 - para excel 3 * // sid = 0x0209 0x06, 0x00, // size = 0x0006 0x00, 0x00, // não utilizado 0x70, 0x00 // 0x70 = valores múltiplos}), /** biff4 fluxo bruto - para Excel 4* /biff4 (novo byte [] tamanho = 0x0006 0x00, 0x00, // não utilizado 0x70, 0x00 // 0x70 = valores múltiplos}, novo byte [] {0x09, 0x04, // sid = 0x0409 0x06, 0x00, // size = 0x0006 0x00, 0x009 0x06, 0x00, // size = 0x0006 0x00, 0x00, //usouse Mswrite (novo byte [] {0x31, (byte) 0xBE, 0x00, 0x00}, novo byte [] {0x32, (byte) 0xBE, 0x00, 0x00}),/** rtf document*/rtf ("{// rtf"),/** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd ** pd (**), pd document*/retn), Última Enum! / ** Magic desconhecido*/ desconhecido (novo byte [0]); Byte final [] [] Magic; Filemagic (longo magia) {this.magic = novo 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: valores ()) {int i = 0; booleano encontrado = true; para (byte [] ma: fm.magic) {for (byte m: ma) {byte d = magic [i ++]; if (! (d == m || (m == 0x70 && (d == 0x10 || d == 0x20 || d == 0x40)))) {encontrado = false; quebrar; }} if (encontrado) {return fm; }}} retornar desconhecido; } / ** * Obtenha a magia do arquivo do InputStream fornecido (que deve * suportar a marca e a redefinição). Isso poderia significar potencialmente, * que o fluxo zip possui bytes líderes lixo * * @param inp um inputStream que suporta Mark/ Redefinir */ public estático Valor Filemagic (inputStream inp) lança a IoException (se (! } // pegue os 8 primeiros bytes byte [] data = ioutils.peekfirst8bytes (inp); return filemagic.valueof (dados); } / ** * Verifica se um {@link inputStream} pode ser redefinido (ou seja, usado para verificar a magia do cabeçalho) e envolve -o se não * * @param Stream Stream para ser verificado para embrulhar * @RETURN UMATEM ALIFICADO (Public Static InputStream PreparetocechMort (stream) {se flua) {se flui); } // usamos para processar os dados por meio de um pushbackInputStream, mas o código do usuário poderia fornecer um pequeno muito pequeno //, por isso usamos um bufferInputStream agora retorna o new bufferInputStream (stream); }}Aqui está o código principal, que determina principalmente o tipo de arquivo com base nos 8 primeiros bytes do InputStream. Não há como pensar que esta é a solução mais elegante. No começo, eu estava realmente pensando que os primeiros bytes do arquivo comprimido pareciam ser definidos por um diferente, MagicMumber. Como as dependências da Filemagic são compatíveis com a versão 3.16, eu só preciso adicionar esta classe; portanto, a maneira correta de ler o arquivo do Word agora é:
String estática privada readdoc (string filepath, inputStream is) {string text = ""; é = FILEMAGIC.PreparetocheckMagic (IS); tente {if (filemagic.valueof (is) == filemagic.ole2) {wordExtractor ex = new wordExtractor (is); texto = ex.getText (); ex.Close (); } else if (filemagic.valueof (is) == filemagic.ooxml) {xwpfdocument doc = new xwpfdocument (is); XwpfwordExtractor extrator = new XWPFWordExtractor (doc); texto = extrator.getText (); extrator.close (); }} catch (Exceção e) {Logger.error ("para arquivo" + filepath, e); } finalmente {if (is! = null) {is.close (); }} retornar texto; } Excel
Para o artigo do Excel, não procurarei comparações entre o plano anterior e o plano atual. Vou me dar as melhores práticas agora:
@Suppresswarnings ("deprecação") Private Static String ReadExcel (string filepath, inputStream INP) lança a exceção {Book WB; Stringbuilder sb = new stringbuilder (); tente {if (filepath.endswith (". xls")) {wb = novo hssfworkbook (inp); } else {wb = streamingReader.builder () .rowcachesize (1000) // Número de linhas para manter na memória (padrão para 10) .Buffersize (4096) // Tamanho do buffer a ser usado ao ler o InputStream para arquivar (padrão para 1024) .Open (inp); // inputStream ou arquivo para o arquivo xlsx (obrigatório)} sb = readsheet (wb, sb, filepath.endswith (". Xls")); wb.close (); } catch (ole2notofficexmlfileException e) {logger.error (filepath, e); } finalmente {if (inp! = null) {inp.close (); }} return sb.toString (); } String estática privada ReadExcelByFile (String filepath, arquivo de arquivo) {WorkBook WB; Stringbuilder sb = new stringbuilder (); tente {if (filepath.endswith (". xls")) {wb = workbookfactory.create (arquivo); } else {wb = streamingReader.builder () .rowcachesize (1000) // Número de linhas para manter na memória (padrão para 10) .Buffersize (4096) // Tamanho do buffer a ser usado ao ler o InputStream para arquivar (padrão para 1024) .OPEN (arquivo); // inputStream ou arquivo para o arquivo xlsx (obrigatório)} sb = readsheet (wb, sb, filepath.endswith (". Xls")); wb.close (); } catch (Exceção e) {Logger.error (filepath, e); } return sb.toString (); } Private StringBuilder ReadSheet (pasta de trabalho WB, Stringbuilder SB, Boolean ISXLS) lança Exceção {for (folha: wb) {for (linha r: sheet) {for (celular: r) {if (Cell.getCelltype () == Cell.Cell_TySELCE_STRING) sb.append (""); } else if (Cell.getCellType () == Cell.cell_type_numeric) {if (isxls) {dataFormatter formatter = new DataFormatter (); sb.append (formatCellValue (célula)); } else {sb.append (Cell.getStringCellValue ()); } sb.append (""); }}}} retorna sb; }De fato, para o Excel Read, o maior problema que minha ferramenta enfrenta é o excesso de memória. Freqüentemente, a leitura de certos arquivos do Excel particularmente grande causará um problema de transbordamento de memória. Mais tarde, finalmente encontrei uma excelente ferramenta Excel-Streaming-Reader, que pode otimizar arquivos XLSX e dividir alguns arquivos particularmente grandes em arquivos pequenos para ler.
Outra otimização é que, no cenário em que os objetos de arquivo podem ser usados, uso objetos de arquivo para ler arquivos em vez de usar o InputStream para lê-los, porque o uso do InputStream exige que todos sejam carregados na memória, portanto isso é muito consumido pela memória.
Finalmente, meu pequeno truque é usar o Cell.getCellType para reduzir a quantidade de dados, porque eu só preciso obter algum conteúdo de texto e números de texto.
O exposto acima são algumas das minhas explorações e descobertas ao ler arquivos usando POI, e espero que seja útil para você. Os exemplos acima também são aplicados em uma das minhas ferramentas em todos os lugares (essa ferramenta pode ajudar principalmente a pesquisar o texto completo do conteúdo no seu computador). Se você estiver interessado, pode dar uma olhada. Bem -vindo ao Star ou PR.
Resumir
O acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.