مقدمة
POI هي مكتبة معروفة لقراءة وكتابة مستندات Microsoft تحت Apache. كان ينبغي على الكثير من الناس استخدام POI عند تصدير التقارير ، وإنشاء مستندات الكلمات ، وقراءتها. POI لا يجلب راحة كبيرة لهذه العمليات. واحدة من الأدوات التي صنعتها مؤخرًا هي قراءة ملفات Word and 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.
فيما يلي بعض المزالق التي تمت مواجهتها في كل من الكلمة والتفوق:
كلمة
بالنسبة لملفات الكلمات ، كل ما أحتاجه هو استخراج النص في النص الرئيسي في الملف. حتى تتمكن من إنشاء طريقة لقراءة ملف DOC أو DOCX:
سلسلة ثابتة خاصة readDoc (سلسلة filepath ، inputStream) {string text = "" ؛ حاول {if (filePath.endswith ("doc")) {WordExtractor ex = new WordExtractor (IS) ؛ النص = ex.getText () ؛ ex.close () ؛ is.close () ؛ } آخر إذا (filePath.endswith ("docx")) {xwpfdocument doc = new xwpfdocument (IS) ؛ xwpfwordextractor extractor = new xwpfwordextractor (doc) ؛ النص = extractor.getText () ؛ extractor.close () ؛ is.close () ؛ }} catch (استثناء e) {logger.error (filepath ، e) ؛ } أخيرًا {if (is! = null) {is.close () ؛ }} نص الإرجاع ؛ }من الناحية النظرية ، يجب أن يكون هذا الرمز صالحًا لقراءة معظم ملفات DOC أو DOCX. لكن!!! لقد وجدت مشكلة غريبة ، أي عندما يقرأ الكود الخاص بي بعض ملفات المستندات ، فإنه غالبًا ما يعطي مثل هذا الاستثناء:
org.apache.poi.poifs.filesystem.officexmlfileException: يبدو أن البيانات المقدمة في Office 2007+ XML. أنت تتصل بجزء من POI الذي يتعامل مع وثائق مكتب OLE2.
ماذا يعني هذا الاستثناء؟ بعبارات بسيطة ، فإن الملف الذي فتحته ليس ملف مستند ، ويجب عليك استخدام طريقة قراءة DOCX لقراءته. لكن ما نفتحه بوضوح هو ملف مع Doc Factix!
في الواقع ، DOC و DOCX يختلفان بشكل أساسي. DOC هو نوع OLE2 ، بينما DOCX هو نوع OOXML. إذا قمت بفتح ملف docx بملف مضغوط ، فستجد بعض المجلدات:
في جوهرها ، ملف DOCX عبارة عن ملف مضغوط يحتوي على بعض ملفات XML. لذلك ، على الرغم من أن بعض ملفات DOCX ليست كبيرة في الحجم ، إلا أن ملفات XML الموجودة في الداخل كبيرة نسبيًا ، وهذا هو السبب في أنها تستهلك الكثير من الذاكرة عند قراءة بعض ملفات DOCX التي لا يبدو أنها كبيرة جدًا.
ثم فتحت ملف المستند هذا باستخدام ملف مضغوط. كما هو متوقع ، فإن الداخلية لها كما هو موضح في الصورة أعلاه ، لذلك يمكننا أن نفكر في الأمر كملف DOCX. ربما يكون ذلك لأنه يتم حفظه في بعض وضع التوافق ، مما يؤدي إلى مشكلة الاحتيال هذه. لذا ، يمكننا الآن الحكم على ما إذا كان الملف هو DOC أو DOCX بناءً على اسم لاحقة ، وهو أمر غير موثوق به.
أن نكون صادقين ، لا أعتقد أن هذه مشكلة نادرة. لكنني لم أجد أي شيء عن هذا على Google. كيفية معرفة ما إذا كان الملف هو .docx أو .doc من Apache POI هذا المثال هو استخدام ZipinputStream لتحديد ما إذا كان الملف هو ملف docx:
Boolean iszip = new ZipinputStream (fileStream) .getNextentry ()! = null ؛
لكنني لا أعتقد أن هذه طريقة جيدة ، لأنني يجب أن أبني ZipinPustream ، وهو أمر غير جيد. بالإضافة إلى ذلك ، يبدو أن هذه العملية تؤثر على InputStream ، لذلك سيكون لديك مشاكل في قراءة ملفات DOC العادية. أو يمكنك استخدام كائن الملف لتحديد ما إذا كان ملف zip. ولكن هذه ليست طريقة جيدة أيضًا ، لأنني بحاجة أيضًا إلى قراءة ملف DOC أو DOCX في الملف المضغوط ، لذلك يجب أن يكون إدخالي إدخال ، لذلك هذا الخيار ليس على ما يرام أيضًا. تحدثت مع مجموعة من الأجانب على Stackoverflow لمعظم الوقت. في بعض الأحيان ، كنت أشك حقًا في قدرة هؤلاء الأجانب على فهمهم ، لكن في النهاية ، أعطتني لقطة كبيرة حلاً جعلني نشوة ، وخيمة. هذه ميزة جديدة تضاف إلى POI 3.17:
التعداد العام fileMagic { / ** Ole2 / biff8+ دفق المستخدم للمكتب 97 والوثائق العليا* / OLE2 (headerblockconstants._signature) ، / ** OOXML / ZIP Stream* / OOXML (OOXML_FILE_HEADER) ، / ** xml* / xml 2 * */ biff2 (byte new [] {0x09 ، 0x00 ، // sid = 0x0009 0x04 ، 0x00 ، // size = 0x0004 0x00 ، 0x00 ، // unused 0x70 ، 0x00 // 0x70 = multivers}) // sid = 0x0209 0x06 ، 0x00 ، // size = 0x0006 0x00 ، 0x00 ، // unused 0x70 ، 0x00 // 0x70 = multivers}) ، /** Biff4 Raw Dream - for excel 4* /biff4 (new byte [] {0x09 ، 0x04 ، size = 0x0006 0x00 ، 0x00 ، // unused 0x70 ، 0x00 // 0x70 = multives} ، byte new [] {0x09 ، 0x04 ، // sid = 0x0409 0x06 ، 0x00 ، // size = 0x0006 0x00 ، 0x00 ، // unuseed 0x00 ، mswrite (new byte [] {0x31 ، (byte) 0xbe ، 0x00 ، 0x00} ، byte new [] {0x32 ، (byte) 0xbe ، 0x00 ، 0x00}) ،/** rtf document*/rtf ("{// rtf") التعداد الأخير! / ** غير معروف السحر*/ غير معروف (بايت جديد [0]) ؛ البايت النهائي [] [] السحر ؛ fileMagic (long magic) {this.magic = new byte [1] [8] ؛ LittleAndian.putlong (this.magic [0] ، 0 ، Magic) ؛ } fileMagic (byte [] ... magic) {this.magic = magic ؛ } fileMagic (String Magic) {this (magic.getBytes (localeUtil.charset_1252)) ؛ } قيمة fileMagic static العامة (byte [] magic) {for (fileMagic fm: dorder ()) {int i = 0 ؛ وجدت منطقية = صواب ؛ لـ (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 ؛ استراحة؛ }} if (تم العثور عليه) {return fm ؛ }}} إرجاع غير معروف ؛ } / ** * احصل على سحر الملف الخاص بـ inputStream المقدم (الذي يجب * دعم علامة وإعادة تعيينه). <p> * * * إذا لم يكن متأكدًا إذا لم يدعم InputStream علامة / إعادة تعيين ، * استخدم {link #preparetocheckmagic (inputStream)} يعني ، أن الدفق zip له بايت غير المرغوب فيه * * param inp inportstream يدعم إما mark/ refet */ public static filemagic valueof (inputStream inp) يلقي ioException {if (! inp.marksupported ()) }. إرجاع fileMagic.valueof (البيانات) ؛ } / ** * يتحقق مما إذا كان يمكن إعادة تعيين {link inputStream} (أي يستخدم للتحقق من سحر الرأس) ويلتفها إذا لم يكن * * * param دفق ليتم فحصه للتغليف * regurn tream tream * / public staticstream preparteCheckMagic (inputstream Stream) {if (stream.markported ()) } // استخدمنا لمعالجة البيانات عبر pushinputStream ، ولكن يمكن أن يوفر رمز المستخدم واحدًا صغيرًا جدًا // لذلك نحن نستخدم bufferedInputStream بدلاً من ذلك الآن إعادة BufferedInputStream (دفق) ؛ }}فيما يلي الرمز الرئيسي ، الذي يحدد بشكل أساسي نوع الملف استنادًا إلى أول 8 بايتات من InputStream. لا توجد طريقة للاعتقاد بأن هذا هو الحل الأكثر أناقة. في البداية ، كنت أفكر في الواقع أن البايتات القليلة الأولى للملف المضغوط تبدو محددة من قبل واحد مختلف ، MagicMumber. نظرًا لأن تبعيات FileMagic متوافقة مع الإصدار 3.16 ، فأنا فقط بحاجة إلى إضافة هذه الفئة ، وبالتالي فإن الطريقة الصحيحة لنا لقراءة ملف Word الآن هي:
سلسلة ثابتة خاصة readDoc (سلسلة filepath ، inputStream) {string text = "" ؛ IS = fileMagic.preparetoCheckMagic (IS) ؛ حاول {if (fileMagic.valueof (is) == fileMagic.ole2) {wordExtractor ex = new WordExtractor (IS) ؛ النص = ex.getText () ؛ ex.close () ؛ } آخر إذا (fileMagic.valueof (IS) == fileMagic.OOxml) {xwpfdocument doc = new xwpfdocument (IS) ؛ xwpfwordextractor extractor = new xwpfwordextractor (doc) ؛ النص = extractor.getText () ؛ extractor.close () ؛ }} catch (استثناء e) {logger.error ("for file" + filepath ، e) ؛ } أخيرًا {if (is! = null) {is.close () ؛ }} نص الإرجاع ؛ } Excel
بالنسبة لمقال Excel ، لن أبحث عن مقارنات بين الخطة السابقة والخطة الحالية. سأعطيني أفضل الممارسات الآن:
suppressWarnings ("deprecation") سلسلة ثابتة readexcel (سلسلة FilePath ، inputStream INP) استثناء {Workbook wb ؛ StringBuilder sb = new StringBuilder () ؛ جرب {if (filePath.endswith (". } آخر {wb = dreamingReader.builder () .RowCachesize (1000) // عدد الصفوف التي يجب الحفاظ عليها في الذاكرة (الافتراضيات إلى 10) .buffersize (4096) // حجم المخزن المؤقت للاستخدام عند قراءة inputStream إلى ملف (الافتراضيات إلى 1024) .Open (Inp) ؛ // inputStream أو ملف لملف XLSX (مطلوب)} sb = Readsheet (WB ، SB ، FilePath.endswith (". XLS")) ؛ WB.Close () ؛ } catch (Ole2NotofficexMlfileException e) {logger.error (filepath ، e) ؛ } أخيرًا {if (inp! = null) {inp.close () ؛ }} return sb.toString () ؛ } سلسلة ثابتة خاصة ReadExcelByFile (سلسلة FilePath ، ملف الملف) {Workbook wb ؛ StringBuilder sb = new StringBuilder () ؛ حاول {if (filePath.endswith (". xls")) {wb = workbookfactory.create (file) ؛ } آخر {wb = dreamingReader.builder () .RowCachesize (1000) // عدد الصفوف التي يجب الحفاظ عليها في الذاكرة (الافتراضيات إلى 10) .buffersize (4096) // حجم المخزن المؤقت للاستخدام عند قراءة inputStream إلى ملف (الافتراضيات إلى 1024). // inputStream أو ملف لملف XLSX (مطلوب)} sb = Readsheet (WB ، SB ، FilePath.endswith (". XLS")) ؛ WB.Close () ؛ } catch (استثناء e) {logger.error (filepath ، e) ؛ } return sb.toString () ؛ } readshele static static static static static (workbook wb ، stringbuilder sb ، boolean isxls) يلقي الاستثناء {for (ورقة: wb) {for (row r: sheet) {for (cell cell: r) {if (cell.getCelltype () == cell.cell_type_string) SB.Append ("") ؛ } آخر إذا (cell.getCellType () == cell.cell_type_numeric) {if (isxls) {dataFormatter formatter = new dataFormatter () ؛ sb.append (formatCellValue (Cell)) ؛ } آخر {sb.append (cell.getStringCellValue ()) ؛ } sb.append ("") ؛ }}}} return sb ؛ }في الواقع ، بالنسبة لقراءة Excel ، فإن المشكلة الأكبر التي تواجهها أدواتي هي تدفق الذاكرة. في كثير من الأحيان ، ستؤدي قراءة بعض ملفات Excel الكبيرة بشكل خاص إلى وجود مشكلة في الفائض في الذاكرة. في وقت لاحق ، وجدت أخيرًا أداة ممتازة لإقامة الإثارة ، والتي يمكنها تبسيط ملفات XLSX وتقسيم بعض الملفات الكبيرة بشكل خاص إلى ملفات صغيرة للقراءة.
التحسين الآخر هو أنه في السيناريو الذي يمكن فيه استخدام كائنات الملفات ، أستخدم كائنات الملف لقراءة الملفات بدلاً من استخدام inputStream لقراءتها ، لأن استخدام inputStream يتطلب تحميلها جميعًا في الذاكرة ، لذلك يستغرق هذا الذاكرة للغاية.
أخيرًا ، خدعة صغيرة هي استخدام Cell.GetCellType لتقليل كمية البيانات ، لأنني بحاجة فقط إلى الحصول على بعض محتوى سلسلة النصوص والأرقام.
ما سبق هو بعض من استكشافاتي واكتشاتي عند قراءة الملفات باستخدام POI ، وآمل أن تكون مفيدة لك. يتم تطبيق الأمثلة أعلاه أيضًا في إحدى أدواتي في كل مكان (يمكن أن تساعدك هذه الأداة بشكل أساسي في البحث في النص الكامل للمحتوى على جهاز الكمبيوتر الخاص بك). إذا كنت مهتمًا ، فيمكنك إلقاء نظرة. مرحبًا بك في Star أو PR.
لخص
ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.