Rubyのencodeとforce_encodingの違い

## はじめに
Rubyの[encode](https://docs.ruby-lang.org/ja/latest/method/String/i/encode.html)メソッドと[force_encoding](https://docs.ruby-lang.org/ja/latest/method/String/i/force_encoding.html)メソッドの違いをご存じでしょうか？
まずはRubyのリファレンスマニュアルを見てみましょう。

**encode**
>self を指定したエンコーディングに変換した文字列を作成して返します。(以下、省略)


**force_encoding**
>文字列の持つエンコーディング情報を指定された encoding に変えます。
このとき実際のエンコーディングは変換されず、検査もされません。 

`encode`はその名の通り文字列を変換してくれて、`force_encoding`はエンコーディング情報(？)を変えるけど、文字列自体は変換されないと。エンコーディング情報が何かは分かりませんが、とりあえず動かしてみましょう。

### 動作確認
検証用に「テスト」という文字列を``iso-2022-jp``という文字符号化方式に変換したものを用意します。メールでUTF-8を期待していたところに別の文字符号化方式の文字列が送られてきたイメージです。

```ruby
puts "テスト".encode('iso-2022-jp')
```

**出力結果**

```
$B%F%9%H(B
```
以降の内容を試す場合は下記のリンク先からエンコードされた文字列をコピーしてご使用ください。
https://paiza.io/projects/jCRDa1-PFB06JTcOZTGJ-g


この文字列を`UTF-8`の元の文字列に戻してみましょう。

まずは`encode`を試してみます。

```ruby
puts "$B%F%9%H(B".encode('utf-8')
```
**出力結果**

```
$B%F%9%H(B
```

あれ、何も変わらない・・・。


次に``force_encoding``

```ruby
puts "$B%F%9%H(B".force_encoding('utf-8')
```

**出力結果**

```
$B%F%9%H(B
```

こちらも変わらず・・・。

この文字列を元に戻すにはどうすれば良いのでしょうか？
以下に、前提となる知識と変換方法をご紹介します。

## エンコーディング情報
``force_encoding``の説明の中にもありましたが、文字列はエンコーディング情報を持ちます。
文字列のエンコーディング情報を確認するには[encoding](https://docs.ruby-lang.org/ja/latest/method/String/i/encoding.html)メソッドで確認することができます。

```ruby
puts "テスト".encoding
```

**出力結果**

```
UTF-8
```

`encoding`の返り値として、[Encoding](https://docs.ruby-lang.org/ja/latest/class/Encoding.html)オブジェクトを返します。Encodingクラスは文字エンコーディング(文字符号化方式)のクラスです。`encode`と`force_encoding`の実行後に確認してみると違いが分かりそうですね。

## 文字列とバイト

たとえば「あ」という文字列はバイトで表すと3つの数字が返ってきます。

```ruby
puts "#{"あ".bytes}"
```

**出力結果**

```
[227, 129, 130]
```

それぞれの数字を16進数に変換すると`[e3 81 82]`となり、この数字は`UTF-8`の「あ」と一致します。([参考](http://aspiration.sakura.ne.jp/wiki/index.php?develop%2FUTF-8%E3%81%B2%E3%82%89%E3%81%8C%E3%81%AA%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A%E8%A1%A8))
このように文字はそれぞれバイト列が決まっているので、バイト列の数値が変化していれば文字が変わったと判断できそうですね。

## 対象文字列の確認
さて、これらを踏まえた上で改めて先程の文字列を見てみましょう。

```ruby
str = "$B%F%9%H(B"
puts "文字列:#{str}"
puts "バイト列:#{str.bytes}"
puts "エンコーディング情報:#{str.encoding}"
```

**出力結果**

```
文字列:$B%F%9%H(B
バイト列:[36, 66, 37, 70, 37, 57, 37, 72, 40, 66]
エンコーディング情報:UTF-8
```

エンコーディング情報を見ると`UTF-8`となっているので、`encode`で`UTF-8`に変換しようとしても何も変わりません。また、`force_encoding`を実行しても、元々のエンコーディング情報が`UTF-8`のためこちらも何も変わりません。これによって最初の動作確認で何も変化しなかった理由が分かりました。では、どうすれば変換できるのでしょうか？

## 変換方法
以下の2ステップで変換します。
1. `force_encoding`でエンコーディング情報を`iso-2022-jp`に変換する
2. `encode`で文字列を`UTF-8`に変換する

```ruby
str = "$B%F%9%H(B"
puts "オリジナル"
puts str
puts str.encoding
puts "#{str.bytes}"

puts "force_encoding"
force_encode_str = str.force_encoding('iso-2022-jp')
puts force_encode_str
puts force_encode_str.encoding
puts "#{force_encode_str.bytes}"

puts "encode"
encode_str = force_encode_str.encode('utf-8')
puts encode_str
puts encode_str.encoding
puts "#{encode_str.bytes}"
```

**実行結果**

```
オリジナル
$B%F%9%H(B
UTF-8
[27, 36, 66, 37, 70, 37, 57, 37, 72, 27, 40, 66]

force_encoding
$B%F%9%H(B
ISO-2022-JP
[27, 36, 66, 37, 70, 37, 57, 37, 72, 27, 40, 66]

encode
テスト
UTF-8
[227, 131, 134, 227, 130, 185, 227, 131, 136]
```

`force_encoding`で文字列に期待するコーディング情報に変換し、その後、`encode`で期待する文字符号化方式(今回であれば`UTF-8`)で文字列を変換しました。文字列はもちろんのこと、バイト列も変換されているのが確認できますね。

## まとめ
### encode
- 文字列を指定したエンコーディングに変換
- 文字列がもつエンコーディング情報についても、指定したエンコーディングに変換
- エンコーディング情報と指定したエンコーディングが同一の場合、変化しない

### force_encoding
- 文字列自体は変化しない
- 文字列がもつエンコーディング情報を指定したエンコーディングに変換
- encodeの前処理として使いそう

## 参考
[encoding - What is the difference between #encode and #force_encoding in ruby? - Stack Overflow](https://stackoverflow.com/questions/21614228/what-is-the-difference-between-encode-and-force-encoding-in-ruby)