调用ImageIO.read后PNG文件损坏问题求助
问题描述
调用ImageIO.read(inputStream)后出现PNG文件损坏问题,相关代码及执行流程如下:
服务执行流程:
InputStream inputStream = multipartFile.getInputStream(); throwExceptionIfImageDimensionsNotValid(inputStream); storeIntoFileSystem(inputStream);
校验方法实现:
private void throwExceptionIfImageDimensionsNotValid(InputStream inputStream) throws IOException, StoreFileException { BufferedImage im = ImageIO.read(inputStream); if(isImageWidthHeightRatioOk(im.getHeight(), im.getWidth())) { return; } else { throw new StoreFileException("图片高宽比需在0.55至0.625之间"); } }
文件存储实现:
void storeIntoFileSystem(InputStream inputStreamIn) throws IOException{ try (InputStream inputStream = inputStreamIn) { Path targetDirectoryPath = Paths.get("Files-Upload/"); Path filePath = Paths.get("Files-Upload/").resolve("pacman_preview.png"); Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException ioe) { throw new IOException("Could not save file: " , ioe); } }
问题表现:
无法加载图片...读取PNG文件时发生致命错误,不是PNG文件
注:注释掉throwExceptionIfImageDimensionsNotValid(inputStream)后,图片可正常打开;调试发现执行ImageIO.read(inputStream)后inputStream的内部状态(bb、bs)已变更。
问题原因
InputStream是单向只读流,ImageIO.read(inputStream)会将流的读取指针移动到流的末尾。后续调用Files.copy时,流中已无剩余数据可读取,导致写入的文件内容不完整或为空,最终造成PNG文件损坏。此外,MultipartFile提供的输入流通常不支持reset()方法,无法直接将指针移回起始位置复用。
解决方案
方案1:将流内容读取到字节数组,复用字节数组生成新流(推荐)
先把输入流的全部内容读取到字节数组中,再分别基于字节数组创建新的输入流用于校验和存储,避免流指针移动的问题:
// 读取MultipartFile的流到字节数组 byte[] imageBytes = IOUtils.toByteArray(multipartFile.getInputStream()); // 校验:用字节数组生成新流 throwExceptionIfImageDimensionsNotValid(new ByteArrayInputStream(imageBytes)); // 存储:再生成一个新流 storeIntoFileSystem(new ByteArrayInputStream(imageBytes));
注:IOUtils.toByteArray来自Apache Commons IO工具类,也可自行实现字节数组读取逻辑。
方案2:利用流的mark/reset特性(仅适用于支持mark的流)
如果输入流支持mark/reset操作,可在校验前标记流的起始位置,校验完成后重置指针:
private void throwExceptionIfImageDimensionsNotValid(InputStream inputStream) throws IOException, StoreFileException { // 先标记流的起始位置,需确保流支持mark if (!inputStream.markSupported()) { throw new IOException("InputStream does not support mark/reset"); } inputStream.mark(Integer.MAX_VALUE); BufferedImage im = ImageIO.read(inputStream); if(isImageWidthHeightRatioOk(im.getHeight(), im.getWidth())) { // 重置流到标记的起始位置 inputStream.reset(); return; } else { throw new StoreFileException("图片高宽比需在0.55至0.625之间"); } }
注:MultipartFile的输入流大多不支持mark/reset,此方案兼容性较差,优先选方案1。
内容的提问来源于stack exchange,提问作者KnowledgePath
相关产品推荐
相关产品推荐

