Java中实现Excel支持导入图片

 1、 准备Excel

  • 如下图所示,需要支持在Excel中导入图片,并上传至指定文件中。
    • Excel中的图片不能嵌入单元格,否则无法实现该功能。

2、 实现思路

  1. 使用在Apache POI中,XSSFDrawing和HSSFPatriarch类,进行获取Excel对应sheet页中的所有图片。
  2. 将图片流式存储至服务器的临时文件夹中。
  3. 业务处理结束后,将临时文件夹中的数据转移至目标文件夹。
  4. 删除临时文件夹。

3、使用工具

导入Apache POI的包解析Excel

    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>4.1.2</version>
      </dependency>
      <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>4.1.2</version>
      </dependency>
      <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>4.1.2</version>
      </dependency>
      <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-schemas</artifactId>
        <version>4.1.2</version>
      </dependency>

4、读取Excel文件

Java 有多种读取文件的方法。可参考使用

5、创建临时文件夹

        在服务器中创建临时文件夹,用于存储当前Excel中的图片,操作结束后无论是否成功,均要删除该文件夹。

  /**
   *
   * 获取历史文件夹目录
   *
   * @param workerId
   *          导入唯一标识,not null
   * @return 文件路径
   */
  public static String genTempSubFolder(String workerId) {
    Date d = new Date();
    String currentFileFolder = new SimpleDateFormat("yyyyMMdd").format(d) + "-" + workerId;
    String path = "tmp".replace("\\", "/");
    String fileId = currentFileFolder.replace("\\", "/");
    if (!path.endsWith("/") && !fileId.startsWith("/")) {
      path += "/";
    }
    return path + fileId;
  }

6、读取Excel中某个Sheet中的图片

  1. 首先判断导入唯一标识workerId是否为空,如果为空则直接返回空的HashMap。
    •   WorkerId的作用是标记每一次的唯一标识,用于生成临时文件夹     
  2. 创建一个HashMap用于保存行号和文件名集合的映射关系。
  3. 根据workerId生成一个临时子文件夹。
  4. 遍历表格中的每一行,通过判断工作簿类型(XSSFWorkbook或HSSFWorkbook),分别处理图片的读取和保存。
  5. 对于XSSFWorkbook类型的工作簿,通过XSSFDrawing获取所有形状对象,判断形状是否为XSSFPicture类型,如果是则调用getPicture方法保存图片,并将文件名添加到结果集中。
  6. 对于HSSFWorkbook类型的工作簿,通过HSSFPatriarch获取所有形状对象,判断形状是否为HSSFPicture类型,如果是则调用getPicture方法保存图片,并将文件名添加到结果集中。
    • 判断是否是图片类型的数据
    • 写入图片到本地
    • 返回结果
  7. 返回结果集。
  /**
   *
   * 获取单元格图片内容
   *
   * @param sheet
   *          标签页,not null
   * @param workerId
   *          导入唯一标识,not null
   * @param nullable
   *          是否允许为空,not null
   *
   * @return 行号 - 文件名集合
   */
  public static Map<Integer, List<String>> readImagesUrl(Sheet sheet, String workerId, boolean nullable)
      throws Exception {
    if (StringUtil.isNullOrBlank(workerId)) {
      return new HashMap<>();
    }

    Map<Integer, List<String>> result = new HashMap<>();
    String tmpDir = genTempSubFolder(workerId);

    for (int rowIdx = 1; rowIdx <= sheet.getLastRowNum(); rowIdx++) {
      Workbook workbook = sheet.getWorkbook();

      int index = 0;
      if (workbook instanceof XSSFWorkbook) {
        XSSFDrawing drawing  = (XSSFDrawing) sheet.createDrawingPatriarch();
        for (XSSFShape shape : drawing.getShapes()) {
          if (shape instanceof XSSFPicture) {
            XSSFPicture picture = (XSSFPicture) shape;
            index++;
            getPicture(picture, tmpDir, workerId, index, result);
          }
        }
      } else if (workbook instanceof HSSFWorkbook) {
        HSSFPatriarch drawing  = (HSSFPatriarch) sheet.createDrawingPatriarch();
        List<HSSFShape> shapes = drawing.getChildren();
        for (HSSFShape shape : shapes) {
          if (shape instanceof HSSFPicture) {
            HSSFPicture picture = (HSSFPicture) shape;
            index++;
            getPicture(picture, tmpDir, workerId, index, result);
          }
        }
      }
    }


    return result;
  }


