首页 > 解决方案 > 如何序列化包含 Image 对象的地图?

问题描述

我正在创建一个图片库,其中包含相册,每个相册都包含图片。这个 irems 像这样存储在 HashMap 中

HashMap<Album, ArrayList<Picture>> albums=new HashMap<>();

当尝试序列化地图时,问题就开始了,因为每个 Picture 对象都包含一个 Image 对象,所以我可以选择这个 Image 并为我的应用程序更轻松地创建一个 ImageView,Picture 构造函数如下所示:

public Picture(String name,String place, String description, Image image)

我总是得到这个异常:java.io.NotSerializableException: javafx.scene.image.Image

有没有办法让我的图片可序列化?

标签: javafxserialization

解决方案


您需要自定义Picture. 要自定义对象的序列化,请使用以下两种方法:

  • void readObject(ObjectInputStream) throws ClassNotFoundException, IOException
  • void writeObject(ObjectOutputStream) throws IOException

这些方法可以有任何访问修饰符,但通常是 (?) private

如果您有以下课程:

public class Picture implements Serializable {

    private final String name;
    private final String place;
    private final String description;
    private transient Image image;

    public Picture(String name, String place, String description, Image image) {
        this.name = name;
        this.place = place;
        this.description = description;
        this.image = image;
    }

    public String getName() {
        return name;
    }

    public String getPlace() {
        return place;
    }

    public String getDescription() {
        return description;
    }

    public Image getImage() {
        return image;
    }

}

关于Image.

  1. 序列化图像的位置。

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
        in.defaultReadObject();
        String url = (String) in.readObject();
        if (url != null) {
            image = new Image(url);
        }
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObjet();
        out.writeObject(image == null ? null : image.getUrl());
    }
    

    Image#getUrl()方法是在 JavaFX 9 中添加的。

  2. Image序列化过孔的像素数据PixelReader。反序列化时,您将使用PixelWriter.

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
        in.defaultReadObject();
        if (in.readBoolean()) {
            int w = in.readInt();
            int h = in.readInt();
    
            byte[] b = new byte[w * h * 4];
            in.readFully(b);
    
            WritableImage wImage = new WritableImage(w, h);
            wImage.getPixelWriter().setPixels(0, 0, w, h, PixelFormat.getByteBgraInstance(), b, 0, w * 4);
    
            image = wImage;
        }
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeBoolean(image != null);
        if (image != null) {
            int w = (int) image.getWidth();
            int h = (int) image.getHeight();
    
            byte[] b = new byte[w * h * 4];
            image.getPixelReader().getPixels(0, 0, w, h, PixelFormat.getByteBgraInstance(), b, 0, w * 4);
    
            out.writeInt(w);
            out.writeInt(h);
            out.write(b);
        }
    }
    

    警告:以这种方式序列化像素数据是以未压缩格式保存图像。图像可能非常大,使用此方法时可能会给您带来问题。

    这取决于PixelReader是否可用,但情况并非总是如此。如果您阅读文档,Image#getPixelReader()您会看到(强调我的):

    PixelReader如果图像可读,则此方法返回提供读取图像像素的访问权限。如果此方法返回 null,则此图像此时不支持读取。如果图像正在从源加载并且仍然不完整(进度仍然 <1.0)或出现错误,则此方法将返回 null 。对于某些格式不支持读取和写入像素的图像,此方法也可能返回 null 。

    在静态加载和错误之外,一些非详尽的测试表明动画 GIF 没有关联的PixelReader.

  3. 序列化实际的图像文件(我不推荐这个,原因如下)。

    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
        in.defaultReadObject();
        if (in.readBoolean()) {
            byte[] bytes = new byte[in.readInt()];
            in.readFully(bytes);
            image = new Image(new ByteArrayInputStream(bytes));
        }
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        out.writeBoolean(image != null);
        if (image != null) {
            byte[] bytes;
            try (InputStream is = new URL(image.getUrl()).openStream()) {
                bytes = is.readAllBytes();
            }
            out.writeInt(bytes.length);
            out.write(bytes);
        }
    }
    

    这假设Image不是从资源加载的(或者至少 URL 有一个方案)。

    但是,正如我所说,我不推荐这种方法。一方面,它只允许您对Picture实例进行一次序列化,因为反序列化后原始 URL 会丢失。您可以通过单独存储位置来解决此问题,但您也可以使用选项#1。还有一个事实是你InputStream在序列化过程中打开并读取它;Picture对于序列化实例的开发人员来说,这可能是非常出乎意料的行为。


一些注意事项:

  • 上面的代码可能还有优化的空间。

  • 选项 #1 和 #3 不考虑请求的图像宽度和高度。这可能会导致反序列化后内存中的图像更大。您可以修改代码来解决此问题。

  • 你的Picture课看起来像一个模型课。如果是这种情况,最好将图像的位置简单地存储在字段中而不是Image本身中(这也可以使自定义序列化变得不必要);然后让其他代码负责Image根据存储在Picture. 要么,要么允许ImagePicture实例中延迟加载。

    关键是避免Image在不需要时加载 s 。例如,如果您的部分 UI 只想按名称显示可用图片列表怎么办。如果您有数千个Pictures,您将希望避免加载数千个Images,因为您很容易耗尽内存(即使只有几十张图像)。


推荐阅读