首页 > 编程语言 >Andorid+Java使用Apache POI库实现doc、docx、xls、xlsx的读取和写入

Andorid+Java使用Apache POI库实现doc、docx、xls、xlsx的读取和写入

时间:2024-09-21 16:50:12浏览次数:13  
标签:xlsx docx Java doc org poi apache import document

1、前言

  最近要用Android Studio和Java实现多种文件的导入和读取,包括常见的文本文件txt、doc、docx、xls和xlsx。其中txt用输入输出流操作即可。经过搜索查找,确定了使用Apache POI库进行其余文件类型的读写。在此记录从开始在apache官网上下载导入包后尝试读取doc便报错,到打release包后安装闪退期间遇到的问题和解决办法。

  有错误欢迎大家指出,有更好的办法也欢迎大家评论和私信,共同进步。

2、Apache POI简介

  “关于POI可以访问Apache POI的官网获取详细的信息。”

  (本章节参照参考链接1写,参考链接1在4.1中)

  由于项目中只是用到了doc、docx、xls和xlsx的组件,下面也只是介绍这四个组件的使用

  通过官网 ->Getting Involved,可以看到构建项目的方式(也可以自行搜索,看更详细的教程),如下图

  通过官网 ->Component APIs,可以看到 doc、docx、xls和xlsx文件分别对应着组件HWPFXWPF、HSSF和XSSF,而HWPF、XWPF、HSSF和XSSF则对应着poi-scratchpad、poi-ooxml、poi和poi-ooxml

文件类型组件名MavenId
docHWPFpoi-scratchpad
docxXWPFpoi-ooxml
xlsHSSFpoi
xlsxXSSFpoi-ooxml

   如下图

3、正确打开方式

  回头看整理下,我认为不走那么多弯路的开发过程。

  (参考链接均在4中)

3.1 下载并导入Apache POI包

  1、下载压缩包。下载地址:https://github.com/ljliu1985/AndroidPoiForReadExcelXlsx

   2、解压缩之后将AndroidPoiForReadExcelXlsx-master\app\libs里面的包全部拷贝到项目中app\libs中

  3、导入包。不太熟悉导入可以看看这个:Android Studio导入jar包教程

3.2 读取和写入

  以下读取和写入均先用代码测试是否能正常运行及正确读取或写入。如果可以,再根据实际需求自定义补充修改代码。

3.2.1 docx的读取和写入

3.2.1.1 读取
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;

import java.io.FileInputStream;
import java.io.IOException;