private static void getPicture(Picture picture, String tmpDir, String workerId, Integer index, Map<Integer, List<String>> result) throws Exception {
    PictureData pictureData = picture.getPictureData();
    ClientAnchor clientAnchor = picture.getClientAnchor();

    byte[] data = null;

    // 起始行
    int startRow = clientAnchor.getRow1();
    // 截止行
    int endRow = clientAnchor.getRow2();
    // 起始列
    int startCol = clientAnchor.getCol1();
    // 截止列
    int endCol = clientAnchor.getCol2();
    if (startRow != endRow) {
      return;
    }
    // 获取图片
    data = pictureData.getData();
    // 获取图片格式
    String ext = pictureData.suggestFileExtension();
    if (!isImageFile(ext)) {
      throw new Exception("非图片类型,不支持excel导入。");
    }

    String fileId = MessageFormat.format("import_{0}_{1}_{2}.{3}", workerId, startRow, index, ext);

    // 写入临时文件夹
    writeFile(tmpDir, fileId, new ByteArrayInputStream(data));

    List<String> rowImages = result.get(startRow);
    if (rowImages == null) {
      rowImages = new ArrayList<>();
      result.put(startRow, rowImages);
    }
    rowImages.add(fileId);
  }


  // 判断是否是图片类型的数据
  private static String[] videoTypes = new String[] {
      "avi", "wmv", "mpg", "mpeg", "mov", "rm", "ram", "swf", "flv", "mp4" };


  public static boolean isImageFile(String fileUrl) {
    Assert.assertArgumentNotNull(fileUrl, "fileUrl");
    String type = fileUrl.substring(fileUrl.lastIndexOf(".") + 1);
    for (String str : imageTypes) {
      if (str.equalsIgnoreCase(type)) {
        return true;
      }
    }
    return false;
  }


  /**
   * 将文件写入到本地。
   * 
   * @param uploadRootDir
   *          上传根目录,not null。
   * @param fileId
   *          上传子路径,含文件名,not null。
   * @param sourceStream
   *          文件输入流,not null。
   * @throws IOException
   */
  public static void writeFile(String uploadRootDir, String fileId, InputStream sourceStream)
      throws IOException {
    String serverFile = FileUtil.concat(uploadRootDir, fileId);
    String filePath = FileUtil.getFilePath(serverFile);
    File dir = new File(filePath);
    if (!dir.exists()) {
      dir.mkdirs();
    }

    try (BufferedInputStream bis = new BufferedInputStream(sourceStream);
        FileOutputStream fos = new FileOutputStream(serverFile);
        BufferedOutputStream bos = new BufferedOutputStream(fos)) {
      byte[] buffer = new byte[4096];
      int readBytes;
      while ((readBytes = bis.read(buffer)) != -1) {
        bos.write(buffer, 0, readBytes);
      }
      bos.flush();
    }
  }

7、获取临时文件中的图片,并移动至其他文件夹

  1.  使用new FileInputStream(path)读取文件;
  2.  使用上述代码中的writeFile()方法写文件;

8、 注意点

在Java中,调用pictureData.getData()方法时,会返回一个byte[]数组,该数组包含了图片的二进制数据。这个数组会占用内存,直到它被垃圾回收器回收。

当你使用多个XSSFPictureData对象调用getData()方法时,每个方法调用都会返回一个新的byte[]数组,并且每个数组都会占用内存,直到它们被垃圾回收器回收。

如果你不再需要这些图片数据,你可以设置它们为null,这样它们将会被垃圾回收器回收,并释放占用的内存。

方案:

  • 及时释放资源:一旦图片数据不再需要,应确保相关的 byte[] 数组不再被引用,以便垃圾回收器能够回收内存。你可以在处理完图片数据后,将 data 数组设置为 null。
  • 避免不必要的数据加载:如果可能,避免重复加载同一张图片的数据。例如,如果需要多次使用同一张图片的数据,可以将其存储在一个缓存中,避免多次调用 getData()。
  • 使用流式处理:如果图片数据非常大,可以考虑使用流式处理技术,如读取图片数据时使用输入流,这样可以避免一次性将整个图片加载到内存中。
  • 图片预处理:在加载图片到Excel之前,可以先进行预处理,比如压缩图片或调整其尺寸,以减少内存占用。
  • 内存监控:定期监控应用程序的内存使用情况,特别是在处理包含大量图片的Excel文件时,确保内存使用在可接受范围内。
  • 提高效率的话可以使用多线程进行图片的处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值