Почему демпинг и загрузка хэша с использованием маршала в Ruby бросают FormatError?

Я запускаю Ruby, установленный из RubyInstaller. Здесь версия:

C:\Users\Sathya>ruby -v
ruby 1.9.2p290 (2011-07-09) [i386-mingw32]

Вот точный код, который выдает ошибку:

hashtime = Hash.new(Time.mktime('1970'))
hashtime[1]  = Time.now
=> 2011-10-04 19:26:53 +0530
print hashtime
{1=>2011-10-04 19:26:53 +0530}=> nil
hashtime[1]  = Time.now
=> 2011-10-04 19:27:20 +0530
print hashtime
{1=>2011-10-04 19:27:20 +0530}=> nil
File.open('timehash','w') do |f|
  f.write Marshal.dump(hashtime)
end
=> 56  

Теперь, пытаясь загрузить его.

Marshal.load (File.read('timehash'))

Дает ошибку:

ArgumentError: dump format error for symbol(0x42)
        from (irb):10:in `load'
        from (irb):10
        from C:/Ruby192/bin/irb:12:in `<main>'

Почему это бросает ошибку? Я что-то делаю неправильно, или это ошибка?

Я работаю на Windows 7 Ultimate, 64-разрядный


Здесь приведены результаты отредактированного кода отладки:

hashtime = Hash.new
=> {}
hashtime[1] = Time.now
=> 2011-10-04 20:49:52 +0530
hashdump = Marshal.dump(hashtime)
=> "\x04\b{\x06i\x06Iu:\tTime\r\x8F\xE4\e\x80<\xADGO\x06:\voffseti\x02XM"
hashtime = Marshal.load (hashdump)
=> {1=>2011-10-04 20:49:52 +0530}
print hashtime
{1=>2011-10-04 20:49:52 +0530}=> nil  

Результаты для редактирования 2:

hashtime = Hash.new
=> {}
hashtime[1] = Time.now
=> 2011-10-04 21:04:24 +0530
hashdump = Marshal.dump(hashtime)
=> "\x04\b{\x06i\x06Iu:\tTime\r\x8F\xE4\e\x80\x92o\x8C\x89\x06:\voffseti\x02XM"
print "hashdump: #{hashdump}"
ÅS?ÇÆoîë?:?offseti?XM=> nile
File.open('timehash','w') do |f|
 f.write hashdump