public class ReadDocxFile {
    public void readDocx(String[] args) {
        String filePath = "path/to/your/document.docx"; // 替换为你的文件路径

        try (FileInputStream fis = new FileInputStream(filePath);
             XWPFDocument document = new XWPFDocument(fis)) {

            // 遍历文档中的段落
            for (XWPFParagraph paragraph : document.getParagraphs()) {
                Log.d("test",paragraph.getText());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3.2.1.2 写入
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;

import java.io.FileOutputStream;
import java.io.IOException;

public class WriteDocxFile {
    public void writeDocx(String[] args) {
        String filePath = "path/to/your/newDocument.docx"; // 替换为你想要创建的文件路径

        try (XWPFDocument document = new XWPFDocument()) {
            // 创建段落
            XWPFParagraph paragraph = document.createParagraph();
            XWPFRun run = paragraph.createRun();
            run.setText("Hello, this is the first paragraph.");

            // 继续添加更多段落
            XWPFParagraph secondParagraph = document.createParagraph();
            XWPFRun run2 = secondParagraph.createRun();
            run2.setText("This is the second paragraph.");

            // 将文档写入文件
            try (FileOutputStream fos = new FileOutputStream(filePath)) {
                document.write(fos);
            }

            Log.d("test", "Document created successfully!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.2.2 doc的读取和写入

3.2.2.1 读取
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.DocumentPart;

import java.io.FileInputStream;
import java.io.IOException;

public class ReadDocFile {
    public void readDoc(String[] args) {
        String filePath = "path/to/your/document.doc"; // 替换为你的文件路径

        try (FileInputStream fis = new FileInputStream(filePath);
             HWPFDocument document = new HWPFDocument(fis)) {

            Range range = document.getRange();

            // 遍历文档中的段落
            for (int i = 0; i < range.numParagraphs(); i++) {
                Paragraph paragraph = range.getParagraph(i);
                Log.d("test", paragraph.text());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3.2.2.2 写入

  doc的写入遇到了问题,"在使用POI写word doc文件的时候我们必须要先有一个doc文件才行,因为我们在写doc文件的时候是通过HWPFDocument来写的,而HWPFDocument是要依附于一个doc文件的。所以通常的做法是我们先在硬盘上准备好一个内容空白的doc文件,然后建立一个基于该空白文件的HWPFDocument。之后我们就可以往HWPFDocument里面新增内容了,然后再把它写入到另外一个doc文件中,这样就相当于我们使用POI生成了word doc文件。"(参考链接1)

  我没有找到好办法,用了一种很笨的办法。如果大家有好办法可以在评论区分享或私信,共同进步。

  在此使用了XWPFDocument创建的一个空白doc文件,“这段代码会创建一个名为blank.doc的空白Word文档。注意,这里使用的是.doc而非.docx扩展名,但是实际上创建的是一个OOXML(.docx)格式的文档。”容易造成混淆。

  “Apache POI库支持两种不同的格式来创建Word文档:.doc 和 .docx。尽管这两种格式在内部实现和功能上有所不同,但它们都可以用来创建和操作Word文件。”

  由此又导致了读取时扩展名是doc但实际是docx文档时报错,以及doc和docx互相更改后缀名导致的读取报错。

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
 
import java.io.FileOutputStream;
 
public class BlankWordFile {
    public void blankWord(String[] args) {
        try (XWPFDocument document = new XWPFDocument()) {
            // 创建一个空白的段落
            XWPFParagraph paragraph = document.createParagraph();
            // 将文档保存到硬盘
            try (FileOutputStream out = new FileOutputStream("blank.doc")) {
                document.write(out);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
读取时扩展名是doc但实际是docx文档时报错

  读取扩展名为doc的文件时先用HWPFDocument读,如果捕获到异常就用XWPFDocument读,这种情况下便不会再报错。同理,读取docx扩展名时捕获到异常先用HWPFDocument读。xls和xlsx同理。下面代码结合了3.2.1docx的读取和3.2.2doc的读取。

import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.DocumentPart;

import java.io.FileInputStream;
import java.io.IOException;

public class ReadDocFile {
    public void readDoc(String[] args) {
        String filePath = "path/to/your/document.doc"; // 替换为你的文件路径

        try (FileInputStream fis = new FileInputStream(filePath);
             HWPFDocument document = new HWPFDocument(fis)) {

            Range range = document.getRange();

            // 遍历文档中的段落
            for (int i = 0; i < range.numParagraphs(); i++) {
                Paragraph paragraph = range.getParagraph(i);
                Log.d("test", paragraph.text());
            }
        } catch (IOException e) {
            //e.printStackTrace();
            try (FileInputStream fis = new FileInputStream(filePath);
             XWPFDocument document = new XWPFDocument(fis)) {

            // 遍历文档中的段落
            for (XWPFParagraph paragraph : document.getParagraphs()) {
                Log.d("test",paragraph.getText());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        }
    }
}

  写入多段可以用下面这段代码测试

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class DocWriter {

    public void writeDocFile(File file) {
        try (XWPFDocument document = new XWPFDocument()) {
            // Add multiple paragraphs
            addParagraph(document, "Hello, World!");
            addParagraph(document, "This is the second line.");
            addParagraph(document, "And this is the third line.");

            // Write the document to the output stream
            try (OutputStream outputStream = new FileOutputStream(file)) {
                document.write(outputStream);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void addParagraph(XWPFDocument document, String text) {
        XWPFParagraph paragraph = document.createParagraph();
        XWPFRun run = paragraph.createRun();
        run.setText(text);
    }
}

3.2.3 xls和xlsx的读取和写入

3.2.3.1 读取
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.io.FileInputStream;
import java.io.IOException;

public class ReadExcelToString {
    public void readExcel(String[] args) {
        String filePath = "path/to/your/excelFile.xls"; // 替换为你的文件路径
        StringBuilder stringBuilder = new StringBuilder();

        try (FileInputStream fis = new FileInputStream(filePath)) {
            Workbook workbook;

            // 根据文件扩展名选择工作簿类型
            if (filePath.endsWith(".xlsx")) {
                workbook = new XSSFWorkbook(fis);
            } else if (filePath.endsWith(".xls")) {
                workbook = new HSSFWorkbook(fis);
            } else {
                throw new IllegalArgumentException("Invalid file format");
            }

            // 读取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);

            // 遍历行
            for (Row row : sheet) {
                // 遍历单元格
                for (Cell cell : row) {
                    switch (cell.getCellType()) {
                        case STRING:
                            stringBuilder.append(cell.getStringCellValue()).append("\t");
                            break;
                        case NUMERIC:
                            stringBuilder.append(cell.getNumericCellValue()).append("\t");
                            break;
                        case BOOLEAN:
                            stringBuilder.append(cell.getBooleanCellValue()).append("\t");
                            break;
                        default:
                            stringBuilder.append("UNKNOWN\t");
                    }
                }
                stringBuilder.append("\n"); // 每行结束后换行
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        // 输出结果
        String result = stringBuilder.toString();
        Log.d("test", result);
    }
}
3.2.4.2 写入
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.io.FileOutputStream;
import java.io.IOException;

public class WriteExcelBasedOnExtension {
    public void WriteExcelBase(String[] args) {
        String filePathXlsx = "output.xlsx"; // 输出的 XLSX 文件路径
        String filePathXls = "output.xls"; // 输出的 XLS 文件路径

        // 写入 XLSX 文件
        writeExcel(filePathXlsx);
        
        // 写入 XLS 文件
        writeExcel(filePathXls);
    }

    private static void writeExcel(String filePath) {
        Workbook workbook = null;

        // 根据文件后缀选择工作簿类型
        if (filePath.endsWith(".xlsx")) {
            workbook = new XSSFWorkbook(); // 创建 XLSX 工作簿
        } else if (filePath.endsWith(".xls")) {
            workbook = new HSSFWorkbook(); // 创建 XLS 工作簿
        } else {
            throw new IllegalArgumentException("Invalid file format: " + filePath);
        }

        try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
            Sheet sheet = workbook.createSheet("Sheet1"); // 创建工作表

            // 写入数据
            Row row = sheet.createRow(0);
            Cell cell = row.createCell(0);
            cell.setCellValue("Hello, " + (filePath.endsWith(".xlsx") ? "XLSX" : "XLS") + "!");

            workbook.write(fileOut); // 写入文件
            Log.d("test", filePath + " 文件写入成功!");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (workbook != null) {
                try {
                    workbook.close(); // 关闭工作簿
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3.3 打包

  以上步骤通过后,开始打包生成安装包测试。如果是用release,会报错java.lang.ExceptionInInitializerError。需要“和其他引入的第三方jar一样,要忽略掉这些jar代码混淆!”。

  “首先,需要在app/proguard-rules.pro文件中,添加混淆文件;

  然后,在build.gradle中去开启混淆:”(参考链接4,下面代码是参考链接5,其中的txt和pro文件需要修改为自己本地项目的对应文件)

  buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        release {
            zipAlignEnabled true
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.myConfig
        }
    }

  最后,点击“运行,看看有问题否,能不能成功。

  结果按此方法,在运行下发现问题,去除错误的混淆,解决掉问题,最终混淆release打包成功。”(参考链接4)

配置完文件后点击运行,如果运行后有问题,用参考链接6中忽略所有引用包,再次点击运行测试。下图是我的忽略混淆配置

4、问题和解决办法

4.1 下载和导包

  之前从官网上Apache POI™ - Download Release Artifacts(参考链接1)下载包,发现高版本会报错,就用了教程中的3.16版本读取doc,的确可以。之后开发doc、docx和xls都能正确读取,但在读取xlsx时报错未能解决。

  又开始搜索,终于找到了能用的包:https://github.com/ljliu1985/AndroidPoiForReadExcelXlsx。(参考链接2)

  即3.17版本的包。“使用Poi读取高版本Excel xlsx文件, 这里主要是rt.jar包解决了以下两个错误:”java.lang.NoClassDefFoundError和javax.xml.stream.FactoryConfigurationError。

  大家可以自行选择,如果想一步到位可以点击github上的包下载,亲测可用

  Window系统下载Apache POI包步骤:

  1、点击Archives of all prior release

  2、点击Binary Artifacts

  3、点击按照链接下载包,“linux系统选择.tar.gz   windows系统选择.zip”(参考链接1)

参考链接1:Android使用ApachePOI组件读写Worddoc和docx文件 - 简书

参考链接2:Android 中读写Excel数据 - 简书

4.2 文件读取和写入

  刚开始读取doc的时候总是报错,最后发现是因为直接更改后缀名导致的。原因如下:

  ‘读写前注意:Apache POI 提供的HWPFDocument类只能读写规范的.doc文件,也就是说假如你使用修改 后缀名 的方式生成doc文件或者直接以命名的方式创建,将会出现错误“Your file appears not to be a valid OLE2 document”’(参考链接1)

Invalid header signature; read 0x7267617266202E31, expected 0xE11AB1A1E011CFD0 - Your file appears not to be a valid OLE2 document 

  最后导出,也就是doc写入的时候遇到了问题,创建doc文件时HWPFDocument document = new HWPFDocument();标红。继续搜索,最后只找到了用XWPFDocument去生成扩展名为doc的docx文档。会产生混淆。所以之后读取时,扩展名doc和docx捕获到异常先分别用XWPFDocument和DWPFDocument读取,这种情况下不会报错。以防万一,xls和xlsx也做了类似的处理。如果大家有更好的办法,欢迎在评论区留言或私信,共同进步。

4.3 打包

  用release打包后读取或写入报错,搜到了一个精准匹配的答案(参考链接3)。小白才知道正确的方向,根据步骤忽略掉doc的混淆后,doc能正常读取了。开始比葫芦画瓢忽略其他文件类型的混淆,但没成功,而且每次设置完忽略混淆都要重新打包安装测试,很麻烦。最后找到了

  “首先,需要在app/proguard-rules.pro文件中,添加混淆文件;

  然后,在build.gradle中去开启混淆:”(参考链接4)

  (下方代码是参考链接5)

  buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        release {
            zipAlignEnabled true
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.myConfig
        }
    }

  最后点击“运行,看看有问题否,能不能成功。结果按此方法,在运行下发现问题,去除错误的混淆,解决掉问题,最终混淆release打包成功。”(参考链接4)

配置完文件后点击运行,发现的确无法运行成功,开始找忽略混淆的方法,刚开始把引入的包中的根目录下的第一级包都忽略混淆还是报错,最后找到了直接把引用包混淆的教程(参考链接6),试了下,终于运行成功了。下图是我的忽略混淆配置

参考链接3:[Android] 解决Release版本HWPFDocument写doc文件失败java.lang.ExceptionInInitializerError_hwpfdocument no static method withinitial-CSDN博客

参考链接4:在Android Studio中的混淆debug与release_release 和 debug 混淆-CSDN博客

参考链接5:

android 如何混淆以及排查混淆后不明Bug_-keepattributes sourcefile,linenumbertable-CSDN博客

参考链接6: Android 混淆后错误整理_android混淆后org.apache.poi.poixmlexception: org.apac-CSDN博客

标签:xlsx,docx,Java,doc,org,poi,apache,import,document
From: https://blog.csdn.net/qq_54350776/article/details/142384325

相关文章

  • 了解 Javascript 中的 POST 请求
    functionnewPlayer(newForm){fetch("http://localhost:3000/Players",{method:"POST",headers:{'Content-Type':'application/json'},body:JSON.stringify(newForm)}).then(resp=&g......
  • 了解 JavaScript 中的高阶组件和高阶函数
    高阶函数高阶函数是一个函数,它要么接受另一个函数作为参数,要么返回一个函数作为结果。这个概念是函数式编程的基础,并允许强大的抽象。示例:functiongreet(name){return`hello,${name}!`;}functionsayhello(fn,name){returnfn(name);}console.log(sayhello(greet,'......
  • 了解 JavaScript 生成器:强大的代码流控制工具
    生成器是javascript中最强大的功能之一,它允许我们编写可以根据需要暂停和恢复的代码。与一次执行所有代码的常规函数??不同,生成器使用延迟执行,增量返回值,从而更容易处理数据序列、迭代或长时间运行的进程。发电机如何工作?在javascript中,生成器是使用function*关键字定义的......
  • 书评:Eloquent JavaScript – Web 开发人员的基本指南
    作为最广泛使用的编程语言之一,JavaScript为网络提供了动力。然而,由于其快速发展,跟上JavaScript趋势可能具有挑战性。许多关于这个主题的书籍很快就会过时,但有一本书经受住了时间的考验:EloquentJavaScript。这本书已成为开发人员的最爱,并且正在稳步发展为那些希望加深对语言理......
  • Java高级05telnet,Socket
    telnet既测试了端口号也测试了IP是否通畅//80是开放的端口号telnet111.206.208.13580//退出ctrl+]quit//查看本机使用的端口号telnet-anoSocket//客户端publicstaticvoidmain(String[]args){//创建一个Socket对象,指定服务器的IP地址和端口号......
  • JAVA网络编程【基于TCP和UDP协议】超详细!!!
    ip地址:唯一标识主机的地址端口号:用于标识计算机上某个特定的网络程序InetAddress类方法说明InetAddressInetAddress.getLocalHost()静态方法,获取本机InetAddress对象(主机名+ip地址)InetAddressInetAddress.getByName("主机名")根据主机名或者域名获取ip地址对象(主机名+ip地址......
  • Java高级06,线程
    多线程进程:1.进程是系统运行程序的基本单位。2.每一个进程都有自己独立的一块内存空间、一组系统资源。3.每一个进程的内部数据和状态都是完全独立的。线程:线程是进程中执行运算的最小单位,可完成一个独立的顺序控制流程。多线程:一个进程中同时运行多个线程来完成不同......
  • TestNG 与 JUnit:哪种 Java 测试框架适合您?
    测试框架是确保软件质量的重要工具,在Java生态系统中,TestNG和JUnit是最流行的两个选项。虽然这两个框架都有一个共同的目标——让测试变得更容易——但它们提供了不同的特性和功能来满足不同的测试需求。在这篇博文中,我们将深入探讨TestNG与JUnit之间的详细比较,帮助您确定......
  • DOM【JavaScript】
    在JavaScript中,DOM(DocumentObjectModel:文档对象模型)是web页面的编程接口,用于表示和操作HTML和XML文档。它将文档结构化为一个树形结构,允许开发者通过JavaScript访问和修改网页的内容、结构和样式。以下是一些关于DOM的关键概念:1.结构DOM树结构是以节点为单位组......
  • 2025基于springboot的网上村委会业务办理系统-JAVA.VUE【源码、论文、开题、实训报告
       博主介绍:......