我正在用JavaFX 17制作一个图片查看器应用程序。总之,这个应用程序类似于Windows /。用户可以打开图片或文件夹。应用程序将显示给定图片或给定文件夹中的第一张图片。我的应用程序将一次显示一张图片,用户可以使用可用的控件(next、prev、last&start)导航图片。
我检查了下面的线程,以确保它得到了足够的优化:
但是,我发现我的代码在处理200张大小在1~2MB左右的图片时遇到了问题。
如果没有背景加载,应用程序就不会显示任何内容。即使导航控制状态由于知道有可用的图片而被更改。因此,单击next & prev只显示一个空白屏幕。当使用背景加载时,只加载第一个图像中的几个。经过几次控制后,突然又变成了空白。
下面是我的最小的、可复制的例子:
package com.swardana.mcve.image;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* JavaFX App
*/
public class App extends Application {
@Override
public void start(Stage stage) {
var view = new View();
var path = Paths.get("Path/to/many/images");
var storage = new Storage(new PictureSource(path));
storage.setOnSucceeded(eh -> view.exhibit(storage.getValue()));
Executors.newSingleThreadExecutor().submit(storage);
var scene = new Scene(view, 640, 480);
scene.addEventFilter(KeyEvent.KEY_PRESSED, eh -> {
switch (eh.getCode()) {
case RIGHT:
view.next();
break;
case DOWN:
view.last();
break;
case LEFT:
view.prev();
break;
case UP:
view.beginning();
break;
default:
throw new AssertionError();
}
});
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
public class Picture {
private final String name;
private final Image image;
public Picture(final String name, final Path src) throws IOException {
this(name, new Image(src.toUri().toURL().toExternalForm(), true));
}
public Picture(final String name, final Image img) {
this.name = name;
this.image = img;
}
public final String name() {
return this.name;
}
public final Image image() {
return this.image;
}
}
public class PictureSource {
private final Path source;
public PictureSource(final Path src) {
this.source = src;
}
public final List<Picture> pictures() {
var dir = this.source.toString();
final List<Picture> pictures = new ArrayList<>();
try (var stream = Files.newDirectoryStream(this.source, "*.{png,PNG,JPG,jpg,JPEG,jpeg,GIF,gif,BMP,bmp}")) {
for (final var path : stream) {
var picName = path.getFileName().toString();
pictures.add(
new Picture(picName, path)
);
}
return pictures;
} catch (final IOException ex) {
throw new RuntimeException(ex);
}
}
}
public class Storage extends Task<List<Picture>> {
private final PictureSource source;
public Storage(final PictureSource src) {
this.source = src;
}
@Override
protected final List<Picture> call() throws Exception {
return this.source.pictures();
}
}
public class View extends VBox {
private final ImageView image;
private List<Picture> pictures;
private int lastIdx;
private int index;
public View() {
this.image = new ImageView();
this.initGraphics();
}
// This method to accept value from the `Storage`.
public void exhibit(final List<Picture> pics) {
this.pictures = pics;
this.index = 0;
this.lastIdx = pics.size();
this.onChange();
}
public void next() {
if (this.index != this.lastIdx - 1) {
this.index++;
this.onChange();
}
}
public void prev() {
if (this.index != 0) {
this.index--;
this.onChange();
}
}
public void last() {
this.index = this.lastIdx - 1;
this.onChange();
}
public void beginning() {
this.index = 0;
this.onChange();
}
// Whenever the action change, update the image from pictures.
public void onChange() {
this.image.setImage(this.pictures.get(this.index).image());
}
private void initGraphics() {
this.getChildren().add(this.image);
}
}
}
真的很感谢你的帮助和建议。
发布于 2022-04-13 11:39:34
问题是,您一次加载所有图像的大小(它需要大量内存),并将它们保存在List<Pictures>
中,这样它们就会留在内存中。我尝试用您的代码加载100张大图片,到第10张图片时,我得到了OutOfMemoryError: Java heap space
(使用的堆大小约为2.5GB )。
我找到了两种可能的解决方案:
将图像调整为800 to宽度,将已使用的堆减少到600 to。为此,我更改了Picture
的类构造函数。
public Picture(final String name, final Path src) throws IOException {
this(name, new Image(src.toUri().toURL().toExternalForm(), 800, 0, true, true, true));
}
如果只有在需要时才加载映像,则使用的堆大小最多为250 to,有几次跳转到500 to。再次,我更改了类Picture
的构造函数,并引入了一个新的字段imageUrl
,因此只需将Path
转换为URL string
,就不会创建Image
对象。
private final String imageUrl;
public Picture(final String name, final Path src) throws IOException {
this(name, src.toUri().toURL().toExternalForm());
}
public Picture(final String name, final String imageUrl) {
this.name = name;
this.imageUrl = imageUrl;
}
image()
方法现在不返回预加载映像,而是按需加载图像并同步加载。
public final Image image() {
return new Image(imageUrl);
}
对于一个包含850幅图像的文件夹,我得到了以下信息:
在创建Picture
时加载映像,并将它们全部保存在List
结果中,以消耗大量内存和GC活动(这无法释放内存)。
在懒惰的情况下,我得到了这些图表。
https://stackoverflow.com/questions/71839473
复制相似问题