Java比较两个Excel是否内容一致

领导每天让比较两个Excel中的内容,为了节省工作效率多摸鱼,就写了个java接口,通过上传两个文件 进行代码比较得到详细的比较结果(这个需要自己根据日志二开) 目前只实现了比较功能

话不多说直接上代码,具体看注释

java 复制代码
package com.yxy.aob.util;

import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description: excel对比
 * @Author: Hyz
 * @Date: 2024/10/14 11:33
 * @Version:1.0
 */
@Slf4j
public class CompareTwoExcels {

    private final static String MSG_PREFIX = "【source】与【target】中";

    /**
     * 文件1流
     */
    private InputStream sourceExcelFile;

    /**
     * 文件2流
     */
    private InputStream targetExcelFile;

    /**
     * 是否模糊   true为精准匹配  false为模糊匹配
     */
    private boolean isPerfectMatch;

    /**
     * 差集
     */
    private List<String> differential;

    /**
     * 错误行
     */
    private List<String> errorMsg;

    /**
     * 匹配行
     */
    private List<String> successMsg;

    /**
     * 构造方法
     *
     * @param sourceExcelFile 文件1
     * @param targetExcelFile 文件2
     * @param isPerfectMatch  表示是否需要完全匹配
     */
    public CompareTwoExcels(InputStream sourceExcelFile, InputStream targetExcelFile, boolean isPerfectMatch) {
        this.sourceExcelFile = sourceExcelFile;
        this.targetExcelFile = targetExcelFile;
        this.isPerfectMatch = isPerfectMatch;
        this.differential = new ArrayList<>();
        this.errorMsg = new ArrayList<>();
        this.successMsg = new ArrayList<>();
    }

