文字コードと Charset を直感でつかむ
まず前提から整理します。
コンピュータの中では、文字は「バイト列(0と1の並び)」として保存・送信されます。
一方、Java の String は「文字そのもの(Unicode)」として扱われます。
この「文字 ↔ バイト列」を変換するときに
「どのルールで変換するか?」を決めるのが文字コード(文字セット/エンコーディング)で、
それを Java で表現したクラスが java.nio.charset.Charset です。
イメージとしては、
文字(Java の String)
↓ エンコード(文字 → バイト)
バイト列(ファイル・ネットワーク)
↑ デコード(バイト → 文字)
文字(Java の String)
この「エンコード・デコードで使う変換ルール」が Charset です。
Java と文字コードの関係(ここはしっかり)
Java の中では Unicode(UTF-16)
Java の String 型は、内部的には「UTF-16 をベースにした Unicode」で文字を扱っています。
つまり、プログラムの中で String として持っているうちは、
Shift_JIS だとか UTF-8 だとかは意識しません。
問題になるのは、
ファイルに書く/読む
ネットワークで送る/受け取る
バイト配列と文字列を相互変換する
こういう、「外部の世界」とやりとりするときです。
このときに「どの文字コードでエンコード/デコードするのか」を
明示的に指定しないと、「文字化け」や「読めないデータ」の原因になります。
Charset クラスの基本(StandardCharsets と forName)
よく使う Charset の取り方
Charset は主に「どの文字コードを使うか」を指定するために使います。
代表的なのが、StandardCharsets 経由の取り方です。
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
Charset utf8 = StandardCharsets.UTF_8;
Charset sjis = Charset.forName("Shift_JIS");
System.out.println(utf8); // UTF-8
System.out.println(sjis); // Shift_JIS
JavaStandardCharsets.UTF_8 のように書くと、
コンパイル時に存在チェックもされますし、書き間違えにくいのでおすすめです。
Charset.forName("Shift_JIS") のように文字列で指定すると、
名前を間違えたときに UnsupportedCharsetException が出ます。
どうしても文字列指定が必要なとき以外は、StandardCharsets を優先すると安全です。
文字列とバイト配列の変換(エンコード/デコード)
getBytes で「文字列 → バイト列」
String#getBytes(Charset) を使うと、指定した文字コードで「文字列 → バイト列」に変換できます。
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
String text = "abc♪";
byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8);
byte[] sjisBytes = text.getBytes(StandardCharsets.SHIFT_JIS);
System.out.println(Arrays.toString(utf8Bytes));
System.out.println(Arrays.toString(sjisBytes));
Java同じ "abc♪" でも、UTF-8 と Shift_JIS ではバイト列が違います。
実際、UTF-8 では abc と ♪ の部分で 6 バイト(ASCII 3 + マルチバイト 3)など、
Shift_JIS では別のバイトパターンになります。
ここで大事なのは、
「文字列の見た目は同じでも、文字コードが違えばバイト列は変わる」
という感覚です。
new String(byte[], Charset) で「バイト列 → 文字列」
逆に、バイト列から文字列に戻すときは new String(bytes, charset) を使います。
byte[] bytes = ...; // どこかから来たバイト列(UTF-8 だと分かっているとする)
String text = new String(bytes, StandardCharsets.UTF_8);
System.out.println(text);
Javaここで重要なのは、
エンコードに使った Charset と、デコードに使う Charset を揃えること
です。
UTF-8 でエンコードしたデータを Shift_JIS として解釈したりすると、即文字化けします。
「デフォルト文字コード」に頼る危険(ここはかなり重要)
getBytes() や new String(bytes) の引数なし版は危ない
Java では、引数なしの getBytes() や new String(bytes) も書けますが、
これらは「プラットフォームのデフォルト文字コード」を使います。
String text = "こんにちは";
byte[] bytes = text.getBytes(); // デフォルト文字コードでエンコード
String restored = new String(bytes); // デフォルト文字コードでデコード
Java同じ環境・同じプロセス内だけで完結するなら、
たまたまこれで動いてしまうこともあります。
しかし、
ファイルを別環境で読む
データをネットワーク経由で「他のシステム」に渡す
Java のバージョンや OS の設定が変わる
などのタイミングで、デフォルト文字コードが違ってくると、一気に文字化けリスクが高まります。
Java 18 以降はデフォルトが UTF-8 に統一されましたが、
それ以前は OS 依存(Windows なら MS932/Shift_JIS 系など)が多く、
過去のコードではこの影響で「環境依存文字化け」がよく起きました。
実務的には、getBytes() や new String(bytes) の「引数なし版」は使わない方針にしておくと安全です。
常に StandardCharsets.UTF_8 などを明示しましょう。
Charset と Reader/Writer(ファイル I/O とセットで使う)
ファイル読み書き時に Charset を指定する
文字コードが一番ダイレクトに効いてくるのが「ファイル I/O」です。
InputStreamReader や OutputStreamWriter は、
バイトストリームと文字ストリームの橋渡しをするクラスですが、
ここでも Charset を指定できます。
import java.io.*;
import java.nio.charset.StandardCharsets;
try (Reader reader = new InputStreamReader(
new FileInputStream("input.txt"),
StandardCharsets.UTF_8)) {
int ch;
while ((ch = reader.read()) != -1) {
System.out.print((char) ch);
}
}
Javatry (Writer writer = new OutputStreamWriter(
new FileOutputStream("output.txt"),
StandardCharsets.UTF_8)) {
writer.write("こんにちは");
}
Javaここで UTF-8 を明示しておけば、
「このファイルは UTF-8 で書かれている/読み取られる」とはっきり決まります。
もしここで Charset を指定せずにデフォルトに任せると、
開発マシンではたまたま正しく見える
別環境や別 OS で読むと文字化けする
といった「移植性の低いコード」になりがちですx。
よく使う Charset と、その実務での感覚
UTF-8 を基本にする
現在の新規開発では、UTF-8 を標準にするのがほぼ定番です。
理由は、
世界中の文字を扱える
多くの言語・ツールが標準でサポートしている
Java でも StandardCharsets.UTF_8 で明示しやすい
からです。
コード上でも、まずは UTF-8 をベースに考えておくとよいです。
String text = "日本語";
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
String restored = new String(bytes, StandardCharsets.UTF_8);
Java既存システムでは Shift_JIS / MS932 なども登場する
日本の古い Windows 系業務システムでは、Shift_JIS やその派生(MS932 / windows-31j)もまだ残っています。
そういう環境と連携するときは、
相手が何の文字コードを使っているか
自分は何の Charset で読んで/書いているか
を常に意識する必要があります。
Charset.forName("Shift_JIS") で取りつつ、
本当にそれでいいか、仕様書や相手側の設定を確認する癖をつけておくと安全です。
まとめ:初心者が Charset でまず押さえておくべきこと
Charset を、初心者向けにざっくりまとめるとこうなります。
Java の String は内部的に Unicode(UTF-16)。
外の世界(ファイル・ネットワーク・バイト配列)とやりとりするときに、「どの文字コードで変換するか」を決めるのが Charset。getBytes(charset) と new String(bytes, charset) で、「文字列 ↔ バイト列」を、指定した Charset で変換する。
デフォルト文字コード(引数なしの getBytes / new String に依存)は危険なので、基本は StandardCharsets.UTF_8 などを明示する。
ファイル I/O の InputStreamReader / OutputStreamWriter にも Charset を指定して、「このファイルは何で書いているか」をコードで固定する。
