Ruby's pack и unpack объяснили

даже после прочтения документации, я до сих пор не могу понять, как Руби Array#pack и String#unpack точно работают. Вот пример, который вызывает у меня больше всего проблем:

irb(main):001:0> chars = ["61","62","63"]
=> ["61", "62", "63"]
irb(main):002:0> chars.pack("H*")
=> "a"
irb(main):003:0> chars.pack("HHH")
=> "```"

Я ожидал, что обе эти операции вернут один и тот же результат: "abc". Каждый из них" терпит неудачу " по-разному (не совсем неудачу, так как я, вероятно, ожидаю неправильной вещи). Итак, два вопроса:

  1. какова логика этих выходы?
  2. как я могу достичь желаемого эффекта, т. е. преобразовать последовательность шестнадцатеричных чисел в соответствующую строку. Еще лучше-учитывая целое число n, как преобразовать его в строку, идентичную текстовому файлу, который при рассмотрении в качестве числа (скажем, в шестнадцатеричном редакторе) равен n?

4 ответов


мы работаем над аналогичной проблемой сегодня утром. Если размер массива неизвестен, вы можете использовать:

ary = ["61", "62", "63"]
ary.pack('H2' * ary.size)
=> "abc"

Вы можете отменить его с помощью:

str = "abc"
str.unpack('H2' * str.size)
=> ["61", "62", "63"]

на Array#pack метод довольно загадочный. Обращаясь к вопросу (2), я смог заставить ваш пример работать, сделав следующее:

> ["61", "62", "63"].pack("H2H2H2")
=> "abc" 

посмотреть Ruby documentation для аналогичного примера. Вот более общий способ сделать это:

["61", "62", "63"].map {|s| [s].pack("H2") }.join

Это, наверное, не самый эффективный способ решения вашей проблемы; я подозреваю, что есть лучший путь, но это помогло бы знать, что именно вы начинаете.

на #pack метод общим для других языков, таких как Perl. Если документация Ruby не помогает, вы можете обратиться к аналогичной документации в другом месте.


на 'H' директива строку Array#pack говорит, что содержимое массива следует интерпретировать как кусочки шестнадцатеричных строк.

в первом примере вы указали:

irb(main):002:0> chars.pack("H*")
=> "a"

вы говорите, чтобы собрать первый элемент массива, как если бы это была последовательность полубайтов (половина байта) hex-строки: 0x61 в этом случае, что соответствует 'a' символ ASCII.

во втором примере:

irb(main):003:0> chars.pack("HHH")
=> "```"

вы говорите, чтобы упакуйте 3 элемента массива, как если бы они были кусочками (высокая часть в этом случае):0x60 соответствует '`' символ ASCII. Нижняя часть или второй укус (0x01)" теряется "из-за отсутствия модификаторов"2" или " * " для "aTemplateString".

что нужно:

chars.pack('H*' * chars.size)

для того чтобы упаковать все грызет всех элементов массива, как если бы они были шестнадцатеричных строк.

случае 'H2' * char.size отлично работает, только если элементы массива представление 1 байта только шестнадцатеричных строк.

это означает, что что-то вроде chars = ["6161", "6262", "6363"] будет неполным:

2.1.5 :047 > chars = ["6161", "6262", "6363"]
 => ["6161", "6262", "6363"] 
2.1.5 :048 > chars.pack('H2' * chars.size)
 => "abc" 

в то время как:

2.1.5 :049 > chars.pack('H*' * chars.size)
 => "aabbcc"

Я ожидал, что обе эти операции вернут один и тот же результат: "abc".

самый простой способ понять, почему ваш подход не работает, можно просто начать с того, что вы ожидаете:

"abc".unpack("H*")
# => ["616263"]

["616263"].pack("H*")
# => "abc"

Итак, кажется, что Ruby ожидает ваши шестнадцатеричные байты в одной длинной строке вместо отдельных элементов массива. Таким образом, самый простой ответ на ваш первоначальный вопрос будет следующим:

chars = ["61", "62", "63"]
[chars.join].pack("H*")
# => "abc"

этот подход также представляется выполните сравнительно хорошо для больших входных данных:

require 'benchmark'

chars = ["61", "62", "63"] * 100000

Benchmark.bmbm do |bm|
  bm.report("join pack") do [chars.join].pack("H*") end
  bm.report("big pack") do chars.pack("H2" * chars.size) end
  bm.report("map pack") do chars.map{ |s| [s].pack("H2") }.join end
end

#                 user     system      total        real
# join pack   0.030000   0.000000   0.030000 (  0.025558)
# big pack    0.030000   0.000000   0.030000 (  0.027773)
# map pack    0.230000   0.010000   0.240000 (  0.241117)