Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/image/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java—缩放图像的正确方法_Java_Image_Memory_Garbage Collection_Javax.imageio - Fatal编程技术网

Java—缩放图像的正确方法

Java—缩放图像的正确方法,java,image,memory,garbage-collection,javax.imageio,Java,Image,Memory,Garbage Collection,Javax.imageio,上下文 我正在回顾一些在服务器端应用程序中用于缩放图像的遗留Java代码。直到最近,它主要用于分辨率为1024x768或以下的输入图像,在这种情况下,它似乎工作得很好(或至少足够好) 然而,现在,许多正在处理的图像的分辨率在2592x1936(8MP)到6000x4000(24MP)之间。这导致服务器上频繁报告OutOfMemoryError,我认为这是由图像处理代码引起的 测试用例 我编写了一个简单的测试应用程序,它以与服务器相同的方式调用图像处理代码(有一个警告;testcase是单线程的,

上下文

我正在回顾一些在服务器端应用程序中用于缩放图像的遗留Java代码。直到最近,它主要用于分辨率为1024x768或以下的输入图像,在这种情况下,它似乎工作得很好(或至少足够好)

然而,现在,许多正在处理的图像的分辨率在2592x1936(8MP)到6000x4000(24MP)之间。这导致服务器上频繁报告
OutOfMemoryError
,我认为这是由图像处理代码引起的

测试用例

我编写了一个简单的测试应用程序,它以与服务器相同的方式调用图像处理代码(有一个警告;testcase是单线程的,而服务器环境显然不是):

此外,我发现,如果我开始注释代码,直到剩下的唯一一行是:

buffereImage image=ImageIO.read(上传文件)

…就最终的记忆计数而言,结果基本上还是一样的

结果

下面是每次运行的最终输出(来自GC日志和内置内存日志语句):

#Source @ 1024x768 (control)
[GC pause (young) 1333M->874M(1612M), 0.0016051 secs]
Memstats after 990 iterations:  Free Memory:  505.5521469116211 MBytes
    Total Memory:  1612.0 MBytes
    Max Memory:  6144.0 MBytes


#Source @ 2592x1936 (test):
[GC pause (young) 2161M->1610M(2632M), 0.0025038 secs]
Memstats after 990 iterations:  Free Memory:  425.37239837646484 MBytes
    Total Memory:  2632.0 MBytes
    Max Memory:  6144.0 MBytes
两者似乎都高于应有的水平。这里没有提到的是,在测试运行期间,垃圾收集器经常一次回收超过1GB的内存

也许还值得注意的是,随着运行的进行,“总内存”值会越来越高,就好像垃圾收集器无法回收所有被搅动的内存一样。那么,这可能意味着内存泄漏

平均而言,垃圾收集器似乎每2-3次循环迭代触发一次。而且在任何情况下,由于我在一个线程上连续缩放图像,搅动量似乎相当不合理。这不应该是消耗千兆字节和千兆字节内存的东西。即使幕后Java将每个图像解压为32位位图,我也不会期望内存使用率像观察到的那样快速增长

问题

  • 在Java中缩放高分辨率图像的正确/最节省内存的方法是什么
  • 遗留代码中是否存在明显的错误,或者我是否应该放弃它并重写
  • 我是否最好生成一个外部进程来执行图像操作(例如,由于某种原因,Java本质上不适合这个用例)

首先,尽管你在这个问题上付出了很多努力,但我不确定它是否适合。也许代码审查是发布它的更好地方?第1项和第3项都是典型的“视情况而定”类问题,需要进一步的背景知识,并且可能会产生自以为是的答案。例如,我可以以牺牲图像质量为代价,以非常少的内存超快速缩放高分辨率图像。但我不知道你们的质量要求是什么。对于第3项,在我看来,对你来说“更好”的是,很大程度上取决于你团队的专业知识。@haraldK-即使我缩减了代码,只剩下
BuffereImage=ImageIO.read(uploadedFile),结果基本相同。因此,我认为这个问题不是关于代码审查,而是关于“BuffereImage
/
ImageIO因其内部如何管理内存而从根本上不可扩展吗?”。我倾向于回答“是的”。根据我所看到的,使用
javax.image
包下的类,您无法以极快的速度和很少的内存完成任何事情。
ImageIO
API使用
ImageReader
子类有许多更“细粒度”的方法来控制图像的读取方式。您不需要使用
ImageIO.read(..)
读取整个图像(顺便说一下,您的旧代码会执行两次)。您可以读取二次采样的图像,这将比读取整个图像更快,并且使用更少的内存。我知道人们正在用Java编写图像服务器,所以我认为“根本无法扩展”的说法是没有根据的。:-)PS:可能对于您的用例(以及您的经验/直觉),衍生到外部流程才是正确的做法。我知道人们也成功地做到了这一点(通常使用ImageMagick/GraphicsMagick)。它只是一个完全不同的野兽,有它自己的一套怪癖和问题。我终于找到时间运行你的测试应用程序,通过在末尾添加一个
System.gc()
并再次打印内存统计数据,我得到了
[完整的gc 741M->540K(7168K),0.1173000秒]1000次迭代后的Memstats:可用内存:6.4516754150390625 MB总内存:7.0 MB最大内存:4096.0 MB
。也就是说,所有内存都被回收并返回给主机操作系统。因此,除了遗留代码中一些明显的低效之外,我不认为有理由在这里抛弃Java。:-)
public static boolean verifyImageIsValidAndScale(File in, File out, int width, int height, boolean aspectFit, int quality){
    boolean valid = verifyImageIsValid(in);
    aspectFitImage(in, out, width, height, quality);

    return valid;
}

