Preferences API 是輕型的、跨平臺的持久性 API,它是在 JDK 1.4中引入的。它不是爲了爲傳統數據庫引擎提供一個接口,而是用恰當的、操做系統特定的後端以實現真正的持久性。這個 API 是用來存儲少許數據的。事實上,它的名字自己就代表它一般用於存儲用戶特定的設置或者首選項,如字體大小或者窗口布局(固然,您能夠在其中存儲任何您想要存儲的內容)。Preferences API 設計爲存儲字符串、數字、布爾值、簡單字節數組等。在本文中,咱們將爲您展現如何用 Preferences API 存儲對象,並提供了一個爲您處理細節的工做庫。若是您的數據能夠容易地表示爲簡單對象而不是像字符串和數字這種分離的值時,它會頗有用。咱們首先對該 API 做一簡短討論,包括一些使用它的簡單例子,而後詳細討論如何使用這個 API 存儲對象,並給出爲咱們完成這項工做的代碼。咱們還展現了一些使用這個 API 的例子。 java
若是說 Preferences API 主要是爲讓 Java 程序訪問 Microsoft Windows 註冊表而建立的,必定會讓人感到意外。爲何我要這麼說呢?這個 API 的設計相似於 Windows 註冊表,本文前三段中的大部分說明也一樣適用於註冊表。不過,Preferences API 就像全部 Java 語言同樣,是以跨平臺爲目的的,因此它在非 Windows 系統上至少能夠工做得同樣好(固然,本文中的代碼是跨平臺的)。 node
Preferences API 規範沒有規定如何實現這個 API,只規定了它必須作什麼。Java 運行時環境(Java Runtime Environment JRE)的每個實現對這個 API 均可以有不一樣的實現。許多非註冊表的實現將 API 數據存儲在一個 XML 格式的文件中,這個文件也許是在用戶的主目錄中或者在一個共享目錄中。 程序員
與 Windows 註冊表同樣,Preferences API 使用層次樹結構來存儲數據。起始點是一個 root node (根節點是樹的根基,全部其餘節點都是這個節點的後代)。節點能夠包含命名的值以及其餘節點。不一樣的程序將它們的數據存儲在樹的不一樣位置上,因此它們不會彼此衝突。正如咱們將要看到的,Preferences API 採用了特殊的方法幫助防止這種衝突。 數據庫
咱們將首先簡單看一下 Preferences API 是如何工做的以及如何使用它。 後端
理解 Preferences API 的最好方法是使用它。須要作的第一件事是訪問根節點: api
Preferences root = Preferences.userRoot();
這一行代碼返回數據樹的 user root。前面咱們說系統中的全部數據都存儲在一個樹中。不過,這並不徹底正確 -- 事實上,有 兩個數據樹 -- 用戶樹和系統樹。這兩個樹的行爲徹底相同,可是它們有不一樣的目的。系統樹用於存儲所用戶均可以使用的數據,而用戶樹對於每個用戶是不一樣的。 數組
這兩個樹天生就有不一樣的目的。您要將字體首選項存儲在用戶樹中,由於這是用戶特定的內容。另外一方面,您要將程序位置存儲在系統樹中,由於位置對於全部用戶是相同的,而且全部用戶均可能用到它。 佈局
小型程序會使用系統樹或者用戶樹,可是不會同時使用這二者。大型應用程序可能同時使用這兩種樹。在本文中,咱們將只針對用戶樹,要記住用戶和系統樹的行爲是同樣的。 字體
如今讓咱們看一下如何用 Preferences API 讀取和寫入簡單的值。 編碼
當您獲得根節點後,就用它讀取和寫入值。下面是如何寫入一個字體大小:
root.putInt( "fontsize", 10 );
下面是在這以後將它讀出來的方法:
int fontSize = prefs.getInt( "fontsize", 12 );
注意getInt()須要一個默認值 -- 在這裏是12。
固然,您能夠讀取和寫入整數以外的值。能夠讀取和寫入許多基本 Java 類型。還能夠將節點存儲在其餘節點中,如這個例子所示:
Preferences child = parent.node( "child" );
這就是 Preferences API 的所有內容 -- 剩下的就是細節使用了,咱們將在下一節討論其中一個細節。
不難想像兩個不一樣的程序員可能但願存儲不一樣的字體大小,若是他們決定以同一個名字「font size」存儲他們的值,那麼咱們就有問題了。一個程序的首選項會影響另外一個程序。
解決方法是將內容存儲在包特定的位置上,像這樣:
Preferences ourRoot = Preferences.userNodeForPackage( getClass() );
userNodeForPackage()方法取一個Class對象並返回這個類特定的節點。這樣,每個應用程序 -- 假定它是在其本身的包中 -- 都會有本身的首選項節點。
對於 Preferences API 的工做方式有了很好的瞭解後,咱們還須要知道如何擴展它以便對對象進行處理。
這就是咱們但願將對象寫入 Preferences 樹的理想方法:
Font font = new Font( ... ); Preferences prefs = Preferences.userNodeForPackage( getClass() ); prefs.putObject( "font", font );
不過,不幸的是,Preferences 對象沒有putObject()和getObject()方法。可是咱們會盡可能作到接近這一點。咱們將在一個名爲PrefObj的類中實現這些方法。如下是咱們的作法:
Font font = new Font( ... ); Preferences prefs = Preferences.userNodeForPackage( getClass() ); PrefObj.putObject( prefs, "font", font );
咱們已經儘可能作到在Preferences類中獲得一個添加方法。
下一節,咱們將看一看getObject()和putObject()是如何實現的。
咱們在這裏使用的技術用到了兩個技巧。第一個技巧是將對象轉變爲一個字節數組。這樣作的緣由很簡單:儘管 Preferences 對象不處理對象,可是它能夠處理字節數組。
幸運的是,咱們不須要從頭開始 -- 它已經創建在 Java 語言中了。有幾種方式將對象轉換爲字節數組,下面展現了咱們在PrefObj類中是如何作的:
static private byte[] object2Bytes( Object o ) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( baos ); oos.writeObject( o ); return baos.toByteArray(); }
這裏的關鍵是ObjectOutputStream類 -- 它是實際完成將對象轉換爲字節流這個魔術的類。經過用ObjectOutputStream包住ByteArrayOutputStream,咱們就將字節流轉換爲字節數組。
還有一種使用其餘方式的方法:
static private Object bytes2Object( byte raw[] ) throws IOException, ClassNotFoundException { ByteArrayInputStream bais = new ByteArrayInputStream( raw ); ObjectInputStream ois = new ObjectInputStream( bais ); Object o = ois.readObject(); return o; }
必定要記ObjectOutputStream只處理實現了 java.io.Serializable 接口的對象。幸運的是,這包括了幾乎全部核心 Java 庫中的對象以及您的程序中全部聲明爲實現 Serializable 的對象。
正如我在前面提到的,Preferences API 的確能夠對字節數組進行處理。不過,咱們在這裏構造的字節數組並非很正確,咱們將在下一節看到這一點。
Preferences API 對能夠存儲在它裏面的數據大小有限制。具體就是字符串限制爲 MAX_VALUE_LENGTH 字符。字節數組限制爲 MAX_VALUE_LENGTH 長度 75%,由於字節數組是經過編碼爲字符串存儲的。
另外一方面,一個對象能夠爲任意大小,因此咱們須要將它分爲幾部分。固然,最容易的方法是首先將它轉換爲一個字節數組,而後將字節數組拆開。下面是拆開字節數組的代碼,它也來自於PrefObj:
static private byte[][] breakIntoPieces( byte raw[] ) { int numPieces = (raw.length + pieceLength - 1) / pieceLength; byte pieces[][] = new byte[numPieces][]; for (int i=0; i<numPieces; ++i) { int startByte = i * pieceLength; int endByte = startByte + pieceLength; if (endByte > raw.length) endByte = raw.length; int length = endByte - startByte; pieces[i] = new byte[length]; System.arraycopy( raw, startByte, pieces[i], 0, length ); } return pieces; }
這裏沒有什麼複雜的內容 -- 咱們只是建立一個數組的數組,每個長度爲最大 pieceLength的字節長度(pieceLength 是 MAX_VALUE_LENGTH 的3/4)。相應地,有另外一種方法將各個部分再合併到一塊兒:
static private byte[] combinePieces( byte pieces[][] ) { int length = 0; for (int i=0; i<pieces.length; ++i) { length += pieces[i].length; } byte raw[] = new byte[length]; int cursor = 0; for (int i=0; i>pieces.length; ++i) { System.arraycopy( pieces[i], 0, raw, cursor, pieces[i].length ); cursor += pieces[i].length; } return raw; }
這個例程檢查全部片斷的總長度並建立一個具備這種長度的新數組。而後將片斷一個一個地拷貝進去。
這裏咱們使用第二個技巧 -- 將值轉換爲節點。通常來講,當咱們用 Preferences API 存儲值時,咱們將它放到首選項數據樹中一個節點的 slot 中。
可是咱們不能在這裏真的這樣作。即便一個對象只有一個值,咱們也要將它轉換爲一組固定長度的字節數組。若是咱們只有一個字節數組,寫入數據樹中的 slot 會很容易,由於 Preferences API 直接支持字節數組。可是這行不通,由於咱們有多個數組。
技巧是爲每個對象分配一個節點。讓咱們弄清楚它的意義。
一般,將值存儲在節點的多個 slot 的其中之一。可是咱們準備爲每個對象建立一個節點, 並將字節數組存儲到該節點的 slot 中。讓咱們說的更具體一些。若是能夠,咱們會將一個對象存儲到單個 slot 中:
Preferences parent = ....; parent.putObject( "child", object );
可是咱們不能這麼作,由於 Preferences 沒有putObject()方法。相反,咱們建立一個節點並將字節數組存儲到其中,以下所示:
Preferences parent = ....; Preferences child = parent.node( "child" ); for (int i=0; i<pieces.length; ++i) { child.putByteArray( ""+i, pieces[i] ); }
這樣,不是將一個值存儲到一個稱爲「child」的 slot 中,咱們將幾個值存儲到一個稱爲 「child」的節點中。這些值是用數字鍵 -- 「0」、「1」、「2」等存儲的。
使用數字鍵可使後面讀取片斷時更容易:
Preferences parent = ....; Preferences child = parent.node( "child" ); for (int i=0; i<numPieces; ++i) { pieces[i] = child.getByteArray( ""+i, null ); }
在下一節,咱們將看一下結合全部這些步驟的例程。
PrefObjs有一個名爲putObject()的靜態方法,它調用在前面清單 3、 5 和 8 中描述的方法。其內容以下:
static public void putObject( Preferences prefs, String key, Object o ) throws IOException, BackingStoreException, ClassNotFoundException { byte raw[] = object2Bytes( o ); byte pieces[][] = breakIntoPieces( raw ); writePieces( prefs, key, pieces ); }
方法putObject()將整個過程分爲三步,分別嵌入咱們在前面討論過的三個方法。它將對象轉換爲字節數組( 清單 3)、將數組分解爲更小的數組( 清單 5)、而後將片斷寫入 Preferences API。
有一個用於讀取的相似方法:
static public Object getObject( Preferences prefs, String key ) throws IOException, BackingStoreException, ClassNotFoundException { byte pieces[][] = readPieces( prefs, key ); byte raw[] = combinePieces( pieces ); Object o = bytes2Object( raw ); return o; }
這個方法從 Preferences API 中讀取片斷,並將它們結合爲單個字節數組,而後將它轉換爲對象。
正如您所看到的,這是一種使用 Preferences API 所具備的功能的簡潔方式,實現了它 原本不具有的功能。這是一種擴展示有庫的好方法。理論上,您能夠改變庫或者建立子類,可是這樣有可能會干擾其餘使用 Preferences API 的程序。而使用這種方式,您能夠保持原來的 API 不變,同時以一種乾淨、有用的方式擴展它。