我正在创建一个图片库,其中包含相册,每个相册包含图片。这样的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
有什么方法可以使我的图片可序列化?
答案 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
的序列化,您至少有三个选择。
序列化图像的位置。
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()
方法。
通过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
。
序列化实际的图像文件(以下原因,我不推荐使用此文件)。
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}},因为这样很容易耗尽内存(甚至只有几十张图像)。