【问题标题】:Java Swing: Read many images files without memory problems?Java Swing:读取许多图像文件而没有内存问题?
【发布时间】:2009-11-01 21:02:46
【问题描述】:

我正在编写一个 Java Swing 应用程序,它将从磁盘加载大约 1500 个 png 图像,每个图像的大小从 50kb 到 75kb 不等。我不需要一次加载所有这些,但我需要一次加载 50 张图像。我一直在尝试使用典型的加载它们:

new javax.swing.ImageIcon(getClass().getResource("myimage.jpeg")

但我的应用程序在大约前 30 张图像后冻结,并且内存不足。

以这样的方式加载这些图像的最佳方法是什么,我不会超载 jvm 并且我将能够监控到目前为止哪些已成功加载?如果可能且有必要,我不介意应用程序在加载图像时显示“正在加载...”屏幕,但我不知道该怎么做。

缓存在这里有用吗?我不太明白,但我看到this question 关于使用 MediaTracker,我不确定如何在这里实现。

【问题讨论】:

    标签: java user-interface image swing


    【解决方案1】:

    为什么不为每个图像创建一个包装对象,按需加载它们,并使用WeakReferences 或SoftReferences。这样垃圾收集器可以在必要时对图像数据进行分箱,并且您可以在弱引用被清除时重新加载。

    好处是可以在需要其他用途时清除图像的内存。缺点是您必须在显示之前重新加载图像(或对图像进行任何操作)。

    【讨论】:

      【解决方案2】:

      如果您已经知道要加载多少个 .png,您可能需要创建一个 ImageIcon 数组并从目录/目录中一一加载(这将允许您显示加载...屏幕) .

      我认为您应该做的是增加最小值/最大值。运行应用程序时 JVM 的 HeapSize。您可以通过例如指定它们添加 -Xmx256m 作为参数(这会将最大堆设置为 256MB)(可能还有 -Xms32m [这会将最小堆设置为 32mb])参见 http://docs.sun.com/source/817-2180-10/pt_chap5.html#wp57033

      您可以在启动应用程序时添加这些选项(例如“java -jar myApp.jar -Xmx128m”),或者添加到系统的 jvm-configuration-file 或项目的构建属性中。

      这段代码会加载整个目录;如果您只想加载 50 张图片,只需调整 start 和 stop 参数即可。
      如前所述,您必须将最大堆 (-Xmx) 设置为 300M 左右(例如,分辨率为 1280x1024 -> 1310720px -> 4 字节/像素 -> 5242880 字节 -> 50 张图像 -> 256MB)。

      File dir = new File("/path/to/directory");
      File[] files = dir.listFiles();
      BufferedImage[] images = new BufferedImage[files.length];
      for (int i = 0; i < files.length; i++)
      {
         try
         {
           images[i] = ImageIO.read(files[i]);
         } catch (IOException ex){}
      }
      

      【讨论】:

      • 好的。好主意,但我是否仍应将它们作为 ImageIcons 循环加载?必须有更好的方法
      • 您可以使用 ImageIO 在循环中将它们加载为图像,然后您知道它在调用返回时已加载,而不是使用单独的回调来跟踪加载。 png 图像在加载时占用多少内存取决于它有多少像素,而不是磁盘上的大小。
      • 你的堆设置是多少?如果您没有添加提到的标志,那么您可能只需要在启动时添加更多内存。
      • 啊,所以这是您要观看的某种幻灯片。我建议然后预加载/缓存例如5 张图片,显示 1 张图片(持续 4 秒)并加载下一张(第六张)图片。
      • @Tedil,问题是我需要一次预加载/缓存至少 50 张图像,但我不知道该怎么做。我正在尝试学习如何实际执行此操作,如果您在答案中添加如何预加载图像,我会将您的答案标记为正确
      【解决方案3】:

      正如 Tedil 所说,您应该通过以下方式启动应用程序来为应用程序提供更多内存:

      java -Xmx256m -classpath yourclasspath YourMainClass
      

      要使用“请稍候”加载屏幕和进度条来加载图像是很棘手的。它已经在 Advanced Swing 的领域。如果您使用的是 Java 6,我建议您阅读 SwingWorker 类。

      这是一个演示,向您展示了一种方法:

      package com.barbarysoftware;
      
      import javax.swing.*;
      import java.awt.*;
      import java.awt.event.ActionEvent;
      import java.awt.image.BufferedImage;
      import java.util.List;
      
      public class ImageLoadingDemo {
      
          public static void main(String[] args) {
              final JFrame frame = new JFrame();
              frame.setPreferredSize(new Dimension(600, 400));
              frame.getContentPane().add(new JLabel("I'm the main app frame", JLabel.CENTER));
              frame.pack();
              frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              frame.setLocationRelativeTo(null);
              frame.setVisible(true);
      
              final JDialog pleaseWaitDialog = new JDialog(frame, "Loading images", true);
      
              final int imageCount = 50;
              final JProgressBar progressBar = new JProgressBar(0, imageCount);
      
              final BufferedImage[] images = loadImages(frame, pleaseWaitDialog, imageCount, progressBar);
              System.out.println("images = " + images);
          }
      
          private static BufferedImage[] loadImages(JFrame frame, final JDialog pleaseWaitDialog, final int imageCount, final JProgressBar progressBar) {
              final BufferedImage[] images = new BufferedImage[imageCount];
              SwingWorker<Void, Integer> swingWorker = new SwingWorker<Void, Integer>() {
                  @Override
                  protected Void doInBackground() throws Exception {
                      for (int i = 0; i < imageCount; i++) {
                          System.out.println("i = " + i);
                          publish(i);
                          Thread.sleep(1000); // to simulate the time needed to load an image
      //                    images[i] = ImageIO.read(new File("... path to an image file ..."));
                      }
                      return null;
                  }
      
                  @Override
                  protected void process(List<Integer> chunks) {
                      final Integer integer = chunks.get(chunks.size() - 1);
                      progressBar.setValue(integer);
                  }
      
                  @Override
                  protected void done() {
                      pleaseWaitDialog.setVisible(false);
                  }
              };
              JPanel panel = new JPanel();
              panel.add(progressBar);
              panel.add(new JButton(new AbstractAction("Cancel") {
                  public void actionPerformed(ActionEvent e) {
                      System.exit(0);
                  }
              }));
              pleaseWaitDialog.getContentPane().add(panel);
              pleaseWaitDialog.pack();
              pleaseWaitDialog.setLocationRelativeTo(frame);
              swingWorker.execute();
              pleaseWaitDialog.setVisible(true);
              return images;
          }
      }
      

      【讨论】:

        【解决方案4】:

        您应该非常小心通过类加载器加载文件,因为当类加载器处于活动状态时,这些资源不会被释放。

        而是使用另一种方法,使用 java.io.File 对象直接从磁盘加载文件。然后它们可以被丢弃而不会隐藏在周围。

        【讨论】:

        • Class#getResource 仅解析一个 URL,可以从中加载图像(或任何其他资源)。类加载器不参与实际加载资源,也不缓存任何内容。
        【解决方案5】:

        您打算如何处理这些图像?您真的需要 ImageIcon 实例,还是 Image 也需要?无论哪种情况,如果您使用 ImageIO 中的同步方法而不是 ImageIcon 构造函数,您可能更容易控制加载。

        如果您在 30 张图像之后已经遇到 OutOfMemoryErrors,我认为这些图像可能具有高分辨率和/或颜色深度,即使它们在磁盘上相对较小。加载图像时,图像会被解压缩,并且需要比压缩文件的大小更多的内存(对于彩色图像,通常为 4*width*height 字节)。除非图像非常小,否则您可能无法在合理的内存量内缓存 1500 张未压缩的图像,因此您必须实施一些策略来仅加载您当前需要的图像。

        【讨论】:

        • 我正在用图像制作幻灯片以进行心理学实验,在另一个线程上,我正在测试不同类型形状的反应时间。每张图片最多显示 4 秒,但如果用户按空格键,则幻灯片会暂停。
        • 使用 ImageIcon 和 Image 实际上有什么区别? ImageIO 中哪些方法是同步的?
        • ImageIO.read 是同步的,直到图像被完全读取并解压缩后才会完成。 Image 是图像的抽象表示,ImageIcon 是用于显示图像的 Swing GUI 组件。如果您使用字节数组或图像源创建 ImageIcon,则在显示组件之前,可能不会实际加载和准备图像,如果您有很多图像,这可能会导致奇怪的行为。如果您使用 ImageIO 创建了 Image 实例,您仍然可以使用该图像创建 ImageIcon 实例并防止后台加载和解压缩。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-08-17
        • 1970-01-01
        • 1970-01-01
        • 2013-12-14
        • 2014-08-16
        • 1970-01-01
        • 2014-07-14
        相关资源
        最近更新 更多