คำนำ
POI เป็นห้องสมุดที่รู้จักกันดีสำหรับการอ่านและเขียนเอกสารของ Microsoft ภายใต้ Apache หลายคนควรใช้ 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
นี่คือข้อผิดพลาดบางอย่างที่พบในทั้งคำและ Excel:
คำ
สำหรับไฟล์ Word สิ่งที่ฉันต้องการคือการแยกข้อความในข้อความหลักในไฟล์ ดังนั้นคุณสามารถสร้างวิธีการอ่านไฟล์ doc หรือ docx:
สตริงคงที่ส่วนตัว readdoc (สตริง filepath, inputstream คือ) {String text = ""; ลอง {ถ้า (filepath.endswith ("doc")) {wordextractor ex = new wordextractor (IS); text = ex.getText (); Ex.close (); is.close (); } อื่นถ้า (filepath.endswith ("docx")) {xwpfdocument doc = ใหม่ xwpfdocument (IS); XWPFWordExtractor Extractor = ใหม่ XWPFWordExtractor (DOC); text = extractor.getText (); extractor.close (); is.close (); }} catch (exception e) {logger.error (filepath, e); } ในที่สุด {ถ้า (คือ! = null) {is.close (); }} ส่งคืนข้อความ; -ในทางทฤษฎีรหัสนี้ควรใช้งานได้สำหรับการอ่านไฟล์ DOC หรือ DOCX ส่วนใหญ่ แต่!!! ฉันพบปัญหาแปลก ๆ นั่นคือเมื่อรหัสของฉันอ่านไฟล์เอกสารบางไฟล์มันมักจะให้ข้อยกเว้นดังกล่าว:
org.apache.poi.poifs.filesystem.officexmlfileexception: ข้อมูลที่ให้มาดูเหมือนจะอยู่ใน Office 2007+ XML คุณกำลังเรียกส่วนหนึ่งของ POI ที่เกี่ยวข้องกับเอกสารสำนักงาน OLE2
ข้อยกเว้นนี้หมายความว่าอย่างไร? กล่าวง่ายๆว่าไฟล์ที่คุณเปิดไม่ใช่ไฟล์ DOC และคุณควรใช้วิธีการอ่าน DOCX เพื่ออ่าน แต่สิ่งที่เราเปิดอย่างชัดเจนคือไฟล์ที่มีเอกสารต่อท้าย!
ในความเป็นจริง Doc และ Docx นั้นแตกต่างกันเป็นหลัก DOC เป็นประเภท OLE2 ในขณะที่ DOCX เป็นประเภท OOXML หากคุณเปิดไฟล์ docx ด้วยไฟล์ที่บีบอัดคุณจะพบโฟลเดอร์บางส่วน:
ในสาระสำคัญไฟล์ docx เป็นไฟล์ zip ที่มีไฟล์ XML บางไฟล์ ดังนั้นแม้ว่าไฟล์ docx บางไฟล์มีขนาดไม่ใหญ่ แต่ไฟล์ XML ภายในมีขนาดค่อนข้างใหญ่ซึ่งเป็นเหตุผลว่าทำไมจึงใช้หน่วยความจำจำนวนมากเมื่ออ่านไฟล์ Docx บางไฟล์ที่ดูเหมือนจะไม่ใหญ่มาก
จากนั้นฉันก็เปิดไฟล์เอกสารนี้โดยใช้ไฟล์บีบอัด ตามที่คาดไว้ภายในของมันแสดงให้เห็นในภาพด้านบนดังนั้นโดยพื้นฐานแล้วเราสามารถคิดว่ามันเป็นไฟล์ docx อาจเป็นเพราะมันถูกบันทึกไว้ในโหมดความเข้ากันได้บางอย่างซึ่งนำไปสู่ปัญหาการหลอกลวง ดังนั้นตอนนี้เราสามารถตัดสินได้ว่าไฟล์เป็น doc หรือ docx ตามชื่อต่อท้ายซึ่งไม่น่าเชื่อถือ
พูดตามตรงฉันไม่คิดว่านี่เป็นปัญหาที่หายาก แต่ฉันไม่พบอะไรเกี่ยวกับเรื่องนี้ใน Google วิธีรู้ว่าไฟล์เป็น. docx หรือ. doc จาก apache poi ตัวอย่างนี้คือการใช้ zipinputstream เพื่อตรวจสอบว่าไฟล์เป็นไฟล์ docx:
บูลีน iszip = ใหม่ zipinputstream (fileStream) .getNextEntry ()! = null;
แต่ฉันไม่คิดว่านี่เป็นวิธีที่ดีเพราะฉันต้องสร้าง Zipinpustream ซึ่งเห็นได้ชัดว่าไม่ดี นอกจากนี้การดำเนินการนี้ดูเหมือนจะส่งผลกระทบต่ออินพุตสตรีมดังนั้นคุณจะมีปัญหาในการอ่านไฟล์เอกสารปกติ หรือคุณใช้วัตถุไฟล์เพื่อตรวจสอบว่าเป็นไฟล์ zip หรือไม่ แต่นี่ไม่ใช่วิธีที่ดีเช่นกันเพราะฉันต้องอ่านไฟล์ DOC หรือ DOCX ในไฟล์ที่บีบอัดดังนั้นอินพุตของฉันจะต้องเป็นอินพุทสตรีมดังนั้นตัวเลือกนี้จึงไม่เป็นไรเช่นกัน ฉันได้พูดคุยกับกลุ่มชาวต่างชาติที่ Stackoverflow เป็นส่วนใหญ่ บางครั้งฉันสงสัยความสามารถของชาวต่างชาติเหล่านี้ที่จะเข้าใจ แต่ในที่สุดช็อตใหญ่ก็ให้วิธีแก้ปัญหาที่ทำให้ฉันมีความสุข นี่คือฟีเจอร์ใหม่ที่เพิ่มเข้ามาใน POI 3.17:
public enum filemagic { / ** ole2 / biff8+ สตรีมที่ใช้สำหรับสำนักงาน 97 และเอกสารที่สูงขึ้น* / ole2 (headerblockconstants._signature), / ** ooxml / zip stream* / ooxml (ouxml_file_header), /* 2 */ biff2 (ไบต์ใหม่ [] {0x09, 0x00, // sid = 0x0009 0x04, 0x00, // size = 0x0004 0x00, 0x00, // unused 0x70, 0x00 // 0x70 = multiple values}) // sid = 0x0209 0x06, 0x00, // size = 0x0006 0x00, 0x00, // unused 0x70, 0x00 // 0x70 = ค่าหลายค่า}), /** biff4 stream raw - สำหรับ Excel 4* /biff4 size = 0x0006 0x00, 0x00, // unused 0x70, 0x00 // 0x70 = หลายค่า}, ไบต์ใหม่ [] {0x09, 0x04, // sid = 0x0409 0x06, 0x00, // size = 0x0006 0x00, 0x00 MSWRITE (BYTE ใหม่ [] {0x31, (BYTE) 0xBE, 0x00, 0x00}, BYTE ใหม่ [] {0x32, (BYTE) 0xBE, 0x00, 0x00}),/** rtf เอกสาร*/rtf ("{// rtf") enum สุดท้าย! / ** เวทมนตร์ที่ไม่รู้จัก*/ ไม่ทราบ (ไบต์ใหม่ [0]); ไบต์สุดท้าย [] [] เวทมนตร์; FileMagic (Magic ยาว) {this.magic = 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) {สำหรับ (filemagic fm: values ()) {int i = 0; บูลีนพบ = จริง; สำหรับ (byte [] ma: fm.magic) {สำหรับ (byte m: ma) {byte d = magic [i ++]; if (! (d == m || (m == 0x70 && (d == 0x10 || d == 0x20 || d == 0x40)))) {พบ = false; หยุดพัก; }} if (พบ) {return fm; }}} return unknown; } / ** * รับความมหัศจรรย์ของไฟล์ของอินพุตที่ให้มา (ซึ่งต้องสนับสนุนเครื่องหมายและรีเซ็ต). <p> * * หากไม่แน่ใจว่าอินพุตของคุณสนับสนุนเครื่องหมาย / รีเซ็ต * ใช้ {@link #prepareTocheckMagic ค่าเฉลี่ย * ว่า zip stream เป็นผู้นำไบต์ขยะ * * @param inp inputstream ซึ่งรองรับ mark/ reset */ public public filemagic valueof (inputstream inp) พ่น ioexception {ถ้า (inp.marksupported () {โยน ioexception ใหม่ } // คว้า 8 ไบต์ไบต์แรก [] data = ioutils.peekfirst8bytes (inp); ส่งคืน filemagic.valueof (ข้อมูล); } / ** * ตรวจสอบว่า {@link inputstream} สามารถรีเซ็ตได้ (เช่นใช้สำหรับการตรวจสอบเวทมนตร์ส่วนหัว) และห่อมันถ้าไม่ใช่ * * @param สตรีมสตรีมที่จะตรวจสอบสำหรับการห่อ * @return เครื่องหมายสตรีมที่เปิดใช้งาน } // เราใช้ในการประมวลผลข้อมูลผ่าน pushbackInputStream แต่รหัสผู้ใช้สามารถให้หนึ่งที่เล็กเกินไป // ดังนั้นเราจึงใช้ bufferedInputStream แทนตอนนี้กลับ bufferedInputStream ใหม่ (สตรีม); -นี่คือรหัสหลักซึ่งส่วนใหญ่จะกำหนดประเภทไฟล์ตาม 8 ไบต์แรกของอินพุตสตรีม ไม่มีวิธีคิดว่านี่เป็นทางออกที่สง่างามที่สุด ในตอนแรกฉันคิดว่าจริง ๆ แล้วสองสามไบต์ของไฟล์ที่ถูกบีบอัดดูเหมือนจะถูกกำหนดโดยไฟล์ที่แตกต่างกัน MagicMumber เนื่องจากการพึ่งพาของ FileMagic เข้ากันได้กับเวอร์ชัน 3.16 ฉันแค่ต้องเพิ่มคลาสนี้ดังนั้นวิธีที่ถูกต้องสำหรับเราในการอ่านไฟล์คำตอนนี้คือ:
สตริงคงที่ส่วนตัว readdoc (สตริง filepath, inputstream คือ) {String text = ""; IS = fileMagic.preparetocheckMagic (IS); ลอง {ถ้า (fileMagic.ValueOf (IS) == fileMagic.ole2) {WordExtractor ex = new wordExtractor (IS); text = ex.getText (); Ex.close (); } อื่นถ้า (fileMagic.ValueOf (IS) == fileMagic.ooxml) {xwpfdocument doc = ใหม่ xwpfdocument (IS); XWPFWordExtractor Extractor = ใหม่ XWPFWordExtractor (DOC); text = extractor.getText (); extractor.close (); }} catch (exception e) {logger.error ("สำหรับไฟล์" + filepath, e); } ในที่สุด {ถ้า (คือ! = null) {is.close (); }} ส่งคืนข้อความ; - ยอดเยี่ยม
สำหรับบทความ Excel ฉันจะไม่มองหาการเปรียบเทียบระหว่างแผนก่อนหน้าและแผนปัจจุบัน ฉันจะให้แนวทางปฏิบัติที่ดีที่สุดตอนนี้:
@suppresswarnings ("การเสื่อมราคา") readexcel สตริงแบบคงที่ส่วนตัว (String filepath, inputstream inp) โยนข้อยกเว้น {workbook wb; StringBuilder sb = new StringBuilder (); ลอง {ถ้า (filepath.endswith (". xls")) {wb = new hssfworkbook (inp); } else {wb = streamingreader.builder () .rowcachesize (1,000) // จำนวนแถวที่จะเก็บไว้ในหน่วยความจำ (ค่าเริ่มต้นถึง 10). buffersize (4096) // ขนาดบัฟเฟอร์ที่จะใช้เมื่ออ่านอินพุตสตรีมไปยังไฟล์ // inputStream หรือไฟล์สำหรับไฟล์ XLSX (จำเป็น)} sb = readsheet (wb, sb, filepath.endswith (". xls")); wb.close (); } catch (ole2notofficexmlfileexception e) {logger.error (filepath, e); } ในที่สุด {ถ้า (inp! = null) {inp.close (); }} ส่งคืน sb.toString (); } สตริงคงที่ส่วนตัว readExcelByFile (สตริง filePath, ไฟล์ไฟล์) {เวิร์กบุ๊ก wb; StringBuilder sb = new StringBuilder (); ลอง {ถ้า (filepath.endswith (". xls")) {wb = workbookfactory.create (ไฟล์); } else {wb = streamingReader.builder () .rowcachesize (1,000) // จำนวนแถวที่จะเก็บไว้ในหน่วยความจำ (ค่าเริ่มต้นถึง 10). buffersize (4096) // ขนาดบัฟเฟอร์ที่จะใช้เมื่ออ่านอินพุตสตรีมไปยังไฟล์ // inputStream หรือไฟล์สำหรับไฟล์ XLSX (จำเป็น)} sb = readsheet (wb, sb, filepath.endswith (". xls")); wb.close (); } catch (exception e) {logger.error (filepath, e); } return sb.toString (); } readsheet StringBuilder ส่วนตัว (Workbook WB, StringBuilder SB, บูลีน isxls) พ่นข้อยกเว้น {สำหรับ (แผ่นชีท: wb) {สำหรับ (แถว R: แผ่น) {สำหรับ (เซลล์เซลล์: r) {if (cell.getCelltype () == cell.cell_ty sb.append (cell.getStringCellValue ()); sb.append (""); } อื่นถ้า (cell.getCellType () == cell.cell_type_numeric) {ถ้า (isxls) {dataFormatter formatter = new dataFormatter (); SB.Append (FormatCellValue (เซลล์)); } else {sb.append (cell.getStringCellValue ()); } sb.append (""); }}}} ส่งคืน sb; -ในความเป็นจริงสำหรับการอ่าน Excel ปัญหาที่ใหญ่ที่สุดที่เครื่องมือของฉันต้องเผชิญคือหน่วยความจำล้น บ่อยครั้งที่การอ่านไฟล์ Excel ขนาดใหญ่โดยเฉพาะจะทำให้เกิดปัญหาการไหลล้นของหน่วยความจำ ในที่สุดฉันก็พบเครื่องมือที่ยอดเยี่ยม Excel-Streaming-Reader ซึ่งสามารถปรับปรุงไฟล์ XLSX และแยกไฟล์ขนาดใหญ่โดยเฉพาะออกเป็นไฟล์ขนาดเล็กเพื่ออ่าน
การปรับให้เหมาะสมอีกอย่างคือในสถานการณ์ที่สามารถใช้วัตถุไฟล์ได้ฉันใช้วัตถุไฟล์เพื่ออ่านไฟล์แทนการใช้อินพุตสตรีมเพื่ออ่านเพราะการใช้อินพุตสตรีมต้องการให้โหลดทั้งหมดลงในหน่วยความจำดังนั้นนี่จึงใช้หน่วยความจำมาก
ในที่สุดเคล็ดลับเล็ก ๆ น้อย ๆ ของฉันคือการใช้ Cell.GetCellType เพื่อลดปริมาณข้อมูลเพราะฉันต้องได้รับเนื้อหาสตริงข้อความและตัวเลขบางส่วนเท่านั้น
ข้างต้นคือการสำรวจและการค้นพบของฉันเมื่ออ่านไฟล์โดยใช้ POI และฉันหวังว่ามันจะเป็นประโยชน์กับคุณ ตัวอย่างข้างต้นจะถูกนำไปใช้ในเครื่องมือของฉันอย่างใดอย่างหนึ่งทุกที่ (เครื่องมือนี้สามารถช่วยคุณค้นหาข้อความทั้งหมดของเนื้อหาบนคอมพิวเตอร์ของคุณ) หากคุณสนใจคุณสามารถดูได้ ยินดีต้อนรับสู่ Star หรือ PR
สรุป
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่าเนื้อหาของบทความนี้จะมีค่าอ้างอิงบางอย่างสำหรับการศึกษาหรือที่ทำงานของทุกคน หากคุณมีคำถามใด ๆ คุณสามารถฝากข้อความไว้เพื่อสื่อสาร ขอบคุณสำหรับการสนับสนุน Wulin.com