一些java類中為什麼需要重載 serialVersionUID 屬性。 就是說 在類裡邊 寫上一個屬性
eg:
private static final long serialVersionUID = -1575386983723846021L;(後邊的資料一般eclipse 會自動產生的)
在Java中,軟體的相容性是一個大問題,尤其在使用到對象串列性的時候,那麼在某一個對象已經被序列化了,可是這個對象又被修改後重新部署了,那麼在這種情況下, 用老軟體來讀取新檔案格式雖然不是什麼難事,但是有可能丟失一些資訊。
serialVersionUID 來解決這些問題,新增的serialVersionUID必須定義成下面這種形式:static final long serialVersionUID=-2805284943658356093L;。其中數字後面加上的L表示這是一個long值。 通過這種方式來解決不同的版本之間的串列話問題。
提綱:
━━━━━━━━
一、概述
二、Java序列化
三、引入版本編號
四、結束語
━━━━━━━━
一、概述
一個程式正式發行出去之後,如果要增加一些新的功能,往往意味著同時要修改使用者儲存資料的方式,也就是必須更改程式儲存檔案的格式——通常是增加儲存到檔案的資料。有些時候,檔案格式必須作徹底的改動,以配合實現程式的新功能。從這個意義上看,檔案格式的發展/變化總是和程式的功能改進相呼應。
但是,大多數情況下,把原有的資料格式一丟了事是行不通的。動物王國中,不能適應環境意味著死亡;軟體領域也相似,新軟體是否支援原有的資料格式很大程度上決定了使用者是否升級。
不管軟體新增/改進了多少功能,不管新的檔案格式是多麼完美,如果新軟體不能利用原來的檔案格式,使用者一般不太會認可新軟體。解決該問題的辦法包括:
● 保留老代碼來讀取老檔案。採用這種方案一般需要額外編寫一些代碼,把老檔案轉換成新的格式(一般地,最簡單的辦法是先把老檔案的資料轉換成新的內部對象,然後利用現有的寫入新版檔案格式的對象)。這種辦法的好處是既保留了原有的代碼,又使它與新的檔案格式相容。但是,這種辦法有時可能導致丟失部分資料,不過總要比丟失全部資料好。
●使新版軟體能夠讀/寫老檔案格式。這種辦法工作量較大,因為程式的新版本一般會增加一些原來沒有的功能,老的資料格式中通常缺乏新功能必需的某些資料。
當新版軟體對原來執行任務的方式作了根本性的變動時,遺失資料決非難得一見的偶然事件。如果新版軟體採用和原來不同的方式達到同樣的效果,原來的功能可能不再有保留的必要。例如,如果一個程式原來用Swing做使用者介面,現在把它改成了Web(瀏覽器)使用者介面,原來的許多使用者介面設定就不再有效。
又如,如果有一個郵件程式,原來用的是以檔案夾為基礎的索引,現在把它改成了以單詞為基礎的索引系統,在升級索引檔案格式的過程中就有可能丟失許多資訊;如果原來的索引檔案儲存了許多使用者配置選項和最佳化措施,在新的索引系統中這些資料可能無法利用。
這類問題沒有絕對完美的解決辦法,但是我們可以採取一些措施,使得升級檔案格式帶來的負面影響儘可能小。Java序列化(Serialization)有著簡單易用的特點,日益成為一種儲存檔案的重要手段,有鑒於此,下面我們就來看看在軟體版本變更過程中,通過Java序列化儲存的檔案如何保持相容性。
二、Java序列化
Java序列化有許多優點:
●容易使用。
●如果一個對象串連到其他對象,序列化機制會儲存所有相關的對象。
●如果某個對象出現多次,序列化機制只儲存一次。這一點極為重要,它不僅減小了檔案空間,而且即使代碼寫得不是很老練,也不必擔心會出現無限迴圈(一個不老練的例子是,用遞迴的方式儲存各個對象,卻又未能有效審計哪些對象已經儲存,這時就有可能陷入永無終止的迴圈)。
遺憾的是,Java序列化機制定義的檔案格式似乎很脆弱,只要稍微改動一下類的定義,原來儲存的對象就可能無法讀取。例如,下面是一個簡單的類定義:
Java代碼 public class Save implements Serializable { String name; public void save() throws IOException { FileOutputStream f = new FileOutputStream("foo"); ObjectOutputStream oos = new ObjectOutputStream(f); oos.writeObject(this); oos.close(); } }
如果在這個類定義中增加一個域,例如final int val = 7;,再來讀取原來儲存的對象,就會出現下面的異常:
java.io.InvalidClassException:
Save; local class incompatible:
stream classdesc serialVersionUID = -2805284943658356093,
local class serialVersionUID = 3419534311899376629
上例異常資訊中的數字串表示類定義裡各種屬性的編碼值:
●類的名字(Save)。
●域的名字(name)。
●方法的名字(Save)。
●已實現的介面(Serializable)。
改動上述任意一項內容(無論是增加或刪除),都會引起編碼值變化,從而引起類似的異常警報。這個數字序列稱為“序列化版本統一標識符”(serial version universal identifier),簡稱UID。解決這個問題的辦法是在類裡面新增一個域serialVersionUID,強制類仍舊使用原來的UID。新增的域必須是:
●static:該域定義的屬性作用於整個類,而非特定的對象。
●final:保證代碼運行期間該域不會被修改。
●long:它是一個64位的數值。
也就是說,新增的serialVersionUID必須定義成下面這種形式:static final long serialVersionUID=-2805284943658356093L;。其中數字後面加上的L表示這是一個long值。
當然,改動之後的類不一定能夠和原來的對象相容。例如,如果把一個域的定義從String改成了int,執行逆-序列化操作時系統就不知道如何處理該值,顯示出錯誤資訊:java.io.InvalidClassException: Save; incompatible types for field name。
Java序列化規範(http://java.sun.com/j2se/1.4.1/docs/guide/
serialization/spec/serialTOC.doc.html)提供了有關相容的改動(http://java.sun.com/j2se/1.4.1/docs/
guide/serialization/spec/version.doc7.html)和不相容改動(http://java.sun.com/j2se/1.4.1/docs/guide/
serialization/spec/version.doc8.html)的清單,這些清單指出了對類作了哪些改動之後仍可能讀取原來序列化的資料。具體細節比較複雜,但瞭解其主要機制還是很容易的:
簡而言之,如果檔案中確實儲存了所有必需的資料,那麼仍有可能讀取該檔案,當然前提是必須處理好序列化的UID。
三、引入版本編號
許多程式都在無意之中作出了這樣的假設:這種檔案格式是我要用到的最後一種格式,以後不再需要制定新的格式,現在要做的是處理好在此之前的各種格式。這種程式會試圖讀取格式版本更高的檔案,操作進行到一半才發現某些不能識別的資料,然後就是突然崩潰。如果檔案包含了大量的中繼資料(描述檔案本身的資料),處理起來就要容易得多。
在Java中,每一個域都由其名稱顯式標明,只要檔案的改動不是很大(只添加了域,沒有被刪除或作重大更改的域),可以想象,用老軟體來讀取新檔案格式不是什麼難事,雖然有可能丟失一些資訊,但可以搞清楚檔案的基本情況。
檔案格式隨著程式功能的改變而改變。理想情況下,程式應當做到既向後相容(新的版本能夠按照老版本的格式讀取,甚至可能允許更新),同時做到向前相容(較老的軟體能夠識別和處理新版的檔案格式)。
通常,檔案的版本無法從表面上一眼看出。大多數程式不會因為檔案的版本不同而變更檔副檔名,而且目前尚無統一的標記檔案版本的辦法。因此,有關檔案格式的版本聲明只能在檔案本身之內進行。如果你現在使用的檔案格式還不包含版本聲明,最好在下次把檔案升級成一個不相容的版本時馬上加入版本戳記,或者尋求一種在當前檔案格式中加入版本戳記但不會帶來負面影響的辦法。
版本資訊一般在檔案的開頭聲明,這是因為程式必須在處理檔案之前首先檢查檔案的版本,除非確定了檔案的版本,否則不必讀取檔案的其餘部分。
按照慣例,檔案版本編號包含兩個部分:主要版本編號和次版本編號。一個特定版本的程式應當有最適合它處理的主-次版本號碼;主要版本號變化意味著檔案格式的重大變化,要繼續使用已經非常困難,必須作出重大修改才能升級到新的版本。
檔案的主次版本號碼之前往往還可以加入另一項內容,稱為“魔術數字”,它的作用就是保證程式處理的檔案類型不會有誤(因為副檔名有可能不能唯一地標明檔案類型)。例如,Java的類檔案總是以下列位元組內容開頭(十六進位):CA FE BA BE。目前還沒有這類數位統一登錄授權單位,不過UNIX在/etc/magic下提供了一個清單(但並不完整)。魔術數字一般有四個位元組,取值範圍很大,所以一般不必擔心會出現取值衝突的情形。
在編寫和維護必須讀/寫檔案的代碼時,注意代碼的向前/向後相容性是非常必要的。在處理檔案的代碼中首先讀取檔案版本,然後根據版本號碼將檔案剩餘內容傳遞給適當的處理方法;如果檔案的版本太老,已不再支援,程式應當給出明確的提示。
四、結束語
檔案格式設計是一個極其重要的話題,但本文還有許多細節問題尚未涉及。例如,對於大型檔案,我們需要隨機訪問,而不是從前向後依次讀取檔案內容的順序訪問,這樣就不必為了訪問檔案最後幾個位元組而讀取整個檔案。無論是XML還是Java序列化對這類隨機訪問的支援都不是很理想,而且這類檔案格式的發展變化比普通檔案更難管理,因為他們依賴於位元組級的訪問,稍微改動一下檔案格式就可能導致不相容。
如果要讓檔案具有ACID特性—— Atomicity、Consistency、Isolation和Durability,即原子性、一致性、隔離性、持久性,問題更加複雜。ACID與事務的概念密切相關,支援多使用者同時訪問一個檔案。對於這類檔案,可以考慮採用某種小型的資料庫系統,例如Birdstep或Sleepycat。不過這已經進入了檔案格式管理的另一個領域,既涉及到資料庫管理軟體的版本,也涉及到資料模式設計的版本。
撇開這些複雜的問題不談,在實踐中,很多時候我們只需簡單的檔案來儲存資料,而且不會出現多使用者並發訪問,可以一次性地處理整個檔案(或者至少適合使用順序訪問方式)。對於這些情形,最好在設計檔案格式時就考慮版本問題,在日後的運行、維護中一定會帶來不少方便。