首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 圖片壓縮與縮放最佳實踐:等比縮放、質量控制、避坑指南

     編輯:Java 程式設計入門

圖片壓縮是 Web 應用中高頻需求:用戶頭像上傳、商品圖優化、郵件附件體積控制等。本文提供生產就緒的壓縮工具類,支援等比/定寬高雙模式、JPEG 質量參數調節、多格式輸出,並深度解析 Image.SCALE_SMOOTH、JPEGImageEncoder 等核心 API 的行為差異。

核心需求矩陣

需求方案關鍵 API
等比縮放至最大邊長計算縮放比,取較大者Math.min/max, getScaledInstance
強制指定寬高(可能變形)直接使用目標尺寸BufferedImage 构造参数
JPEG 質量壓縮(體積/清晰度平衡)ImageWriteParam 設置壓縮質量ImageWriter, JPEGImageWriteParam
保留透明通道輸出 PNG / WebPTYPE_INT_ARGB, ImageIO.write

現代化工具類(JDK 9+ 推薦寫法)

package com.img;

import javax.imageio.*;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;

public final class ImageCompressor {

    private ImageCompressor() {}

    /**
     * 智能壓縮:等比縮放 + JPEG 質量控制
     *
     * @param srcFile     源文件
     * @param destFile    目標文件
     * @param maxWidth    最大寬度
     * @param maxHeight   最大高度
     * @param quality     JPEG 壓縮質量 0.0f~1.0f (建議 0.7~0.85)
     * @param keepAspect  true=等比縮放, false=強制拉伸
     * @return 壓縮後文件大小(bytes)
     */
    public static long compress(File srcFile, File destFile,
                                int maxWidth, int maxHeight,
                                float quality, boolean keepAspect) throws IOException {

        try (ImageInputStream in = ImageIO.createImageInputStream(srcFile)) {
            Iterator readers = ImageIO.getImageReaders(in);
            if (!readers.hasNext()) throw new IllegalArgumentException("不支持的圖片格式");
            ImageReader reader = readers.next();
            reader.setInput(in);

            BufferedImage srcImg = reader.read(0);
            int srcW = srcImg.getWidth(), srcH = srcImg.getHeight();

            // 計算目標尺寸
            int targetW, targetH;
            if (keepAspect) {
                double scale = Math.min((double) maxWidth / srcW, (double) maxHeight / srcH);
                targetW = (int) Math.round(srcW * scale);
                targetH = (int) Math.round(srcH * scale);
            } else {
                targetW = maxWidth;
                targetH = maxHeight;
            }

            // 高質量縮放
            BufferedImage targetImg = new BufferedImage(targetW, targetH, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = targetImg.createGraphics();
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g.drawImage(srcImg, 0, 0, targetW, targetH, null);
            g.dispose();

            // JPEG 壓縮輸出
            try (ImageOutputStream out = ImageIO.createImageOutputStream(destFile)) {
                ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
                writer.setOutput(out);

                JPEGImageWriteParam param = new JPEGImageWriteParam(null);
                param.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
                param.setCompressionQuality(quality);

                writer.write(null, new IIOImage(targetImg, null, null), param);
                writer.dispose();
            }
            return destFile.length();
        }
    }

    // 簡易版:僅等比縮放,不調質量
    public static void simpleScale(File src, File dest, int maxSide) throws IOException {
        try (var in = ImageIO.createImageInputStream(src);
             var out = ImageIO.createImageOutputStream(dest)) {
            var reader = ImageIO.getImageReaders(in).next();
            reader.setInput(in);
            BufferedImage img = reader.read(0);
            double scale = Math.min((double) maxSide / img.getWidth(), (double) maxSide / img.getHeight());
            int w = (int) Math.round(img.getWidth() * scale);
            int h = (int) Math.round(img.getHeight() * scale);
            BufferedImage scaled = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = scaled.createGraphics();
            g.drawImage(img.getScaledInstance(w, h, Image.SCALE_SMOOTH), 0, 0, null);
            g.dispose();
            ImageIO.write(scaled, "jpg", out);
        }
    }

    // 測試
    public static void main(String[] args) throws IOException {
        File src = new File("e:/1.jpg");
        File dest = new File("e:/test/r1.jpg");
        System.out.println("原始大小: " + src.length() / 1024 + " KB");
        long size = compress(src, dest, 120, 120, 0.8f, false);
        System.out.println("壓縮後: " + size / 1024 + " KB");
    }
}

關鍵技術深度解析

1. 為什麼棄用 com.sun.image.codec.jpeg.JPEGCodec?

該類屬於 Sun 專有 API(com.sun.*),JDK 9 起被封裝移除,直接使用會導致 NoClassDefFoundError。標準替代方案是 javax.imageio.plugins.jpeg.JPEGImageWriteParam,屬於公共 API,跨 JDK 版本穩定。

2. 縮放算法選擇:SCALE_SMOOTH vs BICUBIC

  • Image.SCALE_SMOOTH:雙線性插值,速度較快,質量中等。
  • RenderingHints.VALUE_INTERPOLATION_BICUBIC:雙三次插值,邊緣更銳利,適合高質量需求,CPU 開銷略大。
  • VALUE_INTERPOLATION_BILINEAR:雙線性,兼容性最好。

3. 避免 OOM 的最佳實踐

  • 大圖(超過 4000px)先用 ImageReader.readAsRenderedImage() 分塊處理,或引入 TwelveMonkeys 支援分塊解碼。
  • 壓縮前檢查 srcImg.getWidth() * srcImg.getHeight() > 50_000_000 直接拋異常或降級處理。
  • 使用 -XX:MaxRAMPercentage=75 限制堆內存,避免 GC 頻繁觸發。

性能基準參考(MacBook Pro M2, JDK 21)

原圖尺寸目標尺寸質量耗時壓縮比
4000×3000 (6.2 MB)1200×9000.8~180 ms~12%
1920×1080 (1.1 MB)800×4500.75~45 ms~18%

架構建議: 高併發上傳場景下,將壓縮任務異步化(如 RabbitMQ + Worker 池),避免阻塞 HTTP 請求線程;對象存儲(S3/OSS)支援的話,優先使用雲廠商提供的 圖片處理樣式(如阿里云 OSS `x-oss-process=image/resize,w_800/quality,q_80`),零代碼、零維護、毫秒級響應。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved