解决 Java JLabel 网络加载 ImageIcon 消耗内存太大 问题

环境

        Windows10 专业版

        IntelliJ IDEA Community Edition

        jdk15.0.1

程序目的

        设置不同日期标签。

        选定日期标签 -> 在下方列表显示指定日期的若干图片及文字,切换日期刷新列表。

        图片及文字从网络加载。

发现过程

        打开任务管理器观察程序内存使用情况。

        发现内存占用随着切换日期标签不断增大,基本不会减少。

寻找问题

        查看加载图片代码:

public void run() {
			try {
				URL url = new URL(VIDEO_INFO.image);
				ImageIcon imageIcon = new ImageIcon(url);
				imageIcon.setImage(imageIcon.getImage()
						.getScaledInstance(IMAGE_WIDTH, IMAGE_HEIGHT, Image.SCALE_FAST));
				imageLabel.setIcon(imageIcon);
				imageLabel.updateUI();
			} catch (MalformedURLException e) {
				throw new RuntimeException("获取图片失败,url:" + VIDEO_INFO.image + "\n" + e);
			}
		}

        通过网络搜索初步判定为URL缓存复用没有销毁。

        将代码中URL设置为不缓存。

public void run() {
			try {
				URL url = new URL(VIDEO_INFO.image);
				URLConnection urlConnection = url.openConnection();
				urlConnection.setDefaultUseCaches(false);//设置默认不使用缓存
				urlConnection.setUseCaches(false);//设置不使用缓存
				ImageIcon imageIcon = new ImageIcon(url);
				imageIcon.setImage(imageIcon.getImage()
						.getScaledInstance(IMAGE_WIDTH, IMAGE_HEIGHT, Image.SCALE_FAST));
				imageLabel.setIcon(imageIcon);
				imageLabel.updateUI();
			} catch (IOException e) {
				throw new RuntimeException("获取图片失败,url:" + VIDEO_INFO.image + "\n" + e);
			}
		}

        观察任务管理器发现改动无效,内存还是会不断增大。

        遂进入 JLabel.setImage(Icon) 方法查看,无果。

        接着进入 new ImageIcon(URL) 查看,发现端倪

        代码如下:

public ImageIcon (URL location) {
        this(location, location.toExternalForm());
    }

public ImageIcon(URL location, String description) {
        image = Toolkit.getDefaultToolkit().getImage(location);
        if (image == null) {
            return;
        }
        this.location = location;
        this.description = description;
        loadImage(image);
    }

        发现 Image 的获取是通过

    Toolkit.getImage(URL);

        查看文档:

  • public abstract Image getImage(URL url)
    • 返回从指定的URL获取像素数据的图像。 由指定的URL引用的像素数据必须采用以下格式之一:GIF,JPEG或PNG。 底层工具包尝试将具有相同URL的多个请求解析为相同的返回图像。

      由于促进共享Image对象所需的Image可能会继续保持不再使用无限期的图像,因此鼓励开发人员通过使用createImage变体实现自己的缓存图像。 如果指定URL存储的图像数据发生变化,则Image方法返回的Image对象可能仍然包含在先前调用之后从URL中获取的过期信息。 以前加载的图像数据可以通过在返回的Image上调用flush方法手动Image

      此方法首先检查是否安装了安全管理器。 如果是这样,该方法使用url.openConnection()。getPermission()权限调用安全管理器的checkPermission方法,以确保允许对该映像的访问。 为了与1.2之前的安全管理员兼容,如果访问被拒绝使用FilePermissionSocketPermission ,该方法会抛出SecurityException如果相应的1.1型SecurityManager.checkXXX方法也拒绝许可。

      参数

      url - 用于获取像素数据的URL。

      结果

      从指定的URL获取其像素数据的图像。

      异常

      SecurityException - 如果安全管理器存在,并且其checkPermission方法不允许操作。

        终于找到问题!

解决问题

        根据文档所说,使用 Toolkit.createImage(URL) 获取 Image。

private ImageIcon getImage_toolKit_createImage(URL url) {
			Image image = Toolkit.getDefaultToolkit().createImage(url);
			return new ImageIcon(image);
		}

        也可以自己从流中读取字节创建Image。

private ImageIcon getImage_readBytes(URL url) {
			try {
				URLConnection urlConnection = url.openConnection();
				int length = urlConnection.getContentLength();
				InputStream inputStream = urlConnection.getInputStream();
				byte[] bytes = new byte[length];
				byte[] readBytes = new byte[SINGLE_READ_LENGTH];
				int mark = 0;
				int readLength;
				while ((readLength = inputStream.read(readBytes)) != -1) {
					for (int i = 0; i < readLength; i++, mark++) {
						bytes[mark] = readBytes[i];
					}
				}
				inputStream.close();
				return new ImageIcon(bytes);
			} catch (IOException e) {
				return null;
			}
		}

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值