Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >JavaFX图像性能优化

JavaFX图像性能优化
EN

Stack Overflow用户
提问于 2022-04-12 00:35:01
回答 1查看 271关注 0票数 1

我正在用JavaFX 17制作一个图片查看器应用程序。总之,这个应用程序类似于Windows /。用户可以打开图片或文件夹。应用程序将显示给定图片或给定文件夹中的第一张图片。我的应用程序将一次显示一张图片,用户可以使用可用的控件(next、prev、last&start)导航图片。

我检查了下面的线程,以确保它得到了足够的优化:

但是,我发现我的代码在处理200张大小在1~2MB左右的图片时遇到了问题。

如果没有背景加载,应用程序就不会显示任何内容。即使导航控制状态由于知道有可用的图片而被更改。因此,单击next & prev只显示一个空白屏幕。当使用背景加载时,只加载第一个图像中的几个。经过几次控制后,突然又变成了空白。

下面是我的最小的、可复制的例子:

代码语言:javascript
运行
AI代码解释
复制
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);
        }
        
    }

}

真的很感谢你的帮助和建议。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-04-13 11:39:34

问题是,您一次加载所有图像的大小(它需要大量内存),并将它们保存在List<Pictures>中,这样它们就会留在内存中。我尝试用您的代码加载100张大图片,到第10张图片时,我得到了OutOfMemoryError: Java heap space (使用的堆大小约为2.5GB )。

我找到了两种可能的解决方案:

  1. 调整图像大小。
  2. 按需加载映像(懒惰)。

将图像调整为800 to宽度,将已使用的堆减少到600 to。为此,我更改了Picture的类构造函数。

代码语言:javascript
运行
AI代码解释
复制
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对象。

代码语言:javascript
运行
AI代码解释
复制
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()方法现在不返回预加载映像,而是按需加载图像并同步加载。

代码语言:javascript
运行
AI代码解释
复制
public final Image image() {
    return new Image(imageUrl);
}

对于一个包含850幅图像的文件夹,我得到了以下信息:

在创建Picture时加载映像,并将它们全部保存在List结果中,以消耗大量内存和GC活动(这无法释放内存)。

不延迟加载cpu和堆使用的图表

在懒惰的情况下,我得到了这些图表。

延迟加载时cpu和堆使用的图表

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71839473

