使用 2 个 BufferedImage 瞬态字段序列化 Obj,不会读取第二个图像

Serialize Obj with 2 BufferedImage transient fields, second image won't be read

在解释我的事情之前,我想提一下我还没有尝试搜索任何替代解决方案(我非常有信心会找到一些东西)。我很好奇为什么会这样。

所以...我有一个具有 2 个瞬态缓冲图像字段的对象(第一个图像和第二个图像,如下例所示)。

由于 bufferedimage 没有实现可序列化,一种序列化它们的方法(我在 SO 中找到)是 this。我做了完全相同的事情,第一张图片的一切都很完美。 (保存成功,加载也正常)

但是,关于第二张图片,有一个我无法解释的问题。我序列化我的对象没有任何问题,但是当我尝试加载它时,第二个图像由于某种原因被读取为 null。我的代码有什么遗漏吗?

我用 quick swing 应用程序展示了我的问题。

  1. load1 按钮将加载第一张图片并将其设置为对象。
  2. load2 按钮对第二张图片做同样的事情
  3. 保存按钮序列化对象并保存在本地
  4. 显示按钮将在任何图像为空时显示

所以,我启动应用程序。单击加载 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.

中描述了序列化方法签名的详细信息