    public static void main(String[] args) throws IOException {

        try (InputStream sourceInputStream = Files.newInputStream(Paths.get("C:\\Users\\Administrator\\Desktop\\1.xlsx"));
             InputStream targetInputStream = Files.newInputStream(Paths.get("C:\\Users\\Administrator\\Desktop\\1 - 副本.xlsx"))) {

            // 初始化识别器
            CompareTwoExcels compareTwoExcels = new CompareTwoExcels(sourceInputStream, targetInputStream, false);
            // 执行识别方法 需要指定对应的sheet页 下标0开始
            boolean result = compareTwoExcels.comparedExcels(0, 0);

            log.info("result为:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取初始化文件的sheet数量
     *
     * @param type 1 表示sourceExcelFile 2 表示targetExcelFile
     */
    private int getSheetNum(Integer type) {
        log.info("sourceExcelSheetNum为:" + this.sourceExcelFile);
        try (Workbook workbook = WorkbookFactory.create(type == 1 ? this.sourceExcelFile : this.targetExcelFile)) {
            return workbook.getNumberOfSheets();
        } catch (IOException e) {
            return 0;
        }
    }

    /**
     * 比较两个excel
     *
     * @param sourceExcelSheetNum sheet  从0开始
     * @param targetExcelSheetNum sheet  从0开始
     */
    private boolean comparedExcels(int sourceExcelSheetNum, int targetExcelSheetNum) {

        log.info("------------------------读取Excel1数据------------------------");
        List<List<String>> sourceList = this.readExcel(sourceExcelSheetNum, this.sourceExcelFile, "source");
        log.info("------------------------读取Excel2数据------------------------");
        List<List<String>> targetList = this.readExcel(targetExcelSheetNum, this.targetExcelFile, "target");

        if (CollUtil.isEmpty(sourceList) || CollUtil.isEmpty(targetList)) {
            log.error("sourceList为空!!!");
            return false;
        }

        // 行数不一致直接返回false
        if (sourceList.size() != targetList.size()) {
            log.error("sourceList与targetList长度不一致");
            return false;
        }

        log.info("------------------------开始匹配:【{}】------------------------", isPerfectMatch ? "精准匹配" : "模糊匹配");

        boolean result = true;
        try {
            // 模糊匹配和精准匹配区分
            if (!isPerfectMatch) {
                // 模糊匹配 即两份excel的每一行不定
                for (int i = 1; i <= sourceList.size(); i++) {
                    log.info("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 第【{}】行 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓", i);
                    // 表格1的第i行
                    List<String> list1 = sourceList.get(i - 1);
                    List<String> list2 = targetList.get(i - 1);

                    // 判断1、两个表格的同一行是否相同 (即表格1的第1行和表格2的第1行hashCode是否一致)
                    if (list1.hashCode() != list2.hashCode()) {
                        log.info("------{}第【{}】行匹配结果------UNKNOWN", MSG_PREFIX, i);

                        // 判断2、 需要比较表格1的这一行在表格二中是否存在
                        if (targetList.contains(list1)) {
                            int i1 = targetList.indexOf(list1);
                            log.info("---------详情:【source】中第【{}】行与【target】中第【{}】行匹配(跨行)", i, i1 + 1);
                            this.successMsg.add("【source】中第【" + i + "】行与【target】中第【" + (i1 + 1) + "】行匹配(跨行)");

//                            List<String> cells = targetList.get(targetList.indexOf(list1));
//                            for (int j = 1; j <= list1.size(); j++) {
//                                if (cells.contains(list1.get(j - 1))) {
//                                    log.info("---------详情:第【{}】行,第【{}】列 匹配", i, j);
//                                    this.successMsg.add(MSG_PREFIX + "第【" + i + "】行,第【" + j + "】列 匹配");
//                                } else {
//                                    log.info("---------详情:第【{}】行,第【{}】列 不匹配", i, j);
//                                    this.errorMsg.add(MSG_PREFIX + "第【" + i + "】行,第【" + j + "】列 不匹配");
//                                    result = false;
//                                }
//                            }
                        } else {
                            log.info("---------详情:【source】中第【{}】行在【target】中不存在", i);
                            this.errorMsg.add(MSG_PREFIX + "第【" + i + "】行均不存在");
                            result = false;
                        }
                    } else {
                        log.info("------{}第【{}】行匹配结果------SUCCESS", MSG_PREFIX, i);
                        this.successMsg.add(MSG_PREFIX + "第【" + i + "】行匹配");
                    }
                }
            } else {
                // 精准匹配 即两份excel的每一行每一列格式相同
                for (int i = 1; i <= sourceList.size(); i++) {
                    // 表格1的第i行
                    List<String> list1 = sourceList.get(i - 1);
                    // 表格2的第i行
                    List<String> list2 = targetList.get(i - 1);

                    // 精准匹配 即两份excel的每一行每一列都完全匹配
                    // list2.equals(list1)或list1.hashCode() == list2.hashCode()
                    if (list1.hashCode() == list2.hashCode()) {
                        log.info("------第【{}】行, 匹配------", i);
                        this.successMsg.add(MSG_PREFIX + "第【" + i + "】行, 匹配");
                    } else {
                        log.info("------第【{}】行, 不匹配------", i);
                        this.errorMsg.add(MSG_PREFIX + "第【" + i + "】行, 不匹配");
                        result = false;
                    }
                }
            }
        } catch (IndexOutOfBoundsException ex) {
            log.error("两个表格可能存在行数不一致,导致sourceList与targetList长度不一致!");
            result = false;
        } finally {
            log.info("------------------------比较结束------------------------");
        }
        return result;
    }

    /**
     * 读取excel 封装成集合
     * 该程序需要传入一份excel 和excel的列数 行数由程序自动检测
     * 注意:该方法统计的行数是默认第一行为title 不纳入统计的
     *
     * @return excel的集合
     */
    private List<List<String>> readExcel(int sheetNum, InputStream inputStream, String type) {
        List<List<String>> list = new ArrayList<>();
        // 建需要读取的excel文件写入stream

        try (Workbook workbook = WorkbookFactory.create(inputStream)) {
            // 指向sheet下标为0的sheet 即第一个sheet 也可以按在sheet的名称来寻找
            Sheet sheet = workbook.getSheetAt(sheetNum);
            // 获取sheet1中的总行数
            int rowTotalCount = sheet.getLastRowNum();
            //获取总列数
            int columnCount = 0;
            for (int i = 0; i <= rowTotalCount; i++) {
                // 获取第i列的row对象
                Row row = sheet.getRow(i);
                ArrayList<String> listRow = new ArrayList<>();
                int physicalNumberOfCells = row.getPhysicalNumberOfCells();
                if (physicalNumberOfCells >= columnCount) {
                    columnCount = physicalNumberOfCells;
                }
                for (int j = 0; j < physicalNumberOfCells; j++) {
                    //下列步骤为判断cell读取到的数据是否为null 如果不做处理 程序会报错
                    String cell = null;
                    //如果未null则加上""组装成非null的字符串
                    if (row.getCell(j) == null) {
                        cell = row.getCell(j) + "";
                        listRow.add(cell);
                        //如果读取到额cell不为null 则直接加入	listRow集合
                    } else {
                        cell = row.getCell(j).toString();
                        listRow.add(cell);
                    }
                }
                list.add(listRow);
            }
            log.info("Excel【{}】中【{}】页签共计:{}行,{}列(列为最大列)", type, sheet.getSheetName(), rowTotalCount + 1, columnCount);
        } catch (IOException e) {
            log.error("读取excel sheet{}失败: {}", sheetNum, e.getMessage());
        }
        return list;
    }


    /**
     * ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓私有属性方法↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
     */

    public List<String> getDifferential() {
        return differential;
    }

    private void setDifferential(List<String> differential) {
        this.differential = differential;
    }

    public List<String> getErrorMsg() {
        return errorMsg;
    }

    private void setErrorMsg(List<String> errorMsg) {
        this.errorMsg = errorMsg;
    }

    public List<String> getSuccessMsg() {
        return successMsg;
    }

    private void setSuccessMsg(List<String> successMsg) {
        this.successMsg = successMsg;
    }
}
相关推荐
Scott9999HH2 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔2 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社3 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海3 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖4 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大7 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json