Storable - Perl 資料結構的持久性
use Storable;
store \%table, 'file';
$hashref = retrieve('file');
use Storable qw(nstore store_fd nstore_fd freeze thaw dclone);
# Network order
nstore \%table, 'file';
$hashref = retrieve('file'); # There is NO nretrieve()
# Storing to and retrieving from an already opened file
store_fd \@array, \*STDOUT;
nstore_fd \%table, \*STDOUT;
$aryref = fd_retrieve(\*SOCKET);
$hashref = fd_retrieve(\*SOCKET);
# Serializing to memory
$serialized = freeze \%table;
%table_clone = %{ thaw($serialized) };
# Deep (recursive) cloning
$cloneref = dclone($ref);
# Advisory locking
use Storable qw(lock_store lock_nstore lock_retrieve)
lock_store \%table, 'file';
lock_nstore \%table, 'file';
$hashref = lock_retrieve('file');
Storable 套件為包含 SCALAR、ARRAY、HASH 或 REF 物件的 Perl 資料結構帶來持久性,亦即任何可以方便地儲存在磁碟並在稍後時間擷取的資料。
它可以透過呼叫 store 並傳遞要儲存的物件的參考,以及應該將影像寫入其中的檔案名稱,以一般的程序方式使用。
例程會傳回 undef 以表示 I/O 問題或其他內部錯誤,否則會傳回 true 值。嚴重的錯誤會以 die 例外狀況傳播。
若要擷取儲存在磁碟中的資料,請對檔案名稱使用 retrieve。儲存在該檔案中的物件會在記憶體中重新建立,並傳回根物件的參考。如果在讀取時發生 I/O 錯誤,則會傳回 undef。其他嚴重的錯誤會透過 die 傳播。
由於儲存是遞迴執行的,因此您可能想要將共用大量共用資料的物件參考塞進單一陣列或雜湊表,然後儲存該物件。這樣一來,當您擷回整個物件時,物件會繼續共用它們原本共用的資料。
以輕微的標頭開銷為代價,您可以使用 store_fd 例程儲存到已開啟的檔案描述符,並透過 fd_retrieve 從檔案擷取。這些名稱並未預設匯入,因此如果您需要這些例程,則必須明確執行此動作。您提供的檔案描述符必須已開啟,如果您要擷取,則必須開啟為讀取模式;如果您要儲存,則必須開啟為寫入模式。
store_fd(\%table, *STDOUT) || die "can't store to stdout\n";
$hashref = fd_retrieve(*STDIN);
您也可以以網路順序儲存資料,以允許在多個平台間輕鬆共用,或是在已知遠端連線的 socket 上儲存時。要呼叫的例程具有初始的 n 前綴,表示網路,例如 nstore 和 nstore_fd。在擷取時,您的資料會正確還原,因此您不必知道您是從本機或網路順序資料還原。雙精度值會字串化儲存,以確保可攜性,但可能會略微失去小數點後的精度。
在使用 fd_retrieve 時,物件會依序擷取,每個物件(即一個遞迴樹)對應一個關聯的 store_fd。
如果您比較偏好物件導向,您可以繼承 Storable 並透過呼叫 store 作為方法來直接儲存您的物件。將要儲存的樹的根設為祝福參考(即物件)的事實是特殊情況,因此擷取不會提供該物件的參考,而是提供祝福的物件參考本身。(否則,您會取得該祝福物件的參考)。
Storable 引擎也可以將資料儲存在 Perl 標量中,以便稍後擷取。這主要用於在某些安全的壓縮記憶體位置凍結複雜結構(在那裡它有可能透過某些 IPC 傳送到另一個程序,因為凍結結構實際上也會序列化它)。稍後,也許在其他地方,你可以解凍 Perl 標量,並在記憶體中重新建立原始的複雜結構。
令人驚訝的是,要呼叫的常式被命名為 freeze
和 thaw
。如果你希望將凍結的標量傳送到另一部機器,請改用 nfreeze
以取得可攜式映像。
請注意,凍結一個物件結構並立即解凍它實際上會達成該結構的深度複製
dclone(.) = thaw(freeze(.))
Storable 提供一個 dclone
介面,它不會建立那個中間標量,而是將結構凍結在某些內部記憶體空間,然後立即解凍它。
lock_store
和 lock_nstore
常式等同於 store
和 nstore
,但它們在寫入之前會取得檔案的獨佔鎖定。同樣地,lock_retrieve
的作用與 retrieve
相同,但在讀取之前也會取得檔案的共用鎖定。
與任何建議鎖定機制一樣,保護僅在你系統性地使用 lock_store
和 lock_retrieve
時才有效。如果你的應用程式的其中一方使用 store
,而另一方使用 lock_retrieve
,你將完全得不到任何保護。
內部建議鎖定是使用 Perl 的 flock() 常式來實作。如果您的系統不支援任何形式的 flock(),或者您透過 NFS 共享檔案,您可能希望使用其他形式的鎖定,例如使用 LockFile::Simple 模組,它會使用檔案系統項目鎖定檔案,而不是鎖定檔案描述符。
Storable 的核心是用 C 編寫的,以獲得良好的速度。在操作 Perl 內部時已進行額外的低層級最佳化,以犧牲封裝來換取更快的速度。
通常,Storable 會以 Perl 內部儲存的順序儲存雜湊的元素,即偽隨機。如果您將 $Storable::canonical
設為 TRUE
值,Storable 會將雜湊儲存為按其鍵排序的元素。這允許您透過比較其凍結的表示(甚至是壓縮的凍結表示)來比較資料結構,這對於為複雜的查詢建立查詢表很有用。
標準順序並不表示網路順序;這兩個是正交設定。
自 Storable 版本 2.05 以來,程式碼參考可以透過 B::Deparse 來序列化。若要啟用此功能,請將 $Storable::Deparse
設為 true 值。若要啟用反序列化,應將 $Storable::Eval
設為 true 值。請注意,反序列化是透過 eval
進行的,如果 Storable 檔案包含惡意資料,這會很危險。您可以將 $Storable::Eval
設為子常式參考,它將用於取代 eval
。請參閱下方使用 Safe 區隔來反序列化程式碼參考的範例。
如果 $Storable::Deparse
和/或 $Storable::Eval
設為 false 值,則在序列化和反序列化時會尊重 $Storable::forgive_me
的值(請參閱下方)。
這個版本的 Storable 可以用於較新版本的 Perl,以序列化較早版本的 Perl 不支援的資料。預設情況下,Storable 會嘗試執行正確的動作,如果遇到無法反序列化的資料,則會 croak()
。但是,預設值可以如下變更
Perl 5.6 加入支援編碼點 > 255 的 Unicode 字元,而 Perl 5.8 則對雜湊金鑰中的 Unicode 字元提供完整支援。Perl 內部使用 utf8 編碼這些字元的字串,而 Storable 則將它們序列化為 utf8。預設情況下,如果較舊版本的 Perl 遇到無法表示的 utf8 值,它將會 croak()
。若要變更此行為,讓 Storable 將 utf8 編碼值反序列化為位元組字串(實際上會捨棄 is_utf8 旗標),請將 $Storable::drop_utf8
設為某個 TRUE
值。這是一種資料遺失,因為當 $drop_utf8
為 true 時,將無法判斷原始資料是 Unicode 字串,還是碰巧為有效 utf8 的位元組系列。
Perl 5.8 加入對受限雜湊的支援,其金鑰受限於特定集合,且值可以鎖定為唯讀。預設情況下,當 Storable 在不支援受限雜湊的 perl 上遇到受限雜湊時,它會將其反序列化為一般雜湊,同時靜默捨棄任何佔位符金鑰,並讓金鑰和所有值保持未鎖定。若要讓 Storable croak()
,請將 $Storable::downgrade_restricted
設為 FALSE
值。若要還原預設值,請將其設回某個 TRUE
值。
cperl PERL_PERTURB_KEYS_TOP 雜湊策略已知會與受限雜湊產生問題。
在 64 位元系統上,某些資料結構可能會超過 2G(即 I32_MAX)限制。在 32 位元系統上,I32 和 U32(2G-4G)之間的字串也會超過限制。自 Storable 3.00(不在 perl5 核心)以來,我們就能夠儲存和擷取這些物件,即使 perl5 本身無法處理它們。這些物件包括長度超過 4G 的字串、元素超過 2G 的陣列,以及元素超過 2G 的雜湊。cperl 禁止元素超過 2G 的雜湊,但這時 cperl 會失敗。perl5 本身至少在 5.26 版之前允許這樣做,但無法反覆處理它們。請注意,在 perl 有機會中止之前,建立這些物件可能會導致作業系統產生記憶體不足例外狀況。
Storable 的早期版本如果遇到內部版本號高於讀取 Storable 所知的檔案,就會立即 croak。每次將新的資料類型(例如受限雜湊)新增到檔案格式的詞彙表中時,內部版本號就會增加。這表示較新的 Storable 模組無法寫入舊版 Storable 可讀取的檔案,即使寫入器未儲存較新的資料類型也是如此。
此版本的 Storable 會延後 croaking,直到它在檔案中遇到它不認識的資料類型。這表示它會繼續讀取由較新的 Storable 模組產生的檔案,這些模組會小心它們寫入的內容,讓在混合環境中升級 Storable 模組變得更容易。
可以透過將 $Storable::accept_future_minor
設定為某個 FALSE
值來重新套用立即 croaking 的舊行為。
所有這些變數都不會對支援相關功能的較新 Perl 產生影響。
Storable 使用「例外」範例,因為它不會嘗試解決失敗:如果發生不好的事情,會從呼叫者的觀點產生例外(請參閱 Carp 和 croak()
)。使用 eval {} 來攔截這些例外。
當 Storable croak 時,它會嘗試透過 Log::Agent
套件中的 logcroak()
常式回報錯誤(如果可用)。
一般錯誤會透過讓 store() 或 retrieve() 傳回 undef
來回報。這些錯誤通常是 I/O 錯誤(或擷取時的串流截斷錯誤)。
當 Storable 擲出「已超過巢狀結構的最大遞迴深度」錯誤時,我們已經用盡堆疊空間。不幸的是,在某些較早的 perl 版本中,清理遞迴資料結構會遞迴到 free 呼叫中,這將導致清理中的堆疊溢位。然後不會正確清理此資料結構,它只會在全域毀損期間毀損。
任何類別都可以定義掛勾,這些掛勾會在對該類別的實例物件執行序列化和反序列化程序期間被呼叫。這些掛勾可以重新定義序列化執行的方式(因此,也可以定義對稱的反序列化應該如何進行)。
由於我們之前說過
dclone(.) = thaw(freeze(.))
我們對掛勾所說的一切也應該適用於深度複製。但是,掛勾可以知道操作是單純的序列化還是複製。
因此,當涉及序列化掛勾時,
dclone(.) <> thaw(freeze(.))
嗯,你可以讓它們保持同步,但無法保證它總是適用於其他人編寫的類別。此外,這樣做幾乎沒有好處:序列化掛勾只能保留物件的一個屬性,這可能不是在對同一個物件進行深度複製時應該發生的情況。
以下是掛勾介面
STORABLE_freeze
obj, cloning序列化掛勾,在序列化期間對物件呼叫。它可以被繼承,或像任何其他方法一樣在類別本身中定義。
參數:obj 是要序列化的物件,cloning 是表示我們是在 dclone() 中還是透過 store() 或 freeze() 進行一般序列化的一個旗標。
傳回值:一個 LIST ($serialized, $ref1, $ref2, ...)
,其中 $serialized 是要使用的序列化形式,而選用的 $ref1、$ref2 等是額外的參考,你希望讓 Storable 引擎序列化它們。
在反序列化期間,你會收到相同的 LIST,但所有額外的參考都會指向已反序列化的結構。
掛勾在序列化流程中第一次被觸發時,你可以讓它回傳一個空清單。這將會指示 Storable 引擎進一步捨棄這個類別的掛勾,並因此還原底層 Perl 資料的預設序列化。掛勾將會在下次序列化中再次正常處理。
除非你更了解,序列化掛勾應該總是說
sub STORABLE_freeze {
my ($self, $cloning) = @_;
return if $cloning; # Regular default serialization
....
}
以便維持合理的 dclone() 語意。
STORABLE_thaw
obj, cloning, serialized, ...在反序列化期間呼叫物件的解序列化掛勾。但等等:如果我們正在反序列化,那還沒有物件...對吧?
錯了:Storable 引擎會為你建立一個空的物件。如果你知道 Eiffel,你可以將 STORABLE_thaw
視為一個替代建立常式。
這表示掛勾可以像任何其他方法一樣被繼承,而 obj 是你這個特定實例的祝福參考。
如果你知道 STORABLE_freeze
,其他引數看起來應該很熟悉:當我們是深度複製操作的一部分時,cloning 為 true,serialized 是你在 STORABLE_freeze
中傳回給引擎的序列化字串,而且可能有一個參考清單,順序與你序列化時給它們的順序相同,指向已反序列化的物件(已由 Storable 引擎處理)。
當 Storable 引擎找不到任何 STORABLE_thaw
掛勾常式時,它會嘗試動態地載入類別(使用祝福的套件名稱),然後重新嘗試查找。如果在那個時候找不到掛勾,引擎會掛掉。請注意,如果你在同一個檔案中定義多個類別,這個機制會失敗,但 perlmod 已經警告過你了。
由你使用這些資訊,以你想要的方式填入 obj。
傳回值:無。
STORABLE_attach
class, cloning, serialized雖然 STORABLE_freeze
和 STORABLE_thaw
對於每個實例都是獨立的類別很有用,但此機制對於存在於共用程序層級或系統層級資源的物件(例如單例物件、資料庫池、快取或備忘物件)有困難(或不相容)。
替代的 STORABLE_attach
方法為這些共用物件提供了解決方案。您實作 STORABLE_freeze
--> STORABLE_attach
,而不是 STORABLE_freeze
--> STORABLE_thaw
。
引數:class 是我們要附加到的類別,cloning 是表示我們在 dclone() 中或透過 thaw() 進行一般取消序列化處理的旗標,serialized 是資源物件的儲存字串。
由於這些資源物件被視為由整個程序/系統擁有,而不是任何序列化處理的「屬性」,因此序列化字串中不應包含物件底下的任何參考。因此,在任何實作 STORABLE_attach
的類別中,STORABLE_freeze
方法不能傳回任何參考,而且如果 STORABLE_freeze
嘗試傳回參考,Storable
會擲回錯誤。
「附加」回共用資源物件所需的所有資訊一定只能包含在 STORABLE_freeze
傳回字串中。否則,STORABLE_freeze
會對 STORABLE_attach
類別表現得像一般一樣。
由於 STORABLE_attach
傳遞類別(而不是物件),因此它也會直接傳回物件,而不是修改傳遞的物件。
傳回值:class
型別的物件
謂詞不可匯出。必須透過明確加上 Storable 套件名稱為其加上字首來呼叫它們。
Storable::last_op_in_netorder
Storable::last_op_in_netorder()
謂詞會告訴您在最後的儲存或擷取操作中是否使用了網路順序。如果您不知道如何使用這個,就忘記它吧。
Storable::is_storing
如果在儲存操作中(透過 STORABLE_freeze 鉤子),則傳回 true。
Storable::is_retrieving
如果在擷取操作中(透過 STORABLE_thaw 鉤子),則傳回 true。
有了鉤子,就能遞迴回到 Storable 引擎。的確,鉤子是正規 Perl 程式碼,而 Storable 在序列化和反序列化事物時很方便,所以為什麼不使用它來處理序列化字串?
不過,有幾件事您需要知道
從 Storable 3.05 到 3.13,我們探測了參考、陣列和雜湊的堆疊遞迴限制,最大深度約為 ~1200-35000,否則我們可能會陷入堆疊溢位。 кстати,在 JSON::XS 中,此限制為 512。如果參考不會立即相互參考,則還沒有這樣的限制,因此您可能會陷入這樣的堆疊溢位分段錯誤。
此探測和我們執行的檢查有一些限制
建置時間的堆疊大小在執行時間可能不同,例如,堆疊大小可能已使用 ulimit(1) 修改。如果在執行時間較大,Storable 可能會不必要地讓 freeze() 或 thaw() 失敗。如果在建置時間較大,Storable 在執行時間處理深度結構時可能會分段錯誤。
堆疊大小在執行緒中可能不同。
陣列和雜湊遞迴限制會針對相同的遞迴深度分別檢查,在許多巢狀雜湊中包含一大串巢狀陣列的凍結結構可能會耗盡處理器堆疊,而不會觸發 Storable 的遞迴保護。
因此,這些現在有簡單的預設值,而不是在建置時間探測。
您可以透過分別修改 $Storable::recursion_limit
和 $Storable::recursion_limit_hash
來控制最大陣列和雜湊遞迴深度。兩者都可以設定為 -1
以防止任何深度檢查,不過不建議這樣做。
如果你想要測試極限,stacksize 工具包含在 Storable
發行版中。
如果你透過 freeze() 序列化的事物(例如)指向我們在 hook 中嘗試序列化的物件,你可以建立無限迴圈。
物件之間的共用參考不會保持共用:如果我們序列化物件 [A, C] 的清單,其中物件 A 和 C 都指向相同的物件 B,而且在 A 中有一個序列化 hook 宣告 freeze(B),那麼在反序列化時,我們會取得 [A', C'],其中 A' 指向 B',但 C' 指向 D,也就是 B' 的深度複製。拓撲結構未保留。
系統的最大堆疊遞迴極限由 stack_depth()
和 stack_depth_hash()
傳回。雜湊極限通常是陣列和 ref 極限的一半,因為 Perl 雜湊 API 並非最佳。
這就是為什麼 STORABLE_freeze
讓你提供要序列化的參考清單。引擎保證這些參考會在與其他物件相同的內容中序列化,因此共用物件會保持共用。
在上述 [A, C] 範例中,STORABLE_freeze
hook 可以傳回
("something", $self->{B})
而 B 部分會由引擎序列化。在 STORABLE_thaw
中,你可以取回 B' 物件的參考,並已為你反序列化。
因此,通常應該避免遞迴,但它仍然受支援。
CPAN 上有一個 Clone 模組,它原生實作深度複製,亦即不凍結到記憶體並解凍結果。它的目標是有一天取代 Storable 的 dclone()。不過,它目前不支援 Storable hook 來重新定義執行深度複製的方式。
是的,有很多 :-) 但更精確地說,在 UNIX 系統中有一個名為 file
的工具程式,它會根據資料檔案的內容(通常是前幾個位元組)來辨識資料檔案。為了讓此功能運作,需要一個名為 magic 的特定檔案來教授資料的 簽章。該組態檔所在的位置取決於 UNIX 的版本;通常類似於 /usr/share/misc/magic 或 /etc/magic。您的系統管理員需要更新 magic 檔案。必要的簽章資訊會透過呼叫 Storable::show_file_magic() 輸出到 STDOUT。請注意,file
工具程式的 GNU 實作,版本 3.38 或更新版本,預期會包含對 Storable 檔案的辨識支援,以及其他類型的 Perl 檔案。
您也可以使用下列函式從 Storable 映像中萃取檔案標頭資訊
如果給定的檔案是 Storable 映像,則傳回描述它的雜湊。如果檔案可讀,但不是 Storable 映像,則傳回 undef
。如果檔案不存在或不可讀,則會發生 croak。
傳回的雜湊具有下列元素
version
這會傳回檔案格式版本。它是一個字串,例如「2.7」。
請注意,此版本號碼與 Storable 模組本身的版本號碼不同。例如,Storable v0.7 會建立 v2.0 格式的檔案,而 Storable v2.15 會建立 v2.7 格式的檔案。檔案格式版本號碼只會在新增會讓舊版模組混淆的額外功能時才會遞增。
早於 v2.0 的檔案將會有一個版本號碼「-1」、「0」或「1」。當時沒有使用次要號碼。
version_nv
這會傳回檔案格式版本為數字。它是一個字串,例如「2.007」。此值適合用於數字比較。
常數函式 Storable::BIN_VERSION_NV
傳回一個可比較的數字,代表此版本 Storable 完全支援的最高檔案版本號碼(但請參閱上方關於 $Storable::accept_future_minor
的討論)。常數函式 Storable::BIN_WRITE_VERSION_NV
傳回寫入的檔案版本,且在某些組態中可能小於 Storable::BIN_VERSION_NV
。
major
、minor
這也會傳回檔案格式版本。如果版本為「2.7」,則 major 會是 2,minor 會是 7。當 major 小於 2 時,minor 元素會遺失。
hdrsize
這是 Storable 標頭佔用的位元組數。
netorder
如果影像儲存資料以網路順序儲存,則此值為 TRUE。這表示它是使用 nstore() 或類似的函式建立的。
byteorder
這僅在 netorder
為 FALSE 時存在。它是建立此影像的 Perl 的 $Config{byteorder} 字串。它是一個字串,例如「1234」(32 位元小端序)或「87654321」(64 位元大端序)。這必須與目前的 Perl 相符,才能讓 Storable 讀取影像。
intsize
、longsize
、ptrsize
、nvsize
這些僅在 netorder
為 FALSE 時存在。這些是建立此影像的 Perl 中各種 C 資料類型的尺寸。這些必須與目前的 Perl 相符,才能讓 Storable 讀取影像。
nvsize
元素僅存在於檔案格式 v2.2 以上。
file
檔案名稱。
$buffer 應為可儲存的影像或其開頭幾個位元組。如果 $buffer 開頭為可儲存的標頭,則會傳回描述影像的雜湊,否則會傳回 undef
。
雜湊的結構與 Storable::file_magic() 傳回的結構相同。如果影像為檔案影像,則 file
元素為 true。
如果提供 $must_be_file 參數且為 TRUE,則除非影像看起來像是屬於檔案傾印,否則傳回 undef
。
可儲存標頭的最大大小目前為 21 位元組。如果提供的 $buffer 只是可儲存影像的第一部分,則至少應為此長度,以確保 read_magic() 會將其辨識為可儲存影像。
以下是顯示 Storable 可能用法的部分程式碼範例
use Storable qw(store retrieve freeze thaw dclone);
%color = ('Blue' => 0.1, 'Red' => 0.8, 'Black' => 0, 'White' => 1);
store(\%color, 'mycolors') or die "Can't store %a in mycolors!\n";
$colref = retrieve('mycolors');
die "Unable to retrieve from mycolors!\n" unless defined $colref;
printf "Blue is still %lf\n", $colref->{'Blue'};
$colref2 = dclone(\%color);
$str = freeze(\%color);
printf "Serialization of %%color is %d bytes long.\n", length($str);
$colref3 = thaw($str);
它會列印(在我的電腦上)
Blue is still 0.100000
Serialization of %color is 102 bytes long.
序列化 CODE 參照並在安全隔離區中反序列化
use Storable qw(freeze thaw);
use Safe;
use strict;
my $safe = new Safe;
# because of opcodes used in "use strict":
$safe->permit(qw(:default require));
local $Storable::Deparse = 1;
local $Storable::Eval = sub { $safe->reval($_[0]) };
my $serialized = freeze(sub { 42 });
my $code = thaw($serialized);
$code->() == 42;
請勿接受來自不可靠來源的可儲存文件!沒有任何方式可以設定 Storable,讓它能安全地處理不可靠的資料。雖然有各種選項可用於減輕特定安全性問題,但這些選項並未為使用者提供完整的安全防護網,而且處理不可靠的資料可能會導致區段錯誤、遠端程式碼執行或權限提升。以下列出一些已知功能,它們代表安全性問題,此模組的使用者應加以考量。
最明顯的是,選擇性的(預設為關閉)CODE 參照序列化功能允許將程式碼傳輸到反序列化處理程序。此外,任何序列化的物件都會導致 Storable 在反序列化模組中載入與物件類別對應的模組。對於被竄改的模組名稱,這幾乎可以載入任何程式碼。最後,當物件在反序列化處理程序中被銷毀時,會呼叫反序列化的物件的解構函式。惡意製作的可儲存文件可能會將此類物件放入雜湊金鑰的值中,而該雜湊金鑰會被同一個雜湊中的另一個金鑰/值對覆寫,進而導致立即執行解構函式。
若要在解凍/擷取時停用祝福物件,請從 $Storable::flags
中移除旗標 BLESS_OK
= 2,或將解凍/擷取的第 2 個參數設定為 0。
若要在解凍/擷取時停用資料繫結,請從 $Storable::flags
中移除旗標 TIE_OK
= 4,或將解凍/擷取的第二個參數設定為 0。
在 $Storable::flags
= 6 的預設設定下,即使是重新命名的物件,攻擊者也可以建立或銷毀隨機物件。請參閱 CVE-2015-1592 及其 Metasploit 模組。
如果您的應用程式需要接受來自不受信任來源的資料,最好使用功能較弱且較安全的序列化格式和實作。如果您的資料夠簡單,Cpanel::JSON::XS 或 Data::MessagePack 是不錯的替代方案。對於包含各種 Perl 特定資料類型(例如正規表示式或別名資料)的更複雜資料結構,Sereal 是最佳替代方案,並提供最大的互通性。請注意,Sereal 預設是不安全的,但您可以設定編碼器和解碼器以減輕任何安全問題。
如果您在雜湊表中使用參照作為鍵,在擷取資料時,您可能會感到失望。的確,Perl 會將用作雜湊表鍵的參照字串化。如果您稍後希望透過另一個參照字串化(即使用最初用於鍵的相同參照將值記錄到雜湊表中)來存取項目,它會運作,因為兩個參照都會字串化為相同的字串。
不過,在 store
和 retrieve
作業的順序中,它不會運作,因為擷取物件中的位址(字串化參照的一部分)可能會與原始位址不同。結構的拓撲結構會保留,但不會保留像這樣的隱藏語意。
在重要的平台上,務必對傳遞給 Storable 函式的描述符呼叫 binmode()
。
正規儲存包含大型雜湊的資料會比正常儲存相同資料慢很多,因為必須配置、填入、排序和釋放暫存陣列來儲存每個雜湊的鍵。一些測試顯示儲存速度減半——確切的損失將取決於資料的複雜性。擷取不會減慢。
Storable 現在已支援儲存正規表示式,但有重大的限制
需要 perl 5.8 或更新版本。
帶有程式區塊的正規表示式,例如 /(?{ ... })/
或 /(??{ ... })/
在解凍時會擲回例外。
正規表示式語法和旗標在 perl 的歷史中已經改變,因此您在一個 perl 版本中凍結的正規表示式可能無法在另一個 perl 版本中解凍或表現出不同的行為。
根據 perl 版本的不同,正規表示式可能會根據上下文改變行為,但後來的 perl 會將該行為烘焙到 regexp 中。
如果無法解凍凍結的正規表示式,Storable 會擲回例外。
您無法儲存 GLOB、FORMLINE 等。如果您能為這些運算定義語義,請隨時增強 Storable 以便它能處理它們。
除非您將 $Storable::forgive_me
設定為某個 TRUE
值,否則儲存函數會在遇到此類參考時 croak
。在這種情況下,致命訊息會轉換為警告,並會儲存一些無意義的字串。
設定 $Storable::canonical
可能不會產生相等的凍結字串,因為數字可能會字串化。當存在純量字串版本時,它就是儲存的形式;因此,如果您在同一個資料結構的兩個凍結運算之間將數字用作字串,您將會得到不同的結果。
在網路順序中儲存雙倍精度浮點數時,它們的值會儲存為文字。但是,您也不應該期望非數字浮點數值(例如無限大和「不是數字」)能順利通過 nstore()/retrieve() 配對。
由於 Storable 既不了解也不關心字元集(儘管它知道字元可能超過八位元寬),因此主機和目標系統之間的字元碼解譯有任何差異都是您的問題。特別是,如果主機和目標使用不同的碼點來表示浮點數文字表示式中使用的字元,您將無法交換浮點數資料,即使使用 nstore() 也是如此。
Storable::drop_utf8
是一個粗暴的工具。沒有任何功能可以將所有字串作為 utf8 序列傳回,或嘗試將 utf8 資料轉換回 8 位元,如果轉換失敗則 croak()
。
在 Storable 2.01 之前,儲存時未區分有號和無號整數。預設情況下,Storable 偏好儲存一個純量字串表示(如果它有一個),所以這只會在儲存從未轉換為字串或浮點數的大型無號整數時造成問題。換句話說,這些值是由整數運算(例如邏輯運算)產生的,然後在儲存之前未在任何字串或算術上下文中使用。
如果您有現有資料是由 Storable 2.02 或更早版本寫入,且是在 Unix 或 Linux 上的 perl 5.6.0 或 5.6.1 中,而該系統已設定為支援 64 位元整數(非預設),則本節才適用於您。如果您取得的是預編譯的 perl,而不是執行 Configure 從原始碼建置自己的 perl,那麼這幾乎肯定不會影響您,您可以立即停止閱讀(除非您感到好奇)。如果您在 Windows 上使用 perl,則這不會影響您。
Storable 會寫入一個檔案標頭,其中包含建置 Storable 的 C 編譯器各種 C 語言類型的大小(當不以網路順序寫入時),並且會拒絕載入並非在相同(或相容)架構上由 Storable 寫入的檔案。需要進行此檢查和機器位元組順序檢查,因為檔案中各種欄位的尺寸是由 C 語言類型的尺寸給定的,因此在不同架構上寫入的檔案不相容。這是為了提高速度而進行的。(當以網路順序寫入時,所有欄位都會寫成標準長度,這允許完全互通,但讀寫時間較長)
Perl 5.6.x 引入了選擇性設定 perl 解譯器使用 C 的 long long
型態,以允許純量在 32 位元系統中儲存 64 位元整數。然而,由於 Perl 設定系統在非 Windows 平台上產生 C 設定檔的方式,以及 Storable 產生標頭的方式,Storable 檔案標頭中並未反映 perl 寫入時使用 32 或 64 位元整數,儘管 Storable 以不同的方式儲存檔案中的某些資料。因此,在使用 64 位元整數的 perl 上執行 Storable 時,將從由 32 位元 perl 寫入的檔案中讀取標頭,不會發現資料實際上是以微妙的不相容格式儲存,然後在遇到儲存的整數時會發生嚴重的錯誤(可能會崩潰)。這是一個設計缺陷。
Storable 現已變更為寫入和讀取包含整數大小資訊的檔案標頭。無法偵測正在讀取的舊檔案是使用 32 或 64 位元整數寫入的(它們具有相同的標頭),因此無法自動切換到正確的向下相容模式。因此,此 Storable 預設為新的正確行為。
這表示,如果您有由 Storable 1.x 寫入的資料,在設定為在 Unix 或 Linux 上使用 64 位元整數的 perl 5.6.0 或 5.6.1 上執行,則預設情況下,此 Storable 將拒絕讀取它,並傳回錯誤 位元組順序不相容。如果您有此類資料,則應將 $Storable::interwork_56_64bit
設為 true 值,以讓此 Storable 讀取和寫入具有舊標頭的檔案。您還應將資料或任何您正在通訊的較舊 perl 移轉到此目前版本的 Storable。
如果您沒有使用上述 Perl 特定組態寫入的資料,那麼您不應該做任何事。不要設定標記 - 不僅是相同組態的 Storable Perl 會拒絕載入它們,而且不同組態的 Storable Perl 會載入它們,並相信它們是正確的,然後可能會在讀取它們的過程中失敗或崩潰。
感謝(按時間順序)
Jarkko Hietaniemi <jhi@iki.fi>
Ulrich Pfeifer <pfeifer@charly.informatik.uni-dortmund.de>
Benjamin A. Holzman <bholzman@earthlink.net>
Andrew Ford <A.Ford@ford-mason.co.uk>
Gisle Aas <gisle@aas.no>
Jeff Gresham <gresham_jeffrey@jpmorgan.com>
Murray Nesbitt <murray@activestate.com>
Marc Lehmann <pcg@opengroup.org>
Justin Banks <justinb@wamnet.com>
Jarkko Hietaniemi <jhi@iki.fi> (AGAIN, as perl 5.7.0 Pumpkin!)
Salvador Ortiz Garcia <sog@msg.com.mx>
Dominic Dunlop <domo@computer.org>
Erik Haugan <erik@solbors.no>
Benjamin A. Holzman <ben.holzman@grantstreet.com>
Reini Urban <rurban@cpan.org>
Todd Rinaldo <toddr@cpanel.net>
Aaron Crane <arc@cpan.org>
提供錯誤報告、建議和貢獻。
Benjamin Holzman 貢獻了綁定變數支援,Andrew Ford 貢獻了雜湊的正規順序,Gisle Aas 修正了我對 Perl 內部的一些誤解,並透過計算物件數量而非標記它們來最佳化輸出串流中「標籤」的發射(導致 Storable 映像從 0.6 版本開始的二進位不相容性 - 當然,舊的映像仍然可以正確理解)。Murray Nesbitt 讓 Storable 具備執行緒安全性。Marc Lehmann 加入重載和對綁定項目的參考支援。Benjamin Holzman 為重載類別加入效能改善;感謝 Grant Street Group 支付費用。Reini Urban 從 p5p 接手維護,並加入安全性修正和大型物件支援。
Storable 由 Raphael Manfredi <Raphael_Manfredi@pobox.com> 編寫。維護工作目前由 cperl http://perl11.org/cperl 負責。
請透過電子郵件向我們反映問題、錯誤修正、意見和抱怨,不過如果您有讚美,您應該寄給 Raphael。請不要透過電子郵件向 Raphael 反映問題,因為他不再負責 Storable,而您的訊息會在他轉寄給我們時延遲。