2014-11-26 2 views
3

Я хочу прочитать содержимое двоичного файла, выполнить двоичный NOT на каждом байте в буфере файлов, а затем записать измененный буфер обратно в другой файл на диске. В настоящее время я использую примерно следующее:Быстрая и надежная техника для изменения байтов буфера в Ruby?

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 напрямую, не прибегая к необходимости распаковывать его в массив (я решил сделать это, чтобы я мог использовать карту! Для изменения байтов).

Поскольку это необходимо выполнить на 100 тысячах файлов (все файлы имеют размер < = 30 МБ). Вышеупомянутое решение работает нормально, но не является надежным из-за проблемы с памятью. Я полагаю, что избегать распаковки и изменения буфера данных напрямую может избежать этого.

Может ли кто-нибудь улучшить мое существующее решение? Огромное спасибо.

ответ

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

Я пытался каждый раз читать 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 

Было бы хорошо, если бы вы могли проверить его в своей среде, и скажите мне, как оказалось впоследствии.

Смежные вопросы