以下でSerializableインターフェイスの利用方法を検証する。
これは、java.io.Serializableを実装したクラスのインスタンスであれば、
ObjectOutputStreamでインスタンスを簡単にファイルへ出力できて、
ObjectInputStreamで簡単にファイルからインスタンスを復元できる。
この時、インスタンスをStream(1バイトずつ順番に読み書きできるようにする)に変換することから
シリアライズと呼ばれます。
後半で、BufferedImageをシリアライズする検討した時の情報を示します。
package application;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class A implements Serializable {//検証用のシリアライズ化したクラス
int i;
double d;
String s;
int [] buf;
}
public class SerialTest {
public static void main(String[] args) throws Exception{
//インスタンスを生成して初期設定
A a1 = new A();
a1.i = 123;
a1.d = 0.987;
a1.s = "ABC";
a1.buf = new int []{1,2,3};
//インスタンスをファイル化
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("TA.bin"));
os.writeObject(a1);
os.close();
//インスタンスを変数a2に復元
ObjectInputStream is = new ObjectInputStream(new FileInputStream("TA.bin"));
A a2 = (A)is.readObject();
System.out.println(a2.i);//ここから検証
System.out.println(a2.d);
System.out.println(a2.s);
System.out.println(a2.buf.length);
}
}
実行結果を示す。(簡単にファイル化できるのが分かりました。)
123 0.987 ABC 3このファイル内容を試しにバイナリでダンプしてみました。
00000000: AC ED 00 05 73 72 00 0D 61 70 70 6C 69 63 61 74 |....sr..applicat
00000010: 69 6F 6E 2E 41 16 66 C6 34 6F CF 28 FF 02 00 04 |ion.A.f.4o.(....
00000020: 44 00 01 64 49 00 01 69 5B 00 03 62 75 66 74 00 |D..dI..i[..buft.
00000030: 02 5B 49 4C 00 01 73 74 00 12 4C 6A 61 76 61 2F |.[IL..st..Ljava/
00000040: 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 78 70 3F EF |lang/String;xp?.
00000050: 95 81 06 24 DD 2F 00 00 00 7B 75 72 00 02 5B 49 |...$./...{ur..[I
00000060: 4D BA 60 26 76 EA B2 A5 02 00 00 78 70 00 00 00 |M.`&v......xp...
00000070: 03 00 00 00 01 00 00 00 02 00 00 00 03 74 00 03 |.............t..
00000080: 41 42
class A implements Serializable {//検証用のシリアライズ化したクラス
int i;
double d;
String s;
int [] buf;
BufferedImage img;
}
そして、またmainの最後に、次の確認用表示を追加して実行した。123 0.987 ABC 3 nullnullが伝達されて、一見して成功しているように見えるが・・ そこで、実際に、画像を設定して、次のようにmainで設定を追加して検証した。
public class SerialTest {
public static void main(String[] args) throws Exception{
//インスタンスを生成して初期設定
A a1 = new A();
a1.i = 123;
a1.d = 0.987;
a1.s = "ABC";
a1.buf = new int []{1,2,3};
a1.img = new BufferedImage(50,50,BufferedImage.TYPE_4BYTE_ABGR);
Graphics g = a1.img.getGraphics();
g.setColor(Color.blue);
g.fillOval(0, 0, 50, 50);
//インスタンスをファイル化
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("TA.bin"));
os.writeObject(a1);
os.close();
//インスタンスを変数a2に復元
ObjectInputStream is = new ObjectInputStream(new FileInputStream("TA.bin"));
A a2 = (A)is.readObject();
System.out.println(a2.i);//ここから検証
System.out.println(a2.d);
System.out.println(a2.s);
System.out.println(a2.buf.length);
System.out.println(a2.img);
}
}
}
結果は、次の実行エラーで失敗です。
Exception in thread "main" java.io.NotSerializableException: java.awt.image.BufferedImage at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) ・・・・省略この実行エラーは「Serializableされていないインスタンス変数があるため!」ということでしょう。
class A implements Serializable {//検証用のシリアライズ化したクラス
int i;
double d;
String s;
int [] buf;
transient BufferedImage img; //直列化不可フィールドの指定
public static int [] getArrayByImage(BufferedImage img,int w, int h){
BufferedImage subImg = img.getSubimage(0, 0, w, h);
WritableRaster raster = subImg.getRaster();
int size = raster.getNumBands() * w * h;
// System.out.println("getNumBands:" + size);
int [] buf = new int[ size ];
raster.getPixels(0, 0, w, h, buf);
return buf;
}
static BufferedImage getImageByArray(int [] buf,int w, int h){
BufferedImage img = new BufferedImage(w,h,BufferedImage.TYPE_4BYTE_ABGR);
WritableRaster raster = img.getRaster();
raster.setPixels(0, 0, w, h, buf);
return img;
}
}
public class SerialTest {
public static void main(String[] args) throws Exception{
//インスタンスを生成して初期設定
A a1 = new A();
a1.i = 123;
a1.d = 0.987;
a1.s = "ABC";
a1.img = new BufferedImage(w,h,BufferedImage.TYPE_4BYTE_ABGR);
Graphics g = a1.img.getGraphics();
g.setColor(Color.blue);
g.fillOval(0, 0, w, h);
a1.buf = A.getArrayByImage(a1.img, 50, 50);
//インスタンスをファイル化
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("TA.bin"));
os.writeObject(a1);
os.close();
//インスタンスを変数a2に復元
ObjectInputStream is = new ObjectInputStream(new FileInputStream("TA.bin"));
A a2 = (A)is.readObject();
System.out.println(a2.i);//ここから検証
System.out.println(a2.d);
System.out.println(a2.s);
System.out.println(a2.buf.length);
a2.img = a2.getImageByArray(a2.buf,50,50);
ImageIO.write(a2.img, "png", new File("T.png"));//検証でファイル保存
}
}
class A implements Serializable {//検証用のシリアライズ化したクラス
int i;
double d;
String s;
int [] buf;//ここに画像イメージを記憶して使う。
BufferedImage img; // writeObject、readObjectを作って、defaultWriteObject()やdefaultReadObject()使わない場合、直列化不可フィールドのtransient 指定は、必要ない。
public static int [] getArrayByImage(BufferedImage img,int w, int h){
BufferedImage subImg = img.getSubimage(0, 0, w, h);
WritableRaster raster = subImg.getRaster();
int size = raster.getNumBands() * w * h;
// System.out.println("getNumBands:" + size);
int [] buf = new int[ size ];
raster.getPixels(0, 0, w, h, buf);
return buf;
}
static BufferedImage getImageByArray(int [] buf,int w, int h){
BufferedImage img = new BufferedImage(w,h,BufferedImage.TYPE_4BYTE_ABGR);
WritableRaster raster = img.getRaster();
raster.setPixels(0, 0, w, h, buf);
return img;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.putFields();
stream.writeFields();//親クラスの情報出力
//上記この2行の代わりに、stream.defaultWriteObject();を使う方法がある。
// その場合、
stream.writeInt(this.i);
stream.writeDouble(this.d);
stream.writeUTF(this.s);
this.buf = A.getArrayByImage(this.img, 50,50);
stream.writeInt(this.buf.length);
for(int i=0; i < this.buf.length; i++){
stream.writeInt(this.buf[i]);
}
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.readFields();
// 上記代わりに、stream.defaultReadObject();を使う方法がある。
this.i = stream.readInt();
this.d = stream.readDouble();
this.s = stream.readUTF();
this.buf = new int[stream.readInt()];
for(int i=0; i < this.buf.length; i++){
this.buf[i] = stream.readInt();
}
this.img = A.getImageByArray(this.buf,50,50);
}
}
public class SerialTest {
public static void main(String[] args) throws Exception{
//インスタンスを生成して初期設定
A a1 = new A();
a1.i = 123;
a1.d = 0.987;
a1.s = "ABC";
a1.img = new BufferedImage(50,50,BufferedImage.TYPE_4BYTE_ABGR);
Graphics g = a1.img.getGraphics();
g.setColor(Color.blue);
g.fillOval(0, 0, 50, 50);
//インスタンスをファイル化
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("TA.bin"));
os.writeObject(a1);
os.close();
//インスタンスを変数a2に復元
ObjectInputStream is = new ObjectInputStream(new FileInputStream("TA.bin"));
A a2 = (A)is.readObject();
System.out.println(a2.i);//ここから検証
System.out.println(a2.d);
System.out.println(a2.s);
System.out.println(a2.buf.length);
ImageIO.write(a2.img, "png", new File("T.png"));//検証でファイル保存
}
}
package application;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyBufferedImage implements Serializable{
transient int [] buf; // この変数は、ローカル変数にした方がよい。改良すべき点です。
transient BufferedImage img; // 直列化不可フィールドの指定
static int [] getArrayByImage(BufferedImage img){
int w = img.getWidth();
int h = img.getHeight();
int []buf = new int[ w * h ];
img.getRGB(0, 0, w, h, buf, 0, w);
return buf;
}
static BufferedImage getImageByArray(int [] buf, int width){
int height = buf.length / width;
BufferedImage img = new BufferedImage(width,height,BufferedImage.TYPE_4BYTE_ABGR);
img.setRGB(0, 0, width, height, buf, 0, width);
return img;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
//stream.putFields();
//stream.writeFields();//親クラスの情報出力
//上記この2行の代わりに、
stream.defaultWriteObject();//を使った。
int width = this.img.getWidth();
stream.writeInt(width);
this.buf = getArrayByImage(this.img);
stream.writeInt(this.buf.length);
for(int i=0; i < this.buf.使ったlength; i++){
stream.writeInt(this.buf[i]);
}
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
//stream.readFields();
// 上記代わりに、
stream.defaultReadObject();//を使った。
int width = stream.readInt();
this.buf = new int[stream.readInt()];
for(int i=0; i < this.buf.length; i++){
this.buf[i] = stream.readInt();
}
this.img = getImageByArray(this.buf, width);
}
}
なお、getImageByArrayやgetImageByArrayの作り方は、イメージサイズが変わっても使えるように変更しています。
(WritableRaster を使わない方法に変更しています。)
package application;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
class A implements Serializable {//検証用のシリアライズ化したクラス
int i;
double d;
String s;
MyBufferedImage img; // シリアライズ化したクラスのインスタンス変数
}
public class SerialTest {
public static void main(String[] args) throws Exception{
//インスタンスを生成して初期設定
A a1 = new A();
a1.i = 123;
a1.d = 0.987;
a1.s = "ABC";
a1.img = new MyBufferedImage();
//イメージに描画
a1.img.img = new BufferedImage(50,50,BufferedImage.TYPE_4BYTE_ABGR);
Graphics g = a1.img.img.getGraphics();
g.setColor(Color.blue);
g.fillOval(0, 0, 50, 50);
//インスタンスをファイル化
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("TA.bin"));
os.writeObject(a1);
os.close();
//インスタンスを変数a2に復元
ObjectInputStream is = new ObjectInputStream(new FileInputStream("TA.bin"));
A a2 = (A)is.readObject();
System.out.println(a2.i);//ここから検証
System.out.println(a2.d);
System.out.println(a2.s);
System.out.println(a2.img.buf.length);
ImageIO.write(a2.img.img, "png", new File("T.png"));//検証でファイル保存
}
}
以上で、シリアライズしたインスタンス変数で構成されるクラスのシリアライズ化は、とても簡単になりました。
さて、MyBufferedImageのクラス定義は、streamde.faultWriteObject()やstream.defaultReadObject()を使っているので、
これにより、シリアラスされたインスタンス変数の保存と読み込みは、自動的に行われます。
public class MyBufferedImage implements Serializable{
private static final long serialVersionUID = 2162873589398154617L;
int [] buf;
int width;
transient public BufferedImage img; // 直列化不可フィールドの指定
static int [] getArrayByImage(BufferedImage img){
int w = img.getWidth();
int h = img.getHeight();
int []buf = new int[ w * h ];
img.getRGB(0, 0, w, h, buf, 0, w);
return buf;
}
static BufferedImage getImageByArray(int [] buf, int width){
int height = buf.length / width;
BufferedImage img = new BufferedImage(width,height,BufferedImage.TYPE_4BYTE_ABGR);
img.setRGB(0, 0, width, height, buf, 0, width);
return img;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
this.width = this.img.getWidth();
this.buf = getArrayByImage(this.img);
stream.defaultWriteObject();//を使って、this.widthとthis.bufを保存
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();//を使って、this.widthとthis.bufを読み取り
this.img = getImageByArray(this.buf, width);
}
}
コードは簡単になりました。しかし、int [] buf や int width; のインスタンス変数の情報は、本来imgにある情報なので必要ない記憶情報です。
package application;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class MyBufferedImage2 extends BufferedImage implements Externalizable{
private static final long serialVersionUID = 538555975269710360L;
public static int WIDTH =100;
public static int HEIGHT =100;
public MyBufferedImage2(){//引数無しのコンストラクタが必要
super(WIDTH, HEIGHT, TYPE_4BYTE_ABGR);
}
public static int [] getArrayByImage(BufferedImage img,int w, int h){
BufferedImage subImg = img.getSubimage(0, 0, w, h);
WritableRaster raster = subImg.getRaster();
int size = raster.getNumBands() * w * h;
// System.out.println("getNumBands:" + size);
int [] buf = new int[ size ];
raster.getPixels(0, 0, w, h, buf);
return buf;
}
public static void setImageByArray(BufferedImage img, int [] buf, int w, int h){
WritableRaster raster = img.getRaster();
raster.setPixels(0, 0, w, h, buf);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
int w = this.getWidth();
int h = this.getHeight();
out.writeInt(w);
out.writeInt(h);
int [] buf = getArrayByImage(this,w, h);
out.writeInt(buf.length);
for(int i=0; i < buf.length; i++){
out.writeInt(buf[i]);
// System.out.println(i + ":" +buf[i]);
}
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int w = in.readInt();
int h = in.readInt();
int size = in.readInt();
int [] buf = new int[size];
for(int i=0; i < buf.length; i++){
buf[i] = in.readInt();
System.out.println(i + ":" +buf[i]);
}
setImageByArray(this, buf, w, h);
}
}
以下で、これを使って検証する例を示す。
package application;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.awt.Color;
import java.awt.Graphics;
import java.io.File;
import javax.imageio.ImageIO;
class A implements Serializable {//検証用のシリアライズ化したクラス
int i;
double d;
String s;
MyBufferedImage2 img; // 直列化不可フィールドの指定
}
public class SerialTest {
public static void main(String[] args) throws Exception{
//インスタンスを生成して初期設定
A a1 = new A();
a1.i = 123;
a1.d = 0.987;
a1.s = "ABC";
MyBufferedImage2.HEIGHT = 50;
a1.img = new MyBufferedImage2();
//イメージに描画
Graphics g = a1.img.getGraphics();
g.setColor(Color.yellow);
g.fillRect(0, 0, 50, 50);
g.setColor(Color.blue);
g.fillOval(0, 0, 50, 50);
//インスタンスをファイル化
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("TA.bin"));
os.writeObject(a1);
os.close();
//インスタンスを変数a2に復元
ObjectInputStream is = new ObjectInputStream(new FileInputStream("TA.bin"));
A a2 = (A)is.readObject();
System.out.println(a2.i);//ここから検証
System.out.println(a2.d);
System.out.println(a2.s);
// int []buf = MyBufferedImage.getArrayByImage(a1.img, 100, 100);
// MyBufferedImage.setImageByArray(a2.img,buf, 100, 100);
ImageIO.write(a2.img, "png", new File("T.png"));//検証でファイル保存
}
}