我希望读取二进制文件的内容,对文件缓冲区中的每个字节执行二进制NOT,然后将修改后的缓冲区写回磁盘上的另一个文件。我目前使用的内容如下:
data = nil
::File.open( 'somefile.bin', 'rb' ) do | f |
data = f.read( f.stat.size )
end
# unpack can sometimes throw an out of memory exception
raw_bytes = data.unpack( 'C*' )
raw_bytes.map! do | byte |
~byte
end
::File.open( 'somefile.bin.not', 'wb' ) do | f |
f.write( raw_bytes.pack( 'C*' ) )
end
这样可行,但解压缩有时会抛出内存不足异常。是否可以直接编辑data
缓冲区而无需将其解压缩到数组(我选择这样做,所以我可以使用map!来修改字节)。
由于需要对数以千计的文件执行(所有文件大小均为<= 30MB),因此性能非常重要。上述解决方案执行正常但由于内存不足问题而不可靠。我相信避免直接解包和修改数据缓冲区可能会避免这种情况。
任何人都可以改进我现有的解决方案吗?非常感谢。
答案 0 :(得分:4)
我认为避免直接解包和修改数据缓冲区可能会避免这种情况。
您的数据缓冲区是二进制字符串,即0x00到0xFF范围内的字符序列。您可以通过将字符映射到反转范围0xFF到0x00来翻转字符的位:
0x00 (00000000) -> 0xFF (11111111)
0x01 (00000001) -> 0xFE (11111110)
0x02 (00000010) -> 0xFD (11111101)
0x03 (00000011) -> 0xFC (11111100)
...
0x7E (01111110) -> 0x81 (10000001)
0x7F (01111111) -> 0x80 (10000000)
0x80 (10000000) -> 0x7F (01111111)
0x81 (10000001) -> 0x7E (01111110)
...
0xFC (11111100) -> 0x03 (00000011)
0xFD (11111101) -> 0x02 (00000010)
0xFE (11111110) -> 0x01 (00000001)
0xFF (11111111) -> 0x00 (00000000)
应用字符到字符映射的最快方法可能是String#tr
。您只需传递两个字符串a
和b
,tr
将a
中的所有字符替换为b
中的相应字符。
a = (0..255).map(&:chr).join #=> "\x00\x01\x02...\xFD\xFE\xFF"
b = a.reverse #=> "\xFF\xFE\xFD...\x02\x01\x00"
由于"-"
和"\\"
在tr
中具有特殊含义,因此必须对其进行转义:
a.gsub!(/[\\-]/, '\\\\\0')
b.gsub!(/[\\-]/, '\\\\\0')
让我们看看它的表现如何:
require 'benchmark'
@data = IO.read('/dev/random', 30_000_000)
@a = (0..255).map(&:chr).join
@b = @a.reverse
@a.gsub!(/[\\-]/, '\\\\\0')
@b.gsub!(/[\\-]/, '\\\\\0')
Benchmark.bm(5) do |x|
x.report("pack:") { @data.unpack('C*').map(&:~).pack('C*') }
x.report("tr:") { @data.tr(@a, @b) }
end
结果:
user system total real
pack: 4.780000 0.150000 4.930000 ( 5.082274)
tr: 0.070000 0.000000 0.070000 ( 0.078761)
答案 1 :(得分:1)
我每次尝试读取1mb,而不是将所有内容存储到内存中。在测试中我没有一个版本崩溃,所以我不能确定这个版本不会崩溃,但很有可能它不会失败。根据我的测试结果,作为奖励,我还设法获得了5%的适度性能提升(不要问我xD如何)。这是:
File.open( 'somefile.bin', 'rb' ) do | file |
File.open( 'somefile.bin.not', 'wb' ) do | out |
until file.eof?
buffer = file.read( 1024*1024 ).unpack( 'C*' ).map do | byte |
~byte
end
out.write( buffer.pack( 'C*' ) )
end
end
end
如果您可以在您的环境中测试它并告诉我之后的结果会很好。