public static boolean verifyImageIsValid(File file){
    InputStream stream = null;
    try {
        String path = file.getAbsolutePath();
        stream = new FileInputStream(path);

        byte[] buffer = new byte[11];

        int numRead = stream.read(buffer);

        if(numRead > 3 && buffer[1] == 'P' && buffer[2] == 'N' && buffer[3] == 'G') {
            stream.close();
            buffer = null;
            LOG.debug("Valid PNG");
            return ImageIO.read(new File(path)) != null;    //PNG
        } else if (numRead > 10 && ((buffer[6] == 'J' && buffer[7] == 'F' && buffer[8] == 'I' && buffer[9] == 'F' && buffer[10] == 0) || 
                (buffer[6] == 'E' && buffer[7] == 'x' && buffer[8] == 'i' && buffer[9] == 'f' && buffer[10] == 0))){
            stream.close();
            buffer = null;
            LOG.debug("Valid JPEG");
            return ImageIO.read(new File(path)) != null;    //JPEG
        }
        buffer = null;
    } catch (Exception e) {
        return false;
    }
    finally {
        try {
            stream.close();
        }
        catch (Exception ignored) {}
    }
    LOG.debug("Invalid Image");
    return false;
}

public static Image aspectFitImage(File uploadedFile, File outputFile, int maxWidth, int maxHeight, int quality) {
    if (quality < 0) {
        quality = 0;
    }

    Image scaledImage = null;
    try {
        BufferedImage image = ImageIO.read(uploadedFile);
        double scaleX = image.getWidth() > maxWidth ? maxWidth / (double)image.getWidth() : 1.0;
        double scaleY = image.getHeight() > maxHeight ? maxHeight / (double)image.getHeight() : 1.0;
        double useScale = scaleX < scaleY ? scaleX : scaleY;

        scaledImage = image.getScaledInstance((int)(image.getWidth() * useScale), (int)(image.getHeight() * useScale), Image.SCALE_SMOOTH);

        //XXX:  getting bizarre results where overwriting a pre-existing output file doesn't properly overwrite the existing file; explicitly deleting the output file when it already exists seems to solve the issue
        if (outputFile.exists()) {
            outputFile.delete();
        }

        ImageOutputStream imageOut = ImageIO.createImageOutputStream(outputFile);
        ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();

        ImageWriteParam options = writer.getDefaultWriteParam();
        options.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        options.setCompressionQuality(quality / 100.0f);

        PixelGrabber pg = new PixelGrabber(scaledImage, 0, 0, -1, -1, true);
        pg.grabPixels();

        DataBuffer buffer = new DataBufferInt((int[]) pg.getPixels(), pg.getWidth() * pg.getHeight());
        WritableRaster raster = Raster.createPackedRaster(buffer, pg.getWidth(), pg.getHeight(), pg.getWidth(), RGB_MASKS, null);
        BufferedImage bi = new BufferedImage(RGB_OPAQUE, raster, false, null);

        writer.setOutput(imageOut);
        writer.write(null, new IIOImage(bi, null, null), options);
        imageOut.close();
        writer.dispose();
    }
    catch (Exception e) {
        uploadedFile.delete();  
        return null;
    }

    return scaledImage;
}
#Source @ 1024x768 (control)
[GC pause (young) 1333M->874M(1612M), 0.0016051 secs]
Memstats after 990 iterations:  Free Memory:  505.5521469116211 MBytes
    Total Memory:  1612.0 MBytes
    Max Memory:  6144.0 MBytes


#Source @ 2592x1936 (test):
[GC pause (young) 2161M->1610M(2632M), 0.0025038 secs]
Memstats after 990 iterations:  Free Memory:  425.37239837646484 MBytes
    Total Memory:  2632.0 MBytes
    Max Memory:  6144.0 MBytes