Discussion:
[心得] 序列化的小細節
(时间太久无法回复)
Pao
2006-10-25 07:16:21 UTC
Permalink
在物件導向當中,如何將物件裡的資料簡易方便的保存或傳輸,
而不用繁雜的訂定格式、解析資料等,不是個簡單的問題。

所幸在Java的世界裡有序列化這方便好用的功能。
輕易的將物件序列化後就可透過各種資料流保存或傳輸。
相信大家也對序列化都很熟悉了。

這邊想提的是最近我寫程式用到序列化時,忽略掉的細節。
「多個有互相參照的序列化物件儲存問題」

假設今天有兩個序列化的類別如下:
public class A implements Serializable
{
public B b;
}

public class B implements Serializable
{
public A a;
}

兩個物件互相參照:
A a = new A();
B b = new B();
a.b = b;
b.a = a;

想當然爾:
System.out.println(a.b == b);
System.out.println(a.b.a == a);
System.out.println(b.a == a);
System.out.println(b.a.b == b);
印出來的是:
true
true
true
true

分別寫入兩個檔案:
FileOutputStream fos = new FileOutputStream("a.object");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(a);
oos.close();

fos = new FileOutputStream("b.object");
oos = new ObjectOutputStream(fos);
oos.writeObject(b);
oos.close();

嗯,沒錯誤發生。
可見互相參考甚至是循環參考對物件的序列化是沒問題的。

在別的程式裡,讀出那兩個物件來:
FileInputStream fis = new FileInputStream("a.object");
ObjectInputStream ois = new ObjectInputStream(fis);
A a = (A)ois.readObject();

fis = new FileInputStream("b.object");
ois = new ObjectInputStream(fis);
B b = (B)ois.readObject();

此時:
System.out.println(a.b == b);
System.out.println(a.b.a == a);
System.out.println(b.a == a);
System.out.println(b.a.b == b);
印出來的是:
false
true
false
true

哎呀!參照居然變了!
那改成都在同一個資料流呢?

物件輸出資料流用同一個再寫一次:
A a = new A();
B b = new B();
a.b = b;
b.a = a;

FileOutputStream fos = new FileOutputStream("objects");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(a);
oos.writeObject(b);
oos.close();

在別的程式裡,讀出那兩個物件來:
FileInputStream fis = new FileInputStream("objects");
ObjectInputStream ois = new ObjectInputStream(fis);
A a = (A)ois.readObject();
B b = (B)ois.readObject();
ois.close();

此時:
System.out.println(a.b == b);
System.out.println(a.b.a == a);
System.out.println(b.a == a);
System.out.println(b.a.b == b);
印出來的是:
true
true
true
true

終於是原先要的結果了...XD

我想,在我們寫程式寫的忙得時候,
往往會忽略一些小細節。
所以發這篇提醒自己也提醒大家,
希望會有所幫助... ^^

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.113.65.151
痞子軍團團長
2006-10-25 07:41:24 UTC
Permalink
請原諒我幫忙加個註解...
(坦白說,我翻來翻去,看好久才知道確定差異在哪 XDXD)

※ 引述《pao0111 (Pao)》之銘言:
: 分別寫入兩個檔案:

//這是會導致 reference 失敗的寫法
//差異點在於,這邊存在兩個不同的檔案當中
: FileOutputStream fos = new FileOutputStream("a.object");
: ObjectOutputStream oos = new ObjectOutputStream(fos);
: oos.writeObject(a);
: oos.close();
: fos = new FileOutputStream("b.object");
: oos = new ObjectOutputStream(fos);
: oos.writeObject(b);
: oos.close();

: 嗯,沒錯誤發生。
: 可見互相參考甚至是循環參考對物件的序列化是沒問題的。
: 在別的程式裡,讀出那兩個物件來:
: FileInputStream fis = new FileInputStream("a.object");
: ObjectInputStream ois = new ObjectInputStream(fis);
: A a = (A)ois.readObject();
: fis = new FileInputStream("b.object");
: ois = new ObjectInputStream(fis);
: B b = (B)ois.readObject();
: 此時:
: System.out.println(a.b == b);
: System.out.println(a.b.a == a);
: System.out.println(b.a == a);
: System.out.println(b.a.b == b);
: 印出來的是:
: false
: true
: false
: true
: 哎呀!參照居然變了!

我比較想知道,這時候的 a.b 會指到哪裡去 @__@???
為甚麼 a.b.a 又會指回來自己 @__@???

--
 侃侃長論鮮窒礙  網站:http://www.psmonkey.idv.tw
 眾目睽睽無心顫  個人版:telnet://legend.twbbs.org
 煢居少聊常人事 
 殺頭容易告白難  歡迎參觀 Java 版(@ptt.cc)精華區 \囧/

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 61.228.211.52
台大資男喲
2006-10-25 09:38:24 UTC
Permalink
問題出在這:(節錄自J2SE1.4 API)

 The default serialization mechanism for an object writes the class of the
