如何序列化包含Image对象的地图?

时间:2019-08-27 06:27:56

标签: javafx serialization

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

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

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

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

我总是收到此异常:java.io.NotSerializableException:javafx.scene.image.Image

有什么方法可以使我的图片可序列化?

1 个答案:

答案 0 :(得分:2)

您需要自定义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());
    }
    

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

  2. 通过ImagePixelReader的像素数据进行序列化。反序列化时,您将使用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(例如缓存)。要么允许延迟加载Image实例中的Picture

    重点是避免在不需要时加载Image。例如,如果您的UI的一部分仅希望按名称显示可用图片的列表,该怎么办?如果您有成千上万的{{​​1}},那么您将希望避免加载成千上万的{{​​1}},因为这样很容易耗尽内存(甚至只有几十张图像)。

    < / li>