【问题标题】:How to handle huge data/images in RAM in Java?如何在 Java 中处理 RAM 中的大量数据/图像?
【发布时间】:2019-10-01 20:35:24
【问题描述】:

总结

  1. 我正在读取一个包含图像数据的大型二进制文件。
  2. 对数据执行累积计数切割分析[它需要另一个与图像大小相同的数组]。
  3. 数据在 0 到 255 之间拉伸,存储在 BufferedImage 中的像素一个像素,以在 JPanel 上绘制图像。
  4. 在此图像上,使用AffineTransform 执行缩放。

问题

  1. 小图像(<.5gb>

    1.1 当我增加执行缩放的比例因子时,在
    点异常被抛出:-

java.lang.OutOfMemoryError: Java 堆空间。

下面是用于缩放的代码-

    scaled = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    Graphics2D g2d = (Graphics2D)scaled.createGraphics();
    AffineTransform transformer = new AffineTransform();
    transformer.scale(scaleFactor, scaleFactor); 
    g2d.setTransform(transformer);
  1. 大图(>1.5GB)
    • 在加载一个巨大的图像(>1.5GB)时,出现与出现相同的异常 1.1,即使图像小到可以加载,有时也会出现同样的错误。

尝试的解决方案

  1. 我尝试使用BigBufferedImage 代替 BufferedImage 来存储拉伸数据。 BigBufferedImage image = BigBufferedImage.create(newCol,newRow, BufferedImage.TYPE_INT_ARGB);
  2. 但无法传递给g2d.drawImage(image, 0, 0, this); 因为 JPanel 的 repaint 方法只是因为某种原因停止了。

  3. 我尝试以低分辨率加载图像,其中读取像素并且跳转/跳过了少数列和行。但问题是如何决定随着图像大小的变化要跳过多少像素,因此我无法决定如何决定 jump 参数。

    MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0, inChannel.size());
    buffer.order(ByteOrder.LITTLE_ENDIAN);
    FloatBuffer floatBuffer = buffer.asFloatBuffer();
    for(int i=0,k=0;i<nrow;i=i+jump)  /*jump is the value to be skipped, nrow is height of image*/
    {
        for(int j=0,l=0;j<ncol1;j=j+jump)   //ncol is width of image
        {
                index=(i*ncol)+j;
                oneDimArray[(k*ncolLessRes)+l] = floatBuffer.get(index);//oneDimArray is initialised to size of Low Resolution image.
                l++;
        }
        k++;
    }

问题是决定要跳过多少列和行,即应该设置什么跳转值。

  1. 我尝试设置 Xmx,但图像大小不同,我们cannot dynamically set Xmx 值。 以下是一些值 -

table, th, td {
  border: 1px solid black;
}
<table style="width:100%">
  <tr>
    <th>Image Size</th>
    <th>Xmx</th>
    <th>Xms</th>
    <th>Problem</th>
  </tr>
  <tr>
    <td>83Mb</td>
    <td>512m</td>
    <td>256m</td>
    <td>working</td>
  </tr>
  <tr>
    <td>83Mb</td>
    <td>3096m</td>
    <td>2048m</td>
    <td>System hanged</td>
  </tr>
   <tr>
    <td>3.84Gb</td>
    <td>512m</td>
    <td>256m</td>
    <td>java.lang.OutOfMemoryError: Java heap space
  </tr>
  <tr>
    <td>3.84Gb</td>
    <td>3096m</td>
    <td>512m</td>
    <td>java.lang.OutOfMemoryError: Java heap space
  </tr>
</table>
  1. 为此,我尝试查找分配给程序的内存:-
 try(BufferedWriter bw= new BufferedWriter(new FileWriter(dtaFile,true))){
    Runtime runtime=Runtime.getRuntime();
    runtime.gc();
    double oneMB=Math.pow(2,20);
    long[] arr= Instream.range(0,(int)(10.432*long.BYTES*Math.pow(2,20))).asLongStream().toArray();
    runtime.gc();
    long freeMemory= runtime.freeMemory();
    long totalMemory= runtime.totalMemory();
    long usedMemory= totalMemory-freeMemory;
    long maxMemory= runtime.maxMemory();
    String fileLine= String.format(" %9.3f  %9.3f   %9.3f " , usedMemory/oneMb, freeMemory/oneMB, totalMemory/oneMb, maxMemory/oneMB);
    bw.write();
}

