Prefacio
POI es una biblioteca bien conocida para leer y escribir los documentos de Microsoft bajo Apache. Muchas personas deberían haber usado POI al exportar informes, crear documentos de palabras y leerlos. POI trae una gran comodidad a estas operaciones. Una de las herramientas que he hecho recientemente es leer archivos de Word y Excel en mi computadora.
Descripción de la estructura de POI
Descripción del nombre del paquete
HSSF proporciona la capacidad de leer y escribir archivos de formato de Microsoft Excel XLS.
XSSF proporciona la capacidad de leer y escribir Archivos de formato Microsoft Excel Ooxml XLSX.
HWPF proporciona la capacidad de leer y escribir Archivos de formato de Microsoft Word DOC.
HSLF proporciona la capacidad de leer y escribir archivos de formato de Microsoft PowerPoint.
HDGF proporciona la capacidad de leer los archivos de formato de Microsoft Visio.
HPBF proporciona la capacidad de leer archivos en formato de Microsoft Publisher.
HSMF proporciona la función de leer los archivos de formato de Microsoft Outlook.
Estas son algunas de las trampas encontradas en Word y Excel:
Palabra
Para los archivos de Word, todo lo que necesito es extraer el texto en el texto principal en el archivo. Para que pueda crear un método para leer el archivo DOC o DOCX:
string static private readdoc (string filepath, inputStream is) {string text = ""; Pruebe {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 (); }} capt (excepción e) {logger.error (filepath, e); } finalmente {if (is! = null) {is.close (); }} Texto de retorno; }En teoría, este código debe ser válido para leer la mayoría de los archivos DOC o DOCX. ¡¡¡Pero!!! Encontré un problema extraño, es decir, cuando mi código lee ciertos archivos DOC, a menudo da tal excepción:
org.apache.poi.poifs.filesystem.OfficExmlFileException: los datos suministrados parecen estar en la oficina 2007+ XML. Estás llamando a la parte de POI que trata con los documentos de la oficina de Ole2.
¿Qué significa esta excepción? En términos simples, el archivo que abrió no es un archivo DOC, y debe usar el método de leer DOCX para leerlo. ¡Pero lo que claramente estamos abriendo es un archivo con sufijo doc!
De hecho, Doc y Docx son esencialmente diferentes. DOC es de tipo OLE2, mientras que DOCX es de tipo OOXML. Si abre un archivo DOCX con un archivo comprimido, encontrará algunas carpetas:
En esencia, el archivo DOCX es un archivo zip que contiene algunos archivos XML. Por lo tanto, aunque algunos archivos DOCX no son de tamaño grande, los archivos XML en el interior son realmente relativamente grandes, por lo que consume mucha memoria al leer algunos archivos DOCX que no parecen ser muy grandes.
Luego abrí este archivo DOC usando un archivo comprimido. Como se esperaba, sus partes internas son como se muestra en la imagen de arriba, por lo que esencialmente podemos considerarlo como un archivo DOCX. Tal vez sea porque se guarda en algún modo de compatibilidad, lo que conduce a tal problema de estafa. Entonces, ahora podemos juzgar si un archivo es DOC o DOCX basado en el nombre del sufijo, que no es confiable.
Para ser honesto, no creo que este sea un problema raro. Pero no encontré nada sobre esto en Google. Cómo saber si un archivo es .docx o .doc formato de apache poi Este ejemplo es usar ZipInputStream para determinar si un archivo es un archivo DOCX:
boolean iszip = new ZipInputStream (FilErteam) .getNextEntry ()! = NULL;
Pero no creo que esta sea una buena manera, porque tengo que construir un zipinpustream, lo cual obviamente no es bueno. Además, esta operación parece afectar InputStream, por lo que tendrá problemas para leer archivos DOC normales. O usa el objeto de archivo para determinar si es un archivo zip. Pero esta tampoco es una buena manera, porque también necesito leer el archivo DOC o DOCX en el archivo comprimido, por lo que mi entrada debe ser InputStream, por lo que esta opción tampoco está bien. Hablé con un grupo de extranjeros en Stackoverflow la mayor parte del tiempo. A veces realmente dudaba de la capacidad de estos extranjeros para entender, pero al final, un gran disparo me dio una solución que me hizo extático, filemágico. Esta es una nueva característica agregada a POI 3.17:
public enum filemagic { / ** ole2 / biff8+ stream utilizado para Office 97 y documentos superiores* / ole2 (headerBlockConstants._signature), / ** ooxml / zip stream* / ooxml (ooxml_file_header), / ** archivo xml* / xml (raw_xml_file_header), / stream), / stream), / ** xml* / xml (raw_xml_file_header), / storn 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, // tamaño = 0x0006 0x00, 0x00, // sin usar 0x70, 0x00 // 0x70 = múltiples valores}, nuevo byte [] {0x09, 0x04, // sid = 0x0409 0x06, 0x00, // size = 0x0006 0x00, 0x00, // sin usar 0x00, 0x01}), / /size crudo. MswRite (nuevo byte [] {0x31, (byte) 0xbe, 0x00, 0x00}, new byte [] {0x32, (byte) 0xbe, 0x00, 0x00}),/** Documento rtf*/rtf ("{// rtf"),/** PDF Document*/pdf ("%pdf"),////mantenimiento siempre ¡Enume! / ** Magia desconocida*/ desconocida (nuevo byte [0]); Byte final [] [] Magia; Filemagic (larga magia) {this.magic = new byte [1] [8]; Littleendian.putlong (this.magic [0], 0, magia); } Filemagic (byte [] ... magia) {this.magic = mágico; } FileMagic (String Magic) {this (Magic.getBytes (loceutil.charset_1252)); } public static static filemagic valueOf (byte [] magic) {for (filemagic fm: value ()) {int i = 0; booleano encontrado = verdadero; 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 = falso; romper; }} if (encontrado) {return fm; }}} return desconocido; } / ** * Obtenga la magia de archivo del InputStream suministrado (que debe * Apoyar la marca y restablecer). <p> * * Si no está seguro si su InputStream es compatible con Mark / Reset, * use {@link #PreparetOcheckMagic (inputStream)} para envolverlo y asegurar * que debe usar eso, y no el original! <P> * * * * * * incluso si este método regresa {@@@@@@@@@@@@@@@@@@@ # # # significa * que la transmisión ZIP tiene bytes basura líderes * * @param INP Un inputStream que admite mark/ reset */ public static filemagic valueOf (inputStream inp) lanza ioexception {if (! inp.markSupported ()) {tirar nueva iOexception ("getFilemagic () solo opera en flores que de apoyo (int)"); ");"); } // toma los primeros 8 bytes bytes [] data = ioutils.peekfirst8bytes (inp); return fileMagic.ValueOf (datos); } / ** * verifica si se puede restablecer un {@link inputStream} (es decir, se usa para verificar la magia del encabezado) y la envuelve si no * * * @param stream stream se verificará para envolver * @@return una marca habilitada * / public fallstream PrepareToCheckMagic (inputStream stream) {if (strot. } // Solíamos procesar los datos a través de un retroceso, un código de usuario, pero el código de usuario podría proporcionar uno demasiado pequeño //, por lo que usamos un BufferedInputStream en su lugar ahora devuelve el nuevo bufferedInputStream (flujo); }}Aquí está el código principal, que determina principalmente el tipo de archivo basado en los primeros 8 bytes de InputStream. No hay forma de pensar que esta es la solución más elegante. Al principio, en realidad estaba pensando que los primeros bytes del archivo comprimido parecían definirse por uno diferente, MagicMumber. Debido a que las dependencias de Filemagic son compatibles con la versión 3.16, solo necesito agregar esta clase, por lo que la forma correcta de leer el archivo de Word ahora es:
string static private readdoc (string filepath, inputStream is) {string text = ""; es = filemagic.preparetocheckmagic (is); Pruebe {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 (); }} capt (excepción e) {logger.error ("para archivo" + filepath, e); } finalmente {if (is! = null) {is.close (); }} Texto de retorno; } Sobresalir
Para el artículo de Excel, no buscaré comparaciones entre el plan anterior y el plan actual. Me daré las mejores prácticas ahora:
@Suppleswarnings ("deprecation") cadena estática privada readExcel (string filepath, inputStream inp) lanza excepción {Workbook wb; StringBuilder sb = new StringBuilder (); Pruebe {if (filePath.endswith (". Xls")) {wb = new HSSFWorkBook (INP); } else {wb = streamingReader.builder () .RowCachesize (1000) // Número de filas para mantener en la memoria (predeterminada a 10) .BufferSize (4096) // Tamaño del búfer para usar al leer EntryStream a archivo (predeterminado a 1024) .Pen (INP); // InputStream o archivo para el archivo XLSX (requerido)} 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 (); } cadena estática privada readExcelByFile (string filepath, archivo archivo) {Workbook wb; StringBuilder sb = new StringBuilder (); Pruebe {if (filePath.endSwith (". Xls")) {wb = workbookFactory.create (archivo); } else {wb = streamingReader.builder () .RowCachesize (1000) // Número de filas para mantener en la memoria (predeterminada a 10) .BufferSize (4096) // Tamaño del búfer para usar al leer Entrones de entrada a archivo (predeterminado a 1024) .Pen (archivo); // InputStream o archivo para el archivo XLSX (requerido)} sb = readsheet (wb, sb, filepath.endswith (". Xls")); wb.close (); } capt (excepción e) {logger.error (filepath, e); } return sb.ToString (); } private static stringBuilder Readsheet (Workbook WB, StringBuilder SB, Boolean ISXLS) arroja excepción {for (hoja: wb) {for (fila r: sheet) {for (celular: r) {if (celular.getCellType () == celular.cell_type_string) {sb.append (cell.getStingStype () () (). 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 (""); }}}} return sb; }De hecho, para Excel Read, el mayor problema que enfrenta mi herramienta es el desbordamiento de la memoria. A menudo, leer ciertos archivos de Excel particularmente grandes causará un problema de desbordamiento de memoria. Más tarde, finalmente encontré un excelente lector de transmisión de Excel, que puede agilizar los archivos XLSX y dividir algunos archivos particularmente grandes en archivos pequeños para leer.
Otra optimización es que en el escenario en el que se pueden usar objetos de archivo, uso objetos de archivo para leer archivos en lugar de usar InputStream para leerlos, porque el uso de InputStream requiere que todos se carguen en la memoria, por lo que esto es muy consumo de memoria.
Finalmente, mi pequeño truco es usar Cell.getCellType para reducir la cantidad de datos, porque solo necesito obtener un contenido de cadena de texto y números.
Los anteriores son algunas de mis exploraciones y descubrimientos al leer archivos que usan POI, y espero que sea útil para usted. Los ejemplos anteriores también se aplican en una de mis herramientas en todas partes (esta herramienta puede ayudarlo principalmente a buscar el texto completo del contenido en su computadora). Si está interesado, puede echar un vistazo. Bienvenido a Star o PR.
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.