ruby way之高级数据存取

本文介绍了多种Ruby语言中实现数据持久化的方法,包括简单的Marshal序列化、使用PStore进行复杂对象存储、处理CSV数据、利用YAML进行序列化、Madeleine进行对象Prevalence以及使用DBM库。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经常我们需要以一种更透明的方式来存储和得到数据.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的格式的标准:

[quote]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)[/quote]

让我们以创建一个文件开始,为了写一个逗号分割的数据,我们可以简单的以写模式打开文件:

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文件。

[quote]"name","age","salary"
"mark",29,34500
"joe",42,32000
"fred",22,22000
"jake",25,24000
"don",32,52000
[/quote]

下面的程序负责读取:

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 的文件:
[quote]---
- "Hello, world"
- 237
-
- Jan
- Feb
- Mar
- Apr
-
just a: hash.
This: is[/quote]

如果我们现在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对象差不多,想了解它的具体的方法可以去看文档.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值