Ruby 2.4

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