PStore
class PStore
父类:对象
PStore基于Hash实现基于文件的持久性机制。用户代码可以按名称(键)将Ruby对象的层次结构(值)存储到数据存储文件中。一个对象层次可能只是一个单一的对象。用户代码稍后可以根据需要从数据存储中读取值或甚至更新数据。
事务行为确保任何更改一起成功或失败。这可以用来确保数据存储不会处于暂时状态,其中一些值已更新,而另一些则未更新。
在幕后,Ruby对象通过Marshal存储到数据存储文件中。这具有通常的限制。例如,Proc对象不能编组。
用法示例:
require "pstore"
# a mock wiki object...
class WikiPage
def initialize( page_name, author, contents )
@page_name = page_name
@revisions = Array.new
add_revision(author, contents)
end
attr_reader :page_name
def add_revision( author, contents )
@revisions << { :created => Time.now,
:author => author,
:contents => contents }
end
def wiki_page_references
[@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
end
# ...
end
# create a new page...
home_page = WikiPage.new( "HomePage", "James Edward Gray II",
"A page about the JoysOfDocumentation..." )
# then we want to update page data and the index together, or not at all...
wiki = PStore.new("wiki_pages.pstore")
wiki.transaction do # begin transaction; do all of this or none of it
# store page...
wiki[home_page.page_name] = home_page
# ensure that an index has been created...
wiki[:wiki_index] ||= Array.new
# update wiki index...
wiki[:wiki_index].push(*home_page.wiki_page_references)
end # commit changes to wiki data store file
### Some time later... ###
# read wiki data...
wiki.transaction(true) do # begin read-only transaction, no changes allowed
wiki.roots.each do |data_root_name|
p data_root_name
p wiki[data_root_name]
end
end
交易模式
默认情况下,只有操作系统(和底层硬件)不会引发任何意外的I / O错误,才能确保文件完整性。如果在PStore写入文件时发生I / O错误,则文件将被损坏。
你可以通过设置pstore.ultra_safe = true
来防止这种情况
。但是,这会导致性能下降,只能在支持原子文件重命名的平台上运行。ultra_safe
有关详细信息,请参阅文档。
不用说,如果您使用PStore存储有价值的数据,那么您应该不时备份PStore文件。
常量
CHECKSUM_ALGO
用于解除Ruby垃圾收集器的常量。
EMPTY_MARSHAL_CHECKSUM EMPTY_MARSHAL_DATA EMPTY_STRING RDWR_ACCESS RD_ACCESS WR_ACCESS
属性
ultra_safeRW
即使在出现不太可能发生的错误情况(如空间不足条件和其他异常的操作系统文件系统错误)时,PStore是否应尽全力防止文件损坏。设置这个标志的价格是以性能损失的形式出现的。
此标志只对文件重命名为原子的平台有效(例如所有POSIX平台:Linux,MacOS X,FreeBSD等)。默认值是false。
公共类方法
new(file, thread_safe = false) Show source
要构建PStore对象,请传入您希望存储数据的文件
路径。
PStore对象总是可重入的。但是,如果将thread_safe
设置为true,那么它将成为线程安全的代价是性能较低
。
# File lib/pstore.rb, line 118
def initialize(file, thread_safe = false)
dir = File::dirname(file)
unless File::directory? dir
raise PStore::Error, format("directory %s does not exist", dir)
end
if File::exist? file and not File::readable? file
raise PStore::Error, format("file %s not readable", file)
end
@filename = file
@abort = false
@ultra_safe = false
@thread_safe = thread_safe
@lock = Thread::Mutex.new
end
公共实例方法
Show source
按名称
从PStore文件数据中检索一个值。将以该根名称
存储的Ruby对象的层次结构将被返回。
警告
:此方法仅在#transaction中有效。如果在任何其他时间调用它将引发PStore :: Error。
# File lib/pstore.rb, line 154
def [](name)
in_transaction
@table[name]
end
[]=(name, value) Show source
将单个Ruby对象或Ruby对象的层次结构存储在数据存储文件的根名下
。为数据存储中已有的名称
指定clobbers旧数据。
例:
require "pstore"
store = PStore.new("data_file.pstore")
store.transaction do # begin transaction
# load some data into the store...
store[:single_object] = "My data..."
store[:obj_hierarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
"James Gray" => ["erb.rb", "pstore.rb"] }
end # commit changes to data store file
警告
:此方法仅在#transaction中有效,且不能为只读。如果在任何其他时间调用它将引发PStore :: Error。
# File lib/pstore.rb, line 199
def []=(name, value)
in_transaction_wr
@table[name] = value
end
abort() Show source
结束当前的#事务,放弃对数据存储的任何更改。
例:
require "pstore"
store = PStore.new("data_file.pstore")
store.transaction do # begin transaction
store[:one] = 1 # this change is not applied, see below...
store[:two] = 2 # this change is not applied, see below...
store.abort # end transaction here, discard all changes
store[:three] = 3 # this change is never reached
end
警告
:此方法仅在#transaction中有效。如果在任何其他时间调用它将引发PStore :: Error。
# File lib/pstore.rb, line 287
def abort
in_transaction
@abort = true
throw :pstore_abort_transaction
end
commit() Show source
结束当前的#事务,立即向数据存储提交任何更改。
例:
require "pstore"
store = PStore.new("data_file.pstore")
store.transaction do # begin transaction
# load some data into the store...
store[:one] = 1
store[:two] = 2
store.commit # end transaction here, committing changes
store[:three] = 3 # this change is never reached
end
警告
:此方法仅在#transaction中有效。如果在任何其他时间调用它将引发PStore :: Error。
# File lib/pstore.rb, line 261
def commit
in_transaction
@abort = false
throw :pstore_abort_transaction
end
delete(name) Show source
从名称中
删除数据存储中的对象层次结构。
警告
:此方法仅在#transaction中有效,且不能为只读。如果在任何其他时间调用它将引发PStore :: Error。
# File lib/pstore.rb, line 209
def delete(name)
in_transaction_wr
@table.delete name
end
fetch(name, default=PStore::Error) Show source
这个方法就像#[]一样,除此之外你也可以为对象提供一个默认值
。如果在数据存储中未找到指定的名称
,则将返回默认值
。如果不指定默认值
,则在未找到对象时会引发PStore :: Error。
警告
:此方法仅在#transaction中有效。如果在任何其他时间调用它将引发PStore :: Error。
# File lib/pstore.rb, line 168
def fetch(name, default=PStore::Error)
in_transaction
unless @table.key? name
if default == PStore::Error
raise PStore::Error, format("undefined root name `%s'", name)
else
return default
end
end
@table[name]
end
path() Show source
返回数据存储文件的路径。
# File lib/pstore.rb, line 235
def path
@filename
end
root?(name) Show source
如果提供的名称
当前位于数据存储中,则返回true 。
警告
:此方法仅在#transaction中有效。如果在任何其他时间调用它将引发PStore :: Error。
# File lib/pstore.rb, line 230
def root?(name)
in_transaction
@table.key? name
end
roots() Show source
返回存储中当前所有对象层次结构的名称。
警告
:此方法仅在#transaction中有效。如果在任何其他时间调用它将引发PStore :: Error。
# File lib/pstore.rb, line 220
def roots
in_transaction
@table.keys
end
transaction(read_only = false) { |pstore| ... } Show source
打开数据存储的新事务。在传递给此方法的块内部执行的代码可以从数据存储文件读取数据并将其写入数据存储文件。
在块的末尾,更改将自动提交到数据存储。您可以通过调用#commit或#abort提前退出事务。有关如何处理更改的详细信息,请参阅这些方法。在块中提高未捕获的异常等同于调用#abort。
如果read_only
设置为true
,则只能在事务处理期间从数据存储区读取数据,并且任何尝试更改数据的操作都将引发PStore :: Error。
请注意,PStore不支持嵌套事务。
# File lib/pstore.rb, line 310
def transaction(read_only = false) # :yields: pstore
value = nil
if !@thread_safe
raise PStore::Error, "nested transaction" unless @lock.try_lock
else
begin
@lock.lock
rescue ThreadError
raise PStore::Error, "nested transaction"
end
end
begin
@rdonly = read_only
@abort = false
file = open_and_lock_file(@filename, read_only)
if file
begin
@table, checksum, original_data_size = load_data(file, read_only)
catch(:pstore_abort_transaction) do
value = yield(self)
end
if !@abort && !read_only
save_data(checksum, original_data_size, file)
end
ensure
file.close
end
else
# This can only occur if read_only == true.
@table = {}
catch(:pstore_abort_transaction) do
value = yield(self)
end
end
ensure
@lock.unlock
end
value
end
私有实例方法
empty_marshal_checksum() Show source
# File lib/pstore.rb, line 487
def empty_marshal_checksum
EMPTY_MARSHAL_CHECKSUM
end
empty_marshal_data() Show source
# File lib/pstore.rb, line 484
def empty_marshal_data
EMPTY_MARSHAL_DATA
end
in_transaction() Show source
如果调用代码不在#transaction中,则引发PStore :: Error。
# File lib/pstore.rb, line 134
def in_transaction
raise PStore::Error, "not in transaction" unless @lock.locked?
end
in_transaction_wr() Show source
如果调用代码不在#transaction中,或者代码是只读的#transaction,则引发PStore :: Error。
# File lib/pstore.rb, line 141
def in_transaction_wr
in_transaction
raise PStore::Error, "in read-only transaction" if @rdonly
end
load_data(file, read_only) Show source
加载给定的PStore文件。如果read_only
为true,则将返回未编组哈希。如果read_only
为false,则将返回一个三元组:数组的哈希值,数据的校验和以及数据的大小。
# File lib/pstore.rb, line 398
def load_data(file, read_only)
if read_only
begin
table = load(file)
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
rescue EOFError
# This seems to be a newly-created file.
table = {}
end
table
else
data = file.read
if data.empty?
# This seems to be a newly-created file.
table = {}
checksum = empty_marshal_checksum
size = empty_marshal_data.bytesize
else
table = load(data)
checksum = CHECKSUM_ALGO.digest(data)
size = data.bytesize
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
end
data.replace(EMPTY_STRING)
[table, checksum, size]
end
end
on_windows?() Show source
# File lib/pstore.rb, line 426
def on_windows?
is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
self.class.__send__(:define_method, :on_windows?) do
is_windows
end
is_windows
end
open_and_lock_file(filename, read_only) Show source
打开指定的文件名(以只读模式或读写模式)并将其锁定以便读取或写入。
打开的File对象将被返回。如果read_only
为true,并且文件不存在,则返回nil。
所有异常都会传播。
# File lib/pstore.rb, line 373
def open_and_lock_file(filename, read_only)
if read_only
begin
file = File.new(filename, RD_ACCESS)
begin
file.flock(File::LOCK_SH)
return file
rescue
file.close
raise
end
rescue Errno::ENOENT
return nil
end
else
file = File.new(filename, RDWR_ACCESS)
file.flock(File::LOCK_EX)
return file
end
end
save_data(original_checksum, original_file_size, file) Show source
# File lib/pstore.rb, line 434
def save_data(original_checksum, original_file_size, file)
new_data = dump(@table)
if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
if @ultra_safe && !on_windows?
# Windows doesn't support atomic file renames.
save_data_with_atomic_file_rename_strategy(new_data, file)
else
save_data_with_fast_strategy(new_data, file)
end
end
new_data.replace(EMPTY_STRING)
end
save_data_with_atomic_file_rename_strategy(data, file) Show source
# File lib/pstore.rb, line 449
def save_data_with_atomic_file_rename_strategy(data, file)
temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
temp_file = File.new(temp_filename, WR_ACCESS)
begin
temp_file.flock(File::LOCK_EX)
temp_file.write(data)
temp_file.flush
File.rename(temp_filename, @filename)
rescue
File.unlink(temp_file) rescue nil
raise
ensure
temp_file.close
end
end
save_data_with_fast_strategy(data, file) Show source
# File lib/pstore.rb, line 465
def save_data_with_fast_strategy(data, file)
file.rewind
file.write(data)
file.truncate(data.bytesize)
end