Ruby の String#force_encoding
は String の encoding を変更するだけで、文字コードの変換を行うわけではありません。そのため、バイト列は変換されません。
force_encoding はどんなメソッドか
String の encoding を変更します。ただし、バイト列は変更せず、encoding に対しバイト列が正しいかどうかは確認しません。
例えば あ
を EUC-JP に変換して encode
と比較します。
'あ'.encode(Encoding::EUC_JP).encoding # => #<Encoding:EUC-JP> 'あ'.force_encoding(Encoding::EUC_JP).encoding # => #<Encoding:EUC-JP>
どちらも encoding は EUC-JP です。
String#valid_encoding?
で正当なバイト列かどうか確認できます。
'あ'.encode(Encoding::EUC_JP).valid_encoding? # => true 'あ'.force_encoding(Encoding::EUC_JP).valid_encoding? # => false
force_encoding
は false
になり、バイト列が正しくないことがわかります。
encode
と force_encoding
の返り値を見てみると、
'あ'.encode(Encoding::EUC_JP) # => "\x{A4A2}" 'あ'.force_encoding(Encoding::EUC_JP) # => "\xE3\x81\x82"
encode
と force_encoding
で結果が異なっています。
どちらも あ
のバイト列ですが、encode
の返り値の \xA4\xA2
は EUC-JP のバイト列で、 force_encoding
のほうの \xE3\x81\x82
は UTF-8 の あ
のバイト列です。
'あ'.encode.bytes.map { _1.to_s(16) } # => ["e3", "81", "82"]
encode
は encoding とバイト列を変更しますが、 force_encoding
は encoding のみ変更してバイト列は変更しません。
force_encoding はどんなときに使うか
バイト列を変えずに encoding だけを変換したいときに使います。
Array#pack
の返り値に対して encoding を設定する場合
Array#pack
の返り値の encoding は ASCII-8BIT だったりして人間には読みにくく、また encoding に UTF-8 を期待している場合に使いにくいので、UTF-8 (もしくはお使いの環境の文字コード)の文字列として扱いたい場合 force_encoding
をつけると使いやすくなります。
["e38182"].pack("H*") # => "\xE3\x81\x82" ["e38182"].pack("H*").force_encoding(Encoding::UTF_8) # => "あ"
バイト列から文字を組み立てる場合
バイト列だけが分かっていて、encoding を後から設定したい場合です。
例えば以下の記事に書いたような、未定義文字に対する変換先の文字を定義する場合です。これを encode
で同じことをすると少しややこしくなるので force_encoding
したほうがすっきりします。
U+301C from UTF-8 to Windows-31J (Encoding::UndefinedConversionError) に対応する - esm アジャイル事業部 開発者ブログ
str = "\u{2014 301C 2016 2212 00A2 00A3 00AC}" undefined_signs = { "\u2014" => "\x81\x5C".force_encoding(Encoding::Windows_31J), # — EM DASH "\u301C" => "\x81\x60".force_encoding(Encoding::Windows_31J), # 〜 WAVE DASH "\u2016" => "\x81\x61".force_encoding(Encoding::Windows_31J), # ‖ DOUBLE VERTICAL LINE "\u2212" => "\x81\x7C".force_encoding(Encoding::Windows_31J), # − MINUS SIGN "\u00A2" => "\x81\x91".force_encoding(Encoding::Windows_31J), # ¢ CENT SIGN "\u00A3" => "\x81\x92".force_encoding(Encoding::Windows_31J), # £ POUND SIGN "\u00AC" => "\x81\xCA".force_encoding(Encoding::Windows_31J), # ¬ NOT SIGN } p str.encode(Encoding::Windows_31J, fallback: undefined_signs)
この場合は String.new でも同じことができます。
String.new("\x81\xCA", encoding: Encoding::CP932)
DB に入れるデータをバイナリ扱いしたい場合
普通にやると別の文字コードに変換されてしまう文字を DB の設定を変えずに DB に格納したいことがあり、そのために encoding に Encoding::ASCII_8BIT
をつけてバイナリ扱いして encoding を変換させずに入れるために使ったことがあります。
まとめ
force_encoding
では encoding のみを変更し、バイト列は変更しません。また、バイト列が正しいかどうかのチェックも行いません。
encoding に対してバイト列が不正な場合は文字を正しく扱えないため、どうしても force_encoding
したい場合は valid_encoding?
とセットで正しいバイト列かチェックして使う方が安全だと思います。