object, the class signature, and the values of all non-transient and non-static
fields. References to other objects (except in transient or static fields)
cause those objects to be written also. Multiple references to a single object
are encoded using a reference sharing mechanism so that graphs of objects can
be restored to the same shape as when the original was written.

所以"a.object"儲存了兩個物件, "b.object"也同樣儲存了兩個物件

(不信的話可以直接把a.object檔打開看, 一定看的懂的...)

因此讀取的結果是, A有兩個instances, B也有兩個

其中某個A.b是指向一個B instance; 另一個A的refernce b則是指向另一個B instance

自己畫畫圖就很清楚了...

不過也很感謝p大找出這個問題呢:)


※ 引述《PsMonkey (痞子軍團團長)》之銘言:
: 請原諒我幫忙加個註解...
: (坦白說,我翻來翻去,看好久才知道確定差異在哪 XDXD)
: ※ 引述《pao0111 (Pao)》之銘言:
: : 分別寫入兩個檔案:
: //這是會導致 reference 失敗的寫法
: //差異點在於,這邊存在兩個不同的檔案當中
: : FileOutputStream fos = new FileOutputStream("a.object");
: : ObjectOutputStream oos = new ObjectOutputStream(fos);
: : oos.writeObject(a);
: : oos.close();
: : fos = new FileOutputStream("b.object");
: : oos = new ObjectOutputStream(fos);
: : oos.writeObject(b);
: : oos.close();
: : 嗯,沒錯誤發生。
: : 可見互相參考甚至是循環參考對物件的序列化是沒問題的。
: : 在別的程式裡,讀出那兩個物件來:
: : FileInputStream fis = new FileInputStream("a.object");
: : ObjectInputStream ois = new ObjectInputStream(fis);
: : A a = (A)ois.readObject();
: : fis = new FileInputStream("b.object");
: : ois = new ObjectInputStream(fis);
: : B b = (B)ois.readObject();
: : 此時:
: : System.out.println(a.b == b);
: : System.out.println(a.b.a == a);
: : System.out.println(b.a == a);
: : System.out.println(b.a.b == b);
: : 印出來的是:
: : false
: : true
: : false
: : true
: : 哎呀!參照居然變了!
: 我比較想知道,這時候的 a.b 會指到哪裡去 @__@???
: 為甚麼 a.b.a 又會指回來自己 @__@???

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.139.216.85
※ 編輯: NTUtzboy 來自: 220.139.216.85 (10/26 01:38)
愚者
2006-10-26 01:58:21 UTC
Permalink
: 想當然爾:
: System.out.println(a.b == b);
: System.out.println(a.b.a == a);
: System.out.println(b.a == a);
: System.out.println(b.a.b == b);
: 印出來的是:
: true
: true
: true
: true
: 此時:
: System.out.println(a.b == b);
: System.out.println(a.b.a == a);
: System.out.println(b.a == a);
: System.out.println(b.a.b == b);
: 印出來的是:
: false
: true
: false
: true

Dear Pao,
請記得比較物件是用equals方法,不是用==
對於任何物件的製作我們都有責任覆寫hashCode與equals
心有餘力再寫一下toString

另外,Serialization的目的是保持同樣"內容的物件"
我可以有千百個內容相同的物件,但他們的記憶體位置都不同

=========================================================================
import java.io.Serializable;

public class A implements Serializable {
String sign;

public A(String s) {
this.sign = s;
}

public String toString(){
return this.sign;
}

public B b;
}

import java.io.Serializable;

public class B implements Serializable {
String sign;
public A a;
public String toString(){return this.sign;}
public B(String s){
this.sign=s;
}
}

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;



public class NN {
A a;
B b;

public NN() {
a = new A("A@");
b = new B("B@");
a.b = b;
b.a = a;
}

public void makeObjectFile() throws IOException{
FileOutputStream fos = new FileOutputStream("a.object");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(a);
oos.close();

fos = new FileOutputStream("b.object");
oos = new ObjectOutputStream(fos);
oos.writeObject(b);
oos.close();
}

public Object readObjectFile(String name) throws IOException,
ClassNotFoundException{
FileInputStream fis = new FileInputStream(name);
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
ois.close();
return o;
}

public static void main(String[] args) throws IOException,
ClassNotFoundException {
NN n = new NN();
n.makeObjectFile();
B b = (B) n.readObjectFile("b.object");
A a = (A) n.readObjectFile("a.object");

System.out.println(b);
System.out.println(b.a);
System.out.println(a.b);
System.out.println(a);
}
}