end
=> 36
hashdump2 = File.read('timehash')
=> "\x04\b{\x06i\x06Iu:\tTime\n\x8F\xE4\e\x80\x92o\x8C\x89\x06:\voffseti\x02XM"
print "hashdump2: #{hashdump2}"
hashdump2:{?i?Iu:       Time
ÅS?ÇÆoîë?:?offseti?XM=> nil
hashtime2 = Marshal.load (hashdump2)
ArgumentError: dump format error for symbol(0x8c)
        from (irb):73:in `load'
        from (irb):73
        from C:/Ruby192/bin/irb:12:in `<main>'  

Некоторые персонажи не вышли, вот скриншот:

hash-dump-marshal


Теперь я получаю формат времени с разной ошибкой

hashtime = Hash.new
=> {}
hashtime[1] = Time.now
=> 2011-10-04 21:23:15 +0530
hashdump = Marshal.dump(hashtime)
=> "\x04\b{\x06i\x06Iu:\tTime\r\x8F\xE4\e\x80\xB9\xE1\xFB\xD4\x06:\voffseti\x02X
M"
print "hashdump: #{hashdump}"
ÅΣ←Ç╣ß√╘♠:♂offseti☻XM=> nile
File.open('timehash','wb') do |f|
 f.write hashdump
end
=> 36
hashdump2 = File.read('timehash')
=> "\x04\b{\x06i\x06Iu:\tTime\n\x8F\xE4\e\x80\xB9\xE1\xFB\xD4\x06:\voffseti\x02X
M"
print "hashdump2: #{hashdump2}"
hashdump2:{♠i♠Iu:       Time
ÅΣ←Ç╣ß√╘♠:♂offseti☻XM=> nil
hashtime2 = Marshal.load (hashdump2)
TypeError: marshaled time format differ
        from (irb):10:in `_load'
        from (irb):10:in `load'
        from (irb):10
        from C:/Ruby192/bin/irb:12:in `<main>'

Ответ 1

Сочетание двух ответов от @Josh и @derp работает для меня. Вот код (записанный в файл):

hashtime = Hash.new(Time.mktime('1970'))
hashtime[1]  = Time.now
File.open('timehash','wb') do |f|
  f.write Marshal.dump(hashtime)
end
newhash = Marshal.load (File.binread('timehash'))
p newhash
p newhash.default

Результаты в следующем выпуске:

c:\apps\ruby>ruby h.rb
{1=>2011-10-05 08:09:43 +0200}
1970-01-01 00:00:00 +0100

Ответ 2

Вам нужно записать файл в двоичном режиме, добавив b в режим файла:

File.open('timehash','wb') do |f|
  f.write Marshal.dump(hashtime)
end

Вы можете видеть, что это проблема, сравнивая строки (от нашей отладки) до записи на диск и после чтения:

=> "\x04\b{\x06i\x06Iu:\tTime\r\x8F\xE4\e\x80\x92o\x8C\x89\x06:\voffseti\x02XM"
=> "\x04\b{\x06i\x06Iu:\tTime\n\x8F\xE4\e\x80\x92o\x8C\x89\x06:\voffseti\x02XM"
                             ^^

a \r (возврат каретки) изменяется на \n (новая строка)

Однако, похоже, даже с бинарным модификатором ваша система не подчиняется вам и меняет \r на \n... Поэтому попробуйте кодировать данные на base64:

File.open('timehash','w') do |f|
  hashtime_marshal = Marshal.dump(hashtime)
  f.write [hashtime_marshal].pack("m")
end

hashtime_encoded = File.read('timehash')
hashtime = Marshal.load( hashtime_encoded.unpack("m")[0] )

Сообщите мне, если это работает?


Старая информация:

Не передавать ничего в Hash.new:

>> hashtime = Hash.new
=> {}
>> hashtime[1] = Time.now
=> Tue Oct 04 10:57:49 -0400 2011
>> hashtime
=> {1=>Tue Oct 04 10:57:49 -0400 2011}
>> File.open('timehash','w') do |f|
?>   f.write Marshal.dump(hashtime)
>> end
=> 22
>> Marshal.load (File.read('timehash'))
(irb):10: warning: don't put space before argument parentheses
=> {1=>Tue Oct 04 10:57:49 -0400 2011}

В документации указано, что параметр obj для Hash.new является значением по умолчанию... он должен работать так, как у вас есть... Я не знаю, почему это не так... но в вашем случае nil является допустимым значением по умолчанию, просто проверьте, есть ли значения nil, и если да, используйте вместо них Time.mktime('1970').

EDIT:. Это решило проблему для меня, однако я нахожусь на OS X, а не в Windows. Итак, попробуйте немного отладки. Что произойдет, если вы запустите следующий код?

hashtime = Hash.new
hashtime[1] = Time.now
hashdump = Marshal.dump(hashtime)
hashtime = Marshal.load (hashdump)
print hashtime

ИЗМЕНИТЬ №2: ОК. Значит, Marshal.dump и Marshal.load работают. Похоже на что-то с файлом ввода-вывода... Пожалуйста, опубликуйте результаты следующего кода...

hashtime = Hash.new
hashtime[1] = Time.now
hashdump = Marshal.dump(hashtime)
print "hashdump: #{hashdump}"
File.open('timehash','w') do |f|
  f.write hashdump
end
hashdump2 = File.read('timehash')
print "hashdump2: #{hashdump2}"
hashtime2 = Marshal.load (hashdump2)
print hashtime2

Ответ 3

Вместо чтения с помощью File.read попробуйте File.binread или File.open('timehash', 'rb')