经常我们需要以一种更透明的方式来存储和得到数据.Marshal模块提供了简单的对象持久化,PStore 库建立在这项技术之上。dbm 库使用像hash一样并永久的存贮在磁盘上.
1简单的Marshaling
经常我们需要创建一个对象,然后为了以后的使用保存它.ruby对这种对象持久化(或者说是Marshaling)提供了基本的支持.Marshal 模块能够使程序员序列化和反序列化一个ruby对象.
# array of elements [composer, work, minutes]
works = [["Leonard Bernstein","Overture to Candide",11],
["Aaron Copland","Symphony No. 3",45],
["Jean Sibelius","Finlandia",20]]
# We want to keep this for later...
File.open("store","w") do |file|
Marshal.dump(works,file)
end
# Much later...
File.open("store") do |file|
works = Marshal.load(file)
end
这里要注意的是这种技术并不是所有的对象能被dump.如果一个对象包含一个低级别的类的对象,他就不能被dump,比如IO、Proc和Binding,单例对象,匿名对象,模块也不能被序列化.
Marshal.dump 还有另外两种参数的形式,如果调用时传入一个参数的话,它将会返回返回一个由字符串表示的数据,其中前两个字节为主版本号和次版本号:
这边的话1.9和1.8的结果是不同的:
s = Marshal.dump(works)
p s[0] # 4
p s[1] # 8
上面的结果是1.8打印出的结果,而1.9中,结果将会是\004和\b,也就是说它已经没有版本号了.
经常你如果想载入这些数据,当且仅当主版本号相同,次版本号相等或者小于时才会load。如果ruby解释器使用了verbose 参数的话,则版本号必须完全匹配.
第三个参数limit 只有当被序列化的对象是一个嵌套对象时才有意义,它就是遍历时的深度,当你的对象的嵌套深度大于等于你的limit时,就会抛出ArgumentError:
File.open("store","w") do |file|
arr = [ ]
Marshal.dump(arr,file,0) # in `dump': exceed depth limit
# (ArgumentError)
Marshal.dump(arr,file,1)
arr = [1, 2, 3]
Marshal.dump(arr,file,1) # in `dump': exceed depth limit
# (ArgumentError)
Marshal.dump(arr,file,2)
arr = [1, [2], 3]
Marshal.dump(arr,file,2) # in `dump': exceed depth limit
# (ArgumentError)
Marshal.dump(arr,file,3)
end
File.open("store") do |file|
p Marshal.load(file) # [ ]
p Marshal.load(file) # [1, 2, 3]
p Marshal.load(file) # arr = [1, [2], 3]
end
第三个参数的默认值是1,-1的话就是不进行深度检测.
2 更复杂的Marshaling
有时候由于一些限制,我们需要定制我们的序列化。通过创建_load和 _dump 方法我们可以做到定制.这两个方法是当序列化完成时调用的.
接下来的例子中,这个人从出生就赚取5%的利息在他的初始帐户中。 我们这里没有保存年龄和当前的余额,这是因为他们都是时间的函数:
class Person
attr_reader :name
attr_reader :age
attr_reader :balance
def initialize(name,birthdate,beginning)
@name = name
@birthdate = birthdate
@beginning = beginning
@age = (Time.now - @birthdate)/(365*86400)
@balance = @beginning*(1.05**@age)
end
def marshal_dump
Struct.new("Human",:name,:birthdate,:beginning)
str = Struct::Human.new(@name,@birthdate,@beginning)
str
end
def marshal_load(str)
self.instance_eval do
initialize(str.name, str.birthdate, str.beginning)
end
end
# Other methods...
end
p1 = Person.new("Rudy",Time.now - (14 * 365 * 86400), 100)
p [p1.name, p1.age, p1.balance] # ["Rudy", 14.0, 197.99315994394]
str = Marshal.dump(p1)
p2 = Marshal.load(str)
p [p2.name, p2.age, p2.balance] # ["Rudy", 14.0, 197.99315994394]
当保存对象时不会计算年龄和当前余额,对象再生的时候,年龄和当前余额会被计算.注意marshal_load 会假设已经存在一个对象,这时就调用initialize就可以了。
3 使用Marshal执行受限制的深度拷贝.
ruby没有深度拷贝的操作,方法dup和clone可能不会是你所想象的那样.一个对象可能包含一个嵌套的对象引用,这时执行一个copy操作,就好像Pick-Up-Sticks的游戏一样.
我们这里提供了一个受限制的深度拷贝,受限制是因为它使用Marshal ,而Marshal是有很多局限的:
def deep_copy(obj)
Marshal.load(Marshal.dump(obj))
end
a = deep_copy(b)
4 使用PStore进行更好的持久化
PStore 库提供给我们基于文件的ruby对象的持久化存储,一个PStore对象能够持有一个ruby对象的一些层次,每一个层次都有一个通过一个key来标识的root.当一个事务开始时从一个磁盘文件读取层次,当事务结束时将其回写:
require "pstore"
# save
db = PStore.new("employee.dat")
db.transaction do
db["params"] = {"name" => "Fred", "age" => 32,
"salary" => 48000 }
end
# retrieve
require "pstore"
db = PStore.new("employee.dat")
emp = nil
db.transaction { emp = db["params"] }
通常我们能够直接传入PStore 对象给一个事务代码块。
在事务的进行中间我们能够通过commit或者abort方法进行终止.前者保持我们做的改变,后者则是放弃改变:
require "pstore"
# Assume existing file with two objects stored
store = PStore.new("objects")
store.transaction do |s|
a = s["my_array"]
h = s["my_hash"]
# Imaginary code omitted, manipulating
# a, h, etc.
# Assume a variable named "condition" having
# the value 1, 2, or 3...
case condition
when 1
puts "Oops... aborting."
s.abort # Changes will be lost.
when 2
puts "Committing and jumping out."
s.commit # Changes will be saved.
when 3
# Do nothing...
end
puts "We finished the transaction to the end."
# Changes will be saved.
end
在事务中,你也可以使用方法roots 来返回一个根的数组,也有一个delete方法来删除一个root:
store.transaction do |s|
list = s.roots # ["my_array","my_hash"]
if s.root?("my_tree")
puts "Found my_tree."
else
puts "Didn't find # my_tree."
end
s.delete("my_hash")
list2 = s.roots # ["my_array"]
end
5 处理CSV数据
这边我们主要介绍FasterCSV库,因为它比起ruby内置的csv库更快.
CSV模块(csv.rb)能够按照csv的格式生成或者解析数据,对于csv的格式并没有一个统一的观点.FasterCSV库的作者定义了自己的csv的格式的标准:
引用
Record separator: CR + LF
Field separator: comma (,)
Quote data with double quotes if it contains CR, LF, or comma
Quote double quote by prefixing it with another double quote ("-> "")
Empty field with quotes means null string (data,"",data)
Empty field without quotes means NULL (data,,data)
让我们以创建一个文件开始,为了写一个逗号分割的数据,我们可以简单的以写模式打开文件:
require 'csv'
CSV.open("data.csv","w") do |wr|
wr << ["name", "age", "salary"]
wr << ["mark", "29", "34500"]
wr << ["joe", "42", "32000"]
wr << ["fred", "22", "22000"]
wr << ["jake", "25", "24000"]
wr << ["don", "32", "52000"]
end
上面的代码生成一个data.csv文件。
引用
"name","age","salary"
"mark",29,34500
"joe",42,32000
"fred",22,22000
"jake",25,24000
"don",32,52000
下面的程序负责读取:
require 'csv'
CSV.open('data.csv', 'r') do |row|
p row
end
# Output:
# ["name", "age", "salary"]
# ["mark", "29", "34500"]
# ["joe", "42", "32000"]
# ["fred", "22", "22000"]
# ["jake", "25", "24000"]
# ["don", "32", "52000"]
更高级的特性可以去ruby-doc.org去看.
6 用YAML进行序列化
YAML 的意思是YAML Ain't Markup Language.他就是一种灵活的适合人阅读的一种存储格式。它和xml很类似,可是更优美.
当我们require yaml后,每个对象都将会有一个to_yaml 方法:
require 'yaml'
str = "Hello, world"
num = 237
arr = %w[ Jan Feb Mar Apr ]
hsh = {"This" => "is", "just a"=>"hash."}
puts str.to_yaml
puts num.to_yaml
puts arr.to_yaml
puts hsh.to_yaml
# Output:
# --- "Hello, world"
# --- 237
# ---
# - Jan
# - Feb
# - Mar
# - Apr
# ---
# just a: hash.
# This: is
to_yaml 方法刚好和YAML.load 方法相反,我么能给他一个字符串或者一个流为参数.
假设我们有一个data.yaml 的文件:
引用
---
- "Hello, world"
- 237
-
- Jan
- Feb
- Mar
- Apr
-
just a: hash.
This: is
如果我们现在load这个流,我们将会得到刚才的数组:
require 'yaml'
file = File.new("data.yaml")
array = YAML.load(file)
file.close
p array
# Output:
# ["Hello, world", 237, ["Jan", "Feb", "Mar", "Apr"],
# {"just a"=>"hash.", "This"=>"is"}]
7 使用Madeleine进行对象的Prevalence
在一些领域,对象的Prevalence很流行,主要的观点是,内存很便宜并且越来越便宜, 而数据库却很小,因此忘掉数据库,保持所有的对象在内存里.
java实现的版本叫做Prevayler,而相应于ruby的版本叫做Madeleine.
Madeleine并不是对每一个人或者每一个程序都适用的.对象的prevalence有它自己的规则和限制。首先所有对象都必须得全部装载到内存里,其次所有的对象都必须能够被序列化(marshalable).
所有的对象都必须是确定的,也就是说有点像数学中的函数,输入相同,输出也一定相同(这意味着使用system clock或者随机数是不可以的).
对象应当尽可能地和所有的IO相隔离,也就是说一般要在prevalence 系统外调用这些IO操作.
最后,每一个改变prevalence 系统的命令都必须像一个命令对象的形式发出.
想研究Madeleine的,只能自己找资料了.
8 使用DBM 库
dbm是一个平台相关的,基于字符串的散列文件存储 机制。它存储一个key和一个和这个key联系在一起的数据,他们都是字符串.
可以看例子:
require 'dbm'
d = DBM.new("data")
d["123"] = "toodle-oo!"
puts d["123"] # "toodle-oo!"
d.close
puts d["123"] # RuntimeError: closed DBM file
e = DBM.open("data")
e["123"] # "toodle-oo!"
w=e.to_hash # {"123"=>"toodle-oo!"}
e.close
e["123"] # RuntimeError: closed DBM file
w["123"] # "toodle-oo!
DBM类mix了Enumerable模块,他的类方法new和open都是singletons:
q=DBM.new("data.dbm") #
f=DBM.open("data.dbm") # Errno::EWOULDBLOCK:
# Try again - "data.dbm"
操作dbm对象和操作hash对象差不多,想了解它的具体的方法可以去看文档.