Result:
B@
A@
B@
A@


--
btw. 偷打一下廣告

[分享] 實作用serialization實現deep clone
http://www.javaworld.com.tw/jute/post/view?
bid=35&id=154519&sty=3&keywords=clone
(http://tinyurl.com/y8tckq)

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 163.26.34.20
※ 編輯: qrtt1 來自: 163.26.34.20 (10/26 17:58)
※ 編輯: qrtt1 來自: 163.26.34.20 (10/26 17:58)
Pao
2006-10-26 04:25:16 UTC
Permalink
※ 引述《qrtt1 (愚者)》之銘言:
: Dear Pao,
: 請記得比較物件是用equals方法,不是用==
: 對於任何物件的製作我們都有責任覆寫hashCode與equals
: 心有餘力再寫一下toString
: 另外,Serialization的目的是保持同樣"內容的物件"
: 我可以有千百個內容相同的物件,但他們的記憶體位置都不同
以下恕刪

Dear qrtt1,

在我的認知裡:

比較「物件」的「內容」是否相同,使用equals方法。

比較「變數」的「值」是否相同,使用==(雙等號)。

而在我提出的例子裡,想表達的就是序列化的過程裡,

可能會產生多個「內容相同」,但「記憶體位置不同」的物件。
(但嚴格來說,每個參照變數的內容已經不同了)

簡單來說,就是會產生副本。

為了觀察這現象,應該比較物件指向另一個物件的參照的值是否相同。

所以當我要比較兩個物件的「參照變數」的「值」是否相同時,

採用了==來比較,而不是用equals方法。

這點我想是無庸置疑的吧!


關於後面兩句,小弟我資質駑頓看不懂,所以等q大開釋。


對於我為何想提出來給大家參考。

是因為我遇到了一個情形...


我有兩個類別會互相參考到對方,這兩種類別的實例(物件)還可能不少。

並且都存在於記憶體當中。

而不巧的是,我需要將它們保存至檔案系統裡。

萬一讀出來會產生多個副本時,那我「可能」遇到的「麻煩」有:

1.程式邏輯可能出大紕漏。

假設A1和A2都參考到B1,而我經由A1取得B1,

而修改了B1的內容。但事實上A2參考的是B2,

只不過B1.equals(B2) == true。但我修改B1以後

B1.equals(B2) == false。這不是我想見到的結果。

我想要的是A1和A2都「確實」參考到B1,那我只要

修改B1即可,大家都Happy。

2.記憶體佔用會變多。

簡單來說就是浪費,佔用比原先還多的記憶體,

檔案也會一起變大。A1和A2原先都參考到B1,

若產生了副本B2,那記憶體就要多佔用B2的空間。

更不用說A可能從1到很大,B也可能從1到很大。

重複的部分會有多少乘上多少呢?

最可怕的還是B2.equals(B1) == true,這樣我要

B2存在記憶體作啥呢?同樣也不是我想見到的結果。

同樣的內容存一份就好,大家都Happy。


我有遇到這樣的問題,因此提出來給大家參考,小心其中的差異。

如果有人也遇到類似的情形,就曉得該如何小心的使用序列化了。

這樣大家豈不都Happy...^^



--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.113.80.187
godfat 真常
2006-10-27 01:00:52 UTC
Permalink
呃,借問一下,雖然沒有試就問好像不太妙
可是我電腦現在沒灌 jdk, 也懶得為此事而去安裝
(btw, 這個 jdk 的東西電腦裡倒是有好幾個… XD)
(http://homepage2.nifty.com/tkdate/ysmusic/about/SoundTeamJDK.html)

是不是只要把物件寫入檔案,就會把所有有關聯的物件一併寫進去,
且只能一次寫入,不能分開檔案寫入,因為分開寫入就是代表兩份?

如果是這樣,那有辦法強迫各個物件寫入不同的檔案嗎?
雖然這樣做似乎沒有太大的意義…?

另外,可以利用這點實現把物件寫入資料庫嗎?
之前試寫網站時,發現資料庫和物件難以應對…
害我整個覺得很困擾 -_-b 好像連 array 都不能用…?
不過這算題外話就是了…

--
By Gamers, For Gamers - from the past Interplay

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 125.232.1.107
無知的q
2006-10-27 01:44:53 UTC
Permalink
: 另外,可以利用這點實現把物件寫入資料庫嗎?
: 之前試寫網站時,發現資料庫和物件難以應對…
: 害我整個覺得很困擾 -_-b 好像連 array 都不能用…?
: 不過這算題外話就是了…

您試過Hibernate和JDO了嗎 ^^

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 163.26.34.20

继续阅读narkive:
Loading...