使用 2 个 BufferedImage 瞬态字段序列化 Obj,不会读取第二个图像
Serialize Obj with 2 BufferedImage transient fields, second image won't be read
在解释我的事情之前,我想提一下我还没有尝试搜索任何替代解决方案(我非常有信心会找到一些东西)。我很好奇为什么会这样。
所以...我有一个具有 2 个瞬态缓冲图像字段的对象(第一个图像和第二个图像,如下例所示)。
由于 bufferedimage 没有实现可序列化,一种序列化它们的方法(我在 SO 中找到)是 this。我做了完全相同的事情,第一张图片的一切都很完美。 (保存成功,加载也正常)
但是,关于第二张图片,有一个我无法解释的问题。我序列化我的对象没有任何问题,但是当我尝试加载它时,第二个图像由于某种原因被读取为 null。我的代码有什么遗漏吗?
我用 quick swing 应用程序展示了我的问题。
- load1 按钮将加载第一张图片并将其设置为对象。
- load2 按钮对第二张图片做同样的事情
- 保存按钮序列化对象并保存在本地
- 显示按钮将在任何图像为空时显示
所以,我启动应用程序。单击加载 1 和加载 2。点击保存。单击 show = none 其中为空。关闭应用程序。
我再次启动应用程序,它在启动时加载了对象。我点击显示,image1 不为空,但 image 2 为空。
为什么会这样?
我该怎么做才能使这种方式起作用?
您会建议什么替代解决方案?
p.s:我想避免将图像获取到 base64 或 byte[] 并序列化它们。
人class(名字没有意义我只是从以前的测试中得到的):
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.imageio.ImageIO;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private transient BufferedImage firstImage;
private transient BufferedImage secondImage;
public Person() {
}
private void writeObject(ObjectOutputStream out) {
try {
out.defaultWriteObject();
if (firstImage != null)
{
ImageIO.write(firstImage, "png", out);
System.out.println("First image saved.");
}
if (secondImage != null) {
ImageIO.write(secondImage, "png", out);
System.out.println("Second image saved.");
}
System.out.println("-------------------------");
} catch (IOException e) {
e.printStackTrace();
}
}
private void readObject(ObjectInputStream in) {
try {
in.defaultReadObject();
firstImage = ImageIO.read(in);
secondImage = ImageIO.read(in);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Person loadPerson() {
String path = "C://Users//George//Desktop//person.tmp";
File file = new File(path);
Person p = new Person();
if (!file.exists())
return p;
try (FileInputStream fis = new FileInputStream(path); ObjectInputStream ois = new ObjectInputStream(fis);) {
p = (Person) ois.readObject();
ois.close();
fis.close();
} catch (IOException | ClassNotFoundException e1) {
e1.printStackTrace();
}
return p;
}
public void savePerson() {
String path = "C://Users//George//Desktop//person.tmp";
try (FileOutputStream fos = new FileOutputStream(path); ObjectOutputStream oos = new ObjectOutputStream(fos);) {
oos.writeObject(this);
oos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public BufferedImage getSecondImage() {
return secondImage;
}
public void setSecondImage(BufferedImage secondImage) {
this.secondImage = secondImage;
}
public BufferedImage getFirstImage() {
return firstImage;
}
public void setFirstImage(BufferedImage firstImage) {
this.firstImage = firstImage;
}
}
应用程序:
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UnsupportedLookAndFeelException;
@SuppressWarnings("serial")
public class Test extends JPanel {
private JFrame f;
private Person person;
public Test() {
JButton load1 = new JButton("load img1");
load1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
BufferedImage tmp = ImageIO.read(new File("C://Users//George//Desktop//pencil.png"));
person.setFirstImage(tmp);
tmp.flush();
tmp = null;
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
add(load1);
JButton load2 = new JButton("load img2");
load2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
BufferedImage tmp = ImageIO.read(new File("C://Users//George//Desktop//map.png"));
person.setSecondImage(tmp);
tmp.flush();
tmp = null;
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
add(load2);
JButton save = new JButton("save");
save.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
person.savePerson();
}
});
add(save);
JButton show = new JButton("show which is null");
show.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (person.getFirstImage() == null)
System.out.println("first image null");
else
System.out.println("first image not null");
if (person.getSecondImage() == null)
System.out.println("second image null");
else
System.out.println("second image not null");
System.out.println("---------------------");
}
});
add(show);
}
private void display() {
person = Person.loadPerson();
f = new JFrame("Buffered images");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.setSize(new Dimension(300, 300));
// f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new Test().display();
}
});
}
}
documentation for ImageIO.read 声明 InputStream 参数包装在 ImageInputStream 中。
ImageInputStream 与 InputStream 不同。我不认为你可以安全地假设它只读到它需要的地方;它可能会进一步读取,用于缓存目的,或者可能是为了从其 length() 方法中了解 return 的内容。
换句话说,在读取第一张图片后,您的 InputStream 可能没有指向第二张图片开始的确切字节。
解决这个问题的一种方法是写入字节数组,而不是直接写入图像数据。缺点是它不可扩展;如果您有非常大的图像,将它们保存在内存中将是一个性能问题。
public class Person implements Serializable {
// ...
private void writeObject(ObjectOutputStream out)
throws IOException {
out.defaultWriteObject();
if (firstImage != null) {
ByteArrayOutputStream b = new ByteArrayOutputStream();
ImageIO.write(firstImage, "png", b);
out.writeInt(b.size());
b.writeTo(out);
System.out.println("First image saved.");
} else {
out.writeInt(0);
}
if (secondImage != null) {
ByteArrayOutputStream b = new ByteArrayOutputStream();
ImageIO.write(secondImage, "png", b);
out.writeInt(b.size());
b.writeTo(out);
System.out.println("Second image saved.");
} else {
out.writeInt(0);
}
}
private void readObject(ObjectInputStream in)
throws IOException,
ClassNotFoundException {
in.defaultReadObject();
int length = in.readInt();
if (length > 0) {
byte[] bytes = new byte[length];
in.readFully(bytes);
firstImage = ImageIO.read(new ByteArrayInputStream(bytes));
}
length = in.readInt();
if (length > 0) {
byte[] bytes = new byte[length];
in.readFully(bytes);
secondImage = ImageIO.read(new ByteArrayInputStream(bytes));
}
}
}
附带说明,IOException 和 ClassNotFoundException 不应在序列化方法中捕获。它们属于方法声明的 throws
子句。毕竟,您不希望 class 假装序列化成功,但实际上并没有成功。 documentation for Serializable.
中描述了序列化方法签名的详细信息
在解释我的事情之前,我想提一下我还没有尝试搜索任何替代解决方案(我非常有信心会找到一些东西)。我很好奇为什么会这样。
所以...我有一个具有 2 个瞬态缓冲图像字段的对象(第一个图像和第二个图像,如下例所示)。
由于 bufferedimage 没有实现可序列化,一种序列化它们的方法(我在 SO 中找到)是 this。我做了完全相同的事情,第一张图片的一切都很完美。 (保存成功,加载也正常)
但是,关于第二张图片,有一个我无法解释的问题。我序列化我的对象没有任何问题,但是当我尝试加载它时,第二个图像由于某种原因被读取为 null。我的代码有什么遗漏吗?
我用 quick swing 应用程序展示了我的问题。
- load1 按钮将加载第一张图片并将其设置为对象。
- load2 按钮对第二张图片做同样的事情
- 保存按钮序列化对象并保存在本地
- 显示按钮将在任何图像为空时显示
所以,我启动应用程序。单击加载 1 和加载 2。点击保存。单击 show = none 其中为空。关闭应用程序。
我再次启动应用程序,它在启动时加载了对象。我点击显示,image1 不为空,但 image 2 为空。
为什么会这样? 我该怎么做才能使这种方式起作用? 您会建议什么替代解决方案?
p.s:我想避免将图像获取到 base64 或 byte[] 并序列化它们。
人class(名字没有意义我只是从以前的测试中得到的):
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.imageio.ImageIO;
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private transient BufferedImage firstImage;
private transient BufferedImage secondImage;
public Person() {
}
private void writeObject(ObjectOutputStream out) {
try {
out.defaultWriteObject();
if (firstImage != null)
{
ImageIO.write(firstImage, "png", out);
System.out.println("First image saved.");
}
if (secondImage != null) {
ImageIO.write(secondImage, "png", out);
System.out.println("Second image saved.");
}
System.out.println("-------------------------");
} catch (IOException e) {
e.printStackTrace();
}
}
private void readObject(ObjectInputStream in) {
try {
in.defaultReadObject();
firstImage = ImageIO.read(in);
secondImage = ImageIO.read(in);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Person loadPerson() {
String path = "C://Users//George//Desktop//person.tmp";
File file = new File(path);
Person p = new Person();
if (!file.exists())
return p;
try (FileInputStream fis = new FileInputStream(path); ObjectInputStream ois = new ObjectInputStream(fis);) {
p = (Person) ois.readObject();
ois.close();
fis.close();
} catch (IOException | ClassNotFoundException e1) {
e1.printStackTrace();
}
return p;
}
public void savePerson() {
String path = "C://Users//George//Desktop//person.tmp";
try (FileOutputStream fos = new FileOutputStream(path); ObjectOutputStream oos = new ObjectOutputStream(fos);) {
oos.writeObject(this);
oos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public BufferedImage getSecondImage() {
return secondImage;
}
public void setSecondImage(BufferedImage secondImage) {
this.secondImage = secondImage;
}
public BufferedImage getFirstImage() {
return firstImage;
}
public void setFirstImage(BufferedImage firstImage) {
this.firstImage = firstImage;
}
}
应用程序:
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UnsupportedLookAndFeelException;
@SuppressWarnings("serial")
public class Test extends JPanel {
private JFrame f;
private Person person;
public Test() {
JButton load1 = new JButton("load img1");
load1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
BufferedImage tmp = ImageIO.read(new File("C://Users//George//Desktop//pencil.png"));
person.setFirstImage(tmp);
tmp.flush();
tmp = null;
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
add(load1);
JButton load2 = new JButton("load img2");
load2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
BufferedImage tmp = ImageIO.read(new File("C://Users//George//Desktop//map.png"));
person.setSecondImage(tmp);
tmp.flush();
tmp = null;
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
add(load2);
JButton save = new JButton("save");
save.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
person.savePerson();
}
});
add(save);
JButton show = new JButton("show which is null");
show.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (person.getFirstImage() == null)
System.out.println("first image null");
else
System.out.println("first image not null");
if (person.getSecondImage() == null)
System.out.println("second image null");
else
System.out.println("second image not null");
System.out.println("---------------------");
}
});
add(show);
}
private void display() {
person = Person.loadPerson();
f = new JFrame("Buffered images");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.setSize(new Dimension(300, 300));
// f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new Test().display();
}
});
}
}
documentation for ImageIO.read 声明 InputStream 参数包装在 ImageInputStream 中。
ImageInputStream 与 InputStream 不同。我不认为你可以安全地假设它只读到它需要的地方;它可能会进一步读取,用于缓存目的,或者可能是为了从其 length() 方法中了解 return 的内容。
换句话说,在读取第一张图片后,您的 InputStream 可能没有指向第二张图片开始的确切字节。
解决这个问题的一种方法是写入字节数组,而不是直接写入图像数据。缺点是它不可扩展;如果您有非常大的图像,将它们保存在内存中将是一个性能问题。
public class Person implements Serializable {
// ...
private void writeObject(ObjectOutputStream out)
throws IOException {
out.defaultWriteObject();
if (firstImage != null) {
ByteArrayOutputStream b = new ByteArrayOutputStream();
ImageIO.write(firstImage, "png", b);
out.writeInt(b.size());
b.writeTo(out);
System.out.println("First image saved.");
} else {
out.writeInt(0);
}
if (secondImage != null) {
ByteArrayOutputStream b = new ByteArrayOutputStream();
ImageIO.write(secondImage, "png", b);
out.writeInt(b.size());
b.writeTo(out);
System.out.println("Second image saved.");
} else {
out.writeInt(0);
}
}
private void readObject(ObjectInputStream in)
throws IOException,
ClassNotFoundException {
in.defaultReadObject();
int length = in.readInt();
if (length > 0) {
byte[] bytes = new byte[length];
in.readFully(bytes);
firstImage = ImageIO.read(new ByteArrayInputStream(bytes));
}
length = in.readInt();
if (length > 0) {
byte[] bytes = new byte[length];
in.readFully(bytes);
secondImage = ImageIO.read(new ByteArrayInputStream(bytes));
}
}
}
附带说明,IOException 和 ClassNotFoundException 不应在序列化方法中捕获。它们属于方法声明的 throws
子句。毕竟,您不希望 class 假装序列化成功,但实际上并没有成功。 documentation for Serializable.