复制
相关文章
springboot之前端参数验证
对于任何一个应用而言,在客户端做的数据有效性验证主要目的是规范用户的输入,而真实的数据验证工作都是在服务后端代码当中实现的,但在实际的项目当中,也经常会因为各种各样的原因:懒得写,觉得前端验证了,后端没有太多的必要等等没有进行数据验证,其实养成数据的有效性验证是一个非常好的习惯。 1 可以避免很多数据有效性导致的BUG,防范其余开发者的基础攻击 2 在前后端进行接口联调的时候,不需要因为参数的问题沟通很久。
海加尔金鹰
2020/06/08
1.1K0
location.href跳转测试
测试代码 <script type="text/javascript"> function ToUrl(x){ location.href=x; } </script> <a href="javascript:;" onclick="javascript:ToUrl('http://www.baidu.com');">location.href跳转测试1</a> <a href="javascript:void(0);" onclick="javascr
deepcc
2018/07/05
7140
[答疑]是增加了一个功能还是增加了一个系统
关于系统的定义,《软件方法》一书给出了2个关键点,后来在答疑时补充了系统要满足充分,必要条件。现实中做系统增量开发时,并不是很好分辨。
用户6288414
2020/04/08
4150
[答疑]是增加了一个功能还是增加了一个系统
go : gin路径参数
本文介绍 gin框架下如何获取路径参数代码:package mainimport ( "net/http" "github.com/gin-gonic/gin")func main() { router := gin.Default() // This handler will match /user/john but will not match /user/ or /use router.GET("/user/:name", func(c *gin.Context) {
IT工作者
2022/07/22
7830
FastAPI教程 路径参数
所以,如果你运行示例并访问 http://127.0.0.1:8000/items/foo,将会看到如下响应:
wencheng
2022/04/18
6120
FastAPI教程 路径参数
Confluence 6 在升级之前
在这个指南中,我们将会与你一同对最新的 Confluence 站点在 Windows 或者 Linux 平台进行安装和更新。
HoneyMoose
2019/04/20
6340
Confluence 6 在升级之前
location.href跳转测试
测试代码 <script type="text/javascript"> function ToUrl(x){ location.href=x; } </script> <a href="javascript:;" onclick="javascript:ToUrl('http://www.baidu.com');">location.href跳转测试1</a> <a href="javascript:void(0);" onclick="javascr
deepcc
2018/05/16
1.1K0
Confluence 6 在升级之前 原
在这个指南中,我们将会与你一同对最新的 Confluence 站点在 Windows 或者 Linux 平台进行安装和更新。
HoneyMoose
2019/04/29
5340
Confluence 6 在升级之前
                                                                            原
fastapi 用户指南(路径参数、查询参数、请求体)
learn from https://fastapi.tiangolo.com/zh/tutorial/
Michael阿明
2022/01/07
1.9K0
fastapi 用户指南(路径参数、查询参数、请求体)
FastAPI学习-2.路径参数
所以,如果你运行示例并访问 http://127.0.0.1:8000/items/foo,将会看到如下响应:
上海-悠悠
2023/08/22
2820
FastAPI学习-2.路径参数
java——推断日期是否在今天之前
这里说的日期是指字符串的日期格式,如“2014-10-15”,我们要推断这个日期是否在今天之前,网上看到好多推断的方法,都是拿这个日期转换成Date对象 然后与new Date()比較,使用comparetTo() 或者before()方法,事实上这样做都会有点小问题,这样做忽略了一个小时分钟的比較,会出现错误,得不到正确的结果。
全栈程序员站长
2022/07/08
2.2K0
Consul初探-在深交之前先认识
首先,官方介绍是:Consul 是一种服务网格的解决方案,在 Consul 中,提供了服务发现、配置、分段等控制管理平台,Consul 中的每项功能都可以单独使用,也可以一起使用来构建完整的服务网格;在 Consul 内部,有一个简单的代理服务,所以在安装 Consul 后,马上就可以开始使用 Consul ;当然,Consul 也支持集成第三方代理,比如 Envoy。
梁规晓
2019/07/09
5230
Consul初探-在深交之前先认识
FastAPI(4)- 路径参数 Path Parameters
123 传进来的时候是字符串,但 FastAPI 会自动解析转换成 int,如果转换失败就会报错
小菠萝测试笔记
2021/09/26
2.2K0
FastAPI(4)- 路径参数 Path Parameters
sqlmap 源码分析(三)在注入之前
sqlmap是web狗永远也绕不过去的神器,为了能自由的使用sqlmap,阅读源码还是有必要的…
LoRexxar
2023/02/21
1.6K0
MCU在执行main之前做了什么?
本文以Arm Cortex-M为例,介绍了在IAR Embedded Workbench中微控制器(MCU)的启动过程。在MCU复位后,程序计数器(PC)会指向相应的复位向量,并开始执行启动代码(startup code)。如果MCU支持浮点单元(FPU),则在启动过程中,首先会调用__iar_init_vfp来初始化FPU,然后继续执行__iar_program_start。接着,__iar_program_start会调用__cmain函数。在__cmain中,会先调用__low_level_init函数,然后调用__iar_data_init3来进行全局和静态变量的初始化。在__iar_data_init3中,首先会调用__iar_zero_init3来初始化初始值为0的全局和静态变量,随后会调用__iar_copy_init3来初始化初始值为非0的全局和静态变量。最终,在启动过程的最后阶段,会通过调用__call_main来跳转到main函数,从而开始执行主程序。
刘盼
2023/08/22
9890
MCU在执行main之前做了什么?
imread参数_imread函数图片路径matlab
imread的函数原型是:Mat imread( const string& filename, int flags=1 );
全栈程序员站长
2022/11/04
8240
在main方法之前,到底执行了什么?
本人在做接口测试的时候,需要用一个公共类来把所有的执行的代码,然后这个公共类有hsot和hosttype等属性来区分各个测试环境,然后在去不同的地方取用例和请求接口。在给这些属性复制的时候,我是通过不同测试环境新建不同的配置文件,然后执行每个环境的时候让只加载需要测试的环境的配置文件来实现管理测试环境的。中间遇到了一些坑,主要就是对java代码执行循序,特别是在main方法之前的代码执行顺序了解不深入导致的,中间有多个继承关系也有点扰乱了思路。下面分享一下自己这个错误的复现步骤。
FunTester
2019/08/30
7470
HyperSQL 链接参数中文件的路径
如果我们在系统中配置下面的连接参数: spring.datasource.url=jdbc:hsqldb:file:~/db/cwiki-us-jpetstore 我们怎么知道 hsqldb 数据库的存储路径在哪里? 请参考下面的解答: 在 Windows 系统中,如果你登录的用户名为 yhu 的话。 那么这个数据库文件在 :C:\Users\yhu\db 中存储。
HoneyMoose
2019/06/07
1K0
HyperSQL 链接参数中文件的路径
(adsbygoogle = window.adsbygoogle || []).push({});
HoneyMoose
2019/06/11
1.1K0
点击加载更多

相似问题

.htaccess从非www Http子文件夹重定向到www https子文件夹

18

.htaccess子文件夹从http://重定向到www

11

从域www到no-www到子文件夹

22

htaccess重写非www子文件夹到www子文件夹重定向到www根目录

12

htaccess将非www子文件夹重定向到www子文件夹。

32
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档