得到以下结果
Memory Allocation
这种方法失败了,因为可用内存随着我代码的使用而增加。因此,我无法做出跳跃的决定。

预期结果

一种在加载图像之前访问可用内存量的方法,以便我可以使用它来决定跳转的值。是否有其他选择来决定 jump 值(即,我可以降低多少分辨率?)。

【问题讨论】:

  • 请在您的问题中添加您尝试过的 Xmx 值。
  • 4.将 Xmx 设置为任意大的数字(100 GB)怎么样?在这种情况下,当系统无法提供时,JVM 将简单地死掉。用oracle JVM获取系统内存的方法在这里stackoverflow.com/questions/5512378/…。但是,如果您不需要内存中的整个图像(我猜它是原始图像?)为什么不简单地逐行(或逐像素)读取它。 CCC 不需要内存 IIRC 中的整个图像
  • 如果你需要内存中的整个图像怎么办。要在 Panel 上使用 Graphics 绘制它,您需要整个 Image,对吧?
  • @Ellen Spertus 使用手动设置 Xmx 值意味着,您不能像上面提到的那样拥有任意大的图像。另外,我们可以在启动 JVM 后设置 Xmx 值吗?如果出现无法重启 JVM 的场景怎么办?
  • @EllenSpertus 我已在问题中添加了值。

标签: java graphics2d


【解决方案1】:

您可以读取图像的特定部分,然后以降低的分辨率对其进行缩放以进行显示。

因此,在您的情况下,您可以分块读取图像(读取图像部分,就像我们从 db 中逐行读取数据一样)

例如:

// Define the portion / row size 50px or 100px
int rowHeight = 50;
int rowsToScan = imageHeight / rowHeight;
if(imageHeight % rowHeight > 0) rowsToScan++;

int x = 0;
int y = 0;
int w = imageWidth;
int h = rowHeight;

ArrayList<BufferedImage> scaledImagePortions = new ArrayList<>();

for(int i = 1; i <= rowsToScan; i++) {
    // Read the portion of an image scale it
    // and push the scaled version in lets say array
    BufferedImage scalledPortionOfImage = this.getScaledPortionOfImage(img, x, y, w, h);
    scaledImagePortions.add(scalledPortionOfImage);

    y = (rowHeight * i);
}

// Create single image out of scaled images portions

可以帮助您获取图像部分的线程Read region from very large image file in Java

可以帮助您缩放图像的线程(我的快速搜索结果:)) how to resize Image in java?

可以帮助您合并缓冲图像的线程:Merging two images

您可以随时调整 sn-ps :)

【讨论】:

    【解决方案2】:
    1. OutOfMemoryError 这是自我解释 - 你内存不足。也就是说,不是您机器上的物理 RAM,而是 JVM 达到了由 -xmx 设置设置的内存分配上限
    2. 当您尝试将 3.8GB 大小的图像放入 512MB 内存块时,您的 xmlx 设置测试毫无意义。它行不通——你不能把 10 升水放在 5 升的瓶子里。对于内存使用,您至少需要图像 x3 的大小,因为您分别存储每个像素并且包含 3 个字节 (RGB)。这仅适用于纯图像数据。剩下的是整个应用程序和数据对象结构的开销 + 计算所需的额外空间,可能还有很多我没有提到,我什至不知道的空间。
    3. 您不想“动态设置”-xmx。将其设置为系统中的最大可能值(反复试验)。除非需要,否则 JVM 不会占用那么多内存。通过额外的 -X 设置,您可以告诉 JVM 释放未使用的内存,这样您就不必担心未使用的内存会被 JVM “冻结”。
    4. 我从未从事过图像处理应用程序。 Photoshop 或 Gimp 是否能够对如此大的图像打开并做一些有用的事情?也许您应该寻找有关在那里处理大量数据的线索(如果它正在工作)
    5. 如果上述观点只是幼稚的,因为您需要将其用于科学目的(这不是 Photoshop 或 Gimp 的用途,除非您是 flatearther :)),那么您将需要科学级硬件。
    6. 一件事进入我的脑海,根本不是将图像读入内存,而是动态处理它。这可以将内存消耗减少到兆字节的数量级。

    仔细研究ImageReader API,因为它建议(readTile 方法)可能只能读取图像区域(例如用于放大)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-24
      • 1970-01-01
      • 1970-01-01
      • 2015-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多