12

即使在阅读了标准文档之后,我仍然无法理解 Ruby 的具体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

4 回答 4

12

今天早上我们正在研究一个类似的问题。如果数组大小未知,可以使用:

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

您可以使用以下方法反转它:

str = "abc"
str.unpack('H2' * str.size)
=> ["61", "62", "63"]
于 2012-12-05T18:41:42.247 回答
9

'H'String 指令Array#pack表示数组内容应该被解释为十六进制字符串的半字节。

在您提供的第一个示例中:

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

您要打包数组的第一个元素,就好像它是十六进制字符串的半字节(半字节)序列:0x61在这种情况下,它对应于'a'ASCII 字符。

在第二个例子中:

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

您要打包数组的 3 个元素,就好像它们是半字节一样(本例中的高部分):0x60对应于'`'ASCII 字符。0x01由于缺少“aTemplateString”的“2”或“*”修饰符,低位或第二个半字节 ( ) “丢失”。

你需要的是:

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"
于 2015-01-29T14:16:45.077 回答
5

Array#pack方法非常神秘。解决问题(2),我可以通过这样做让你的例子起作用:

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

有关类似示例,请参阅Ruby 文档。这是一种更通用的方法:

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

这可能不是解决问题的最有效方法;我怀疑有更好的方法,但它有助于了解您开始使用哪种输入。

#pack方法在其他语言中很常见,例如 Perl。如果 Ruby 的文档没有帮助,您可以在别处查阅类似的文档。

于 2012-12-05T18:11:22.067 回答
4

我希望这两个操作都返回相同的输出:“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)
于 2014-10-01T09:00:55.860 回答