前回も書いたように Crystal の String のエンコーディングは UTF-8 固定です。なので Ruby のようにバイナリデータを String オブジェクトで扱うことはできません。
バイナリデータは Pointer, Slice, MemoryIO で扱うことができるようです。 自分でもよくわかってなかったので、自分用のメモとしてまとめておきます。
Pointer
最も低レイヤーのクラスです。C のポインタと同じです。
p = Pointer(UInt8).malloc(10) # 10バイト獲得 p[0] = 0xAAu8 p[1] = 'X'.ord.to_u8
x = 123 p = pointerof(x) # x のアドレス p.value #=> 123 p.value = 456 x #=> 456
獲得したメモリ領域を超えてアクセスできてしまうため、簡単にメモリを破壊できるのも C と同じです。
p = Pointer(UInt8).malloc(10) # 10バイト獲得 p[999999] #=> Segmentation fault
p = Pointer(UInt8).null # NULLポインタ p[0] #=> Segmentation fault
Slice
固定のサイズを持ったメモリ領域です。オブジェクト生成時に型とサイズが決まります。 領域を超えたアクセスはできません。Pointer に比べると安全です。
s = Slice(UInt8).new(10) # 10バイト獲得 s[0] = 0xAAu8 s[1] = 'X'.ord.to_u8 p s #=> [170, 88, 0, 0, 0, 0, 0, 0, 0, 0] s[10] #=> Index out of bounds (IndexError)
IO#read, #write でバイナリの読み書きをする場合は Slice を介して行います。
IO#read は最大 Slice 領域分の大きさを読み込もうとし、実際に読み込んだバイト数を返します。 ちょっと C っぽいです。
File.open("somefile") do |f| s = Slice(UInt8).new(100) len = f.read(s) # ファイルから100バイト読み込み len # 実際に読み込んだバイト数 end
IO#write は Slice 領域の全データを書き込みます。Slice の一部を書き込むには、その一部を取り出した Slice オブジェクトを作って渡します。
File.open("somefile", "w") do |f| s = Slice(UInt8).new(100) f.write(s) # 100バイト書き込み f.write(s[20, 10]) # 先頭から20バイト目から10バイト分だけ書き込み end
MemoryIO
IO じゃなくてメモリ上にバイナリデータを作成したい場合には MemoryIO を使用します。 IO と同じようなメソッドが使えますが、データはメモリ上にあります。 Slice は色んな型を持てますが、MemoryIO は UInt8 固定です。
m = MemoryIO.new s = Slice(UInt8).new(100) m.write(s) # 100バイト書き込み m.write(s[20, 10]) # 10バイト書き込み m.size #=> 110
数値の内部表現
IO#read_bytes, IO#write_bytes を使って、整数や浮動小数点数の内部表現を IO に読み書きすることができます。
m = MemoryIO.new m.write_bytes(123456) m.write_bytes(123.456) m.rewind s = Slice(UInt8).new(100) m.read(s) #=> 12 s[0, 12] #=> [64, 226, 1, 0, 119, 190, 159, 26, 47, 221, 94, 64] m.rewind m.read_bytes(UInt32) #=> 123456 m.read_bytes(Float64) #=> 123.456
相互変換
# Pointer -> Slice pointer.to_slice(size) Slice.new(pointer, size) # Pointer -> String String.new(pointer) # NUL文字終わりのポインタ # Slice -> Pointer slice.pointer(size) # サイズはチェック用 slice.to_unsafe # Slice -> MemoryIO MemoryIO.new(slice) # Slice -> String String.new(slice) # MemoryIO -> Pointer memoryio.buffer # MemoryIO -> Slice memoryio.to_slice # MemoryIO -> String memoryio.to_s # String -> Pointer string.to_unsafe # String -> Slice string.to_slice # String -> MemoryIO MemoryIO.new(string)