深入探索

Marshal 版本号

Marshal 库(一个名为 ‘marshal.c’ 的 C 语言文件)的嵌入式文档说明如下:

编排(Marshaled)数据具有与对象信息一起存储的主要(major)和次要(minor)版本号。 在正常使用中,编排只能加载使用相同主版本号和相同或较低版本号编写的数据。

这显然提出了通过编排(marshaling)创建的数据文件格式可能与当前 Ruby 应用程序不兼容的潜在问题。另外地,Marshal 版本号不依赖于 Ruby 版本号,因此仅基于 Ruby 版本进行兼容性假设是不安全的。

这种不兼容的可能性意味着我们应该尝试在加载已保存数据之前检查其版本号。但是我们如何获得版本号呢?嵌入式文档再一次提供了线索。它指出:

你可以通过读取编排(marshaled )数据的前两个字节来提取版本号。

它提供了这个示例:

  1. str = Marshal.dump("thing")
  2. RUBY_VERSION #=> "1.8.0"
  3. str[0] #=> 4
  4. str[1] #=> 8

好的,让我们在一段完整的代码中尝试这一点。开始…

version_m.rb
  1. x = Marshal.dump( "hello world" )
  2. print( "Marshal version: #{x[0]}:#{x[1]}\n" )

打印出:

  1. "Marshal version: 4:8"

当然,如果你使用的是不同版本的 Marshal 库,则显示的数字会有所不同。在上面的代码中,x 是一个字符串,它的前两个字节是主要和次要版本号。Marshal 库还声明了两个常量 MAJOR_VERSIONMINOR_VERSION,它们存储了当前正在使用的 Marshal 库的版本号。因此,乍一看,似乎很容易将保存数据的版本号与当前版本号进行比较。

只有一个问题:当你将数据保存到磁盘上的文件中时,dump 方法接受 的是IO 或 File 对象,它返回 IO(或 File)对象而不是字符串:

version_error.rb
  1. f = File.open( 'friends.sav', 'w' )
  2. x = Marshal.dump( ["fred", "bert", "mary"], f )
  3. f.close #=> x is now: #<File:friends.sav (closed)>

如果你现在尝试获取 x[0]x[1] 的值,你将收到错误消息。从文件加载数据不再具有意义:

  1. File.open( 'friends.sav' ){ |f|
  2. x = Marshal.load(f)
  3. }
  4. puts( x[0] )
  5. puts( x[1] )

这里的两个 puts 语句没有(如我希望)打印出编排(marshaled)数据的主要和次要版本号;事实上,它们打印出了名称,”fred” 和 “bert”,即从数据文件 ‘friends.sav’ 加载到数组 x 中的前两项。

那么我们如何才能从保存的数据中获取版本号?我必须承认,我被迫在 marshal.c 中的 C 代码中获取可能的方式(不是我最喜欢的活动!)并检查保存的文件中的十六进制数据以便弄清楚这一点。事实证明,正如文档所述,你可以通过读取编排(marshaled)数据的前两个字节来提取版本号。但是,你不适合这么做。你必须明确地读取这些数据 - 像这样:

  1. f = File.open('test2.sav')
  2. vMajor = f.getc()
  3. vMinor = f.getc()
  4. f.close

这里,getc 方法从输入流读取下一个 8 位字节。我的示例项目 version_m2.rb 给出了一种简单的方法,可以将保存数据的版本号与当前 Marshal 库的版本号进行比较,以确定在尝试重新加载数据之前数据格式是否可能兼容。

version_m2.rb
  1. if vMajor == Marshal::MAJOR_VERSION then
  2. puts( "Major version number is compatible" )
  3. if vMinor == Marshal::MINOR_VERSION then
  4. puts( "Minor version number is compatible" )
  5. elsif vMinor < Marshal::MINOR_VERSION then
  6. puts( "Minor version is lower - old file format" )
  7. else
  8. puts( "Minor version is higher - newer file format" )
  9. end
  10. else
  11. puts( "Major version number is incompatible" )
  12. end