Java | 基礎文法:文字コード

Java Java
スポンサーリンク

文字コードの全体像

文字コードは「文字をバイト列に変換・復元するための規則」です。Java の内部表現は Unicode を採用し、文字列は UTF-16(コードユニット)で保持されます。一方、ファイルやネットワークでは「UTF-8」や「Shift_JIS」のような外部エンコーディングを明示して読み書きします。重要なのは「内部は Unicode」「外部はエンコーディングを必ず指定」の二本柱です。


Java の内部表現(Unicode と UTF-16)

String と char の基礎

Java の String は Unicode を UTF-16 のコードユニット配列として保持します。char は16ビットで1ユニットを表しますが、すべての文字が char 1個で表現できるわけではありません。絵文字や一部の漢字拡張など、コードポイントが大きい文字は「サロゲートペア(char 2個)」になります。

String s = "A😊";           // 'A' は1ユニット、'😊' はサロゲートペア
System.out.println(s.length());           // 3(UTF-16の長さ)
System.out.println(s.codePoints().count()); // 2(Unicodeコードポイント数)
Java

コードポイントで安全に扱う

文字単位の正確な処理(長さ、切り出し、走査)は「コードポイント」APIを使います。length() はコードユニット数、codePoints() は実際の文字数に近いコードポイント列です。

int cpCount = s.codePointCount(0, s.length()); // コードポイント数
int first = s.codePointAt(0);                  // 先頭コードポイント
String head = new String(Character.toChars(first)); // 正しい1文字切り出し
Java

外部エンコーディングの明示(UTF-8 を基本に)

バイト⇔文字列の往復

外部とやり取りする時は、エンコーディングを必ず指定します。未指定だと「環境依存の既定値」になり、文字化けが起きやすくなります。

import java.nio.charset.StandardCharsets;

// 文字列 → バイト
byte[] bytes = "江東区".getBytes(StandardCharsets.UTF_8);

// バイト → 文字列
String text = new String(bytes, StandardCharsets.UTF_8);
Java

ファイル読み書きでの指定

ファイル I/O は Files の「文字セットあり」APIか、Reader/WriterCharset を渡します。UTF-8 を基本とし、必要なら相手仕様に合わせて切り替えます。

import java.nio.file.*;
import java.nio.charset.StandardCharsets;

// 読み取り
String s = Files.readString(Path.of("data.txt"), StandardCharsets.UTF_8);

// 書き込み
Files.writeString(Path.of("out.txt"), "江東区", StandardCharsets.UTF_8);
Java
import java.io.*;
import java.nio.charset.StandardCharsets;

try (var br = new BufferedReader(new InputStreamReader(
         new FileInputStream("in.txt"), StandardCharsets.UTF_8))) {
    System.out.println(br.readLine());
}
try (var bw = new BufferedWriter(new OutputStreamWriter(
         new FileOutputStream("out.txt"), StandardCharsets.UTF_8))) {
    bw.write("江東区");
}
Java

よくある文字化けの原因と対策(重要ポイントの深掘り)

既定エンコーディングの誤用

エンコーディング未指定の I/O は、OSやJDK設定に依存します。プロジェクト全体のルールとして「常に UTF-8 を明示」すると、環境差での文字化けを防げます。

入力と出力の不一致

読み取りと書き込みで異なるエンコーディングを使うと文字化けします。仕様書やAPIドキュメントで「何エンコーディングか」を確認し、コードで揃えます。Web/JSON/CSV などは UTF-8 が事実上の標準です。

BOM(Byte Order Mark)の扱い

UTF-8 の BOM付きファイルは、一部ツールで先頭に不可視文字が混入します。Java の Files.readString は BOM を透過しがちですが、問題が起きるときは BOM を除去して扱うか、BOMなしのUTF-8で出力します。

// 先頭の不可視文字を取り除く例
String normalized = text.replaceFirst("^\uFEFF", "");
Java

Shift_JIS / Windows-31J / EUC-JP の罠

日本語レガシー系は機種依存文字や拡張差があり、互換性問題が起こりやすいです。やむを得ず扱う時は Charset.forName("Windows-31J") など、相手仕様に合わせて厳密に指定します。

var sjis = java.nio.charset.Charset.forName("Windows-31J");
byte[] bs = "㈱".getBytes(sjis);             // 対応有無に注意
String t = new String(bs, sjis);
Java

正規化・比較・並びの注意(Unicode を使いこなす)

同じ見た目でも別コード

合成文字や全角半角など、見た目が同じでもコードポイントが異なる場合があります。厳密な一致が必要なら正規化してから比較します。

import java.text.Normalizer;
String n1 = Normalizer.normalize("パ", Normalizer.Form.NFC);
String n2 = Normalizer.normalize("パ", Normalizer.Form.NFC);
System.out.println(n1.equals(n2)); // true(NFCで揃える)
Java

大文字小文字・濁点の比較と整列

言語ルールで並べたい場合は Collator を使います。コードポイント順(compareTo)は人間の感覚とズレることがあります。

import java.text.Collator;
import java.util.Locale;

var col = Collator.getInstance(Locale.JAPAN);
col.setStrength(Collator.PRIMARY); // 大小・濁点を弱める
System.out.println(col.compare("ばなな", "バナナ"));
Java

例題で身につける

例 1: ファイルを UTF-8 で安全に往復

import java.nio.file.*;
import java.nio.charset.StandardCharsets;

var p = Path.of("memo.txt");
Files.writeString(p, "江東区 東京", StandardCharsets.UTF_8);
String s = Files.readString(p, StandardCharsets.UTF_8);
System.out.println(s);
Java

例 2: ネットワークのバイト列を文字列へ

import java.nio.charset.StandardCharsets;

byte[] payload = /* HTTPレスポンスボディ */;
String json = new String(payload, StandardCharsets.UTF_8); // Content-Typeに従うのが原則
Java

例 3: コードポイントで安全に1文字ずつ処理

String text = "A😊東";
text.codePoints().forEach(cp -> {
    String ch = new String(Character.toChars(cp));
    System.out.println(ch);
});
Java

例 4: レガシーとの相互変換

var sjis = java.nio.charset.Charset.forName("Windows-31J");
String s = "東京都 江東区";
byte[] b = s.getBytes(sjis);                       // SJISへ
String back = new String(b, sjis);                 // SJISから
System.out.println(back);
Java

仕上げのアドバイス(重要部分のまとめ)

文字列の内部は Unicode(UTF-16)、外部 I/O はエンコーディングを必ず指定——まずこの原則を体に入れる。UTF-8 をプロジェクト標準にし、レガシー系は仕様に合わせて明示する。長さや切り出しはコードポイントで安全に行い、比較や並びで人間の感覚を求めるなら正規化や Collator を使う。BOM・既定エンコーディング・読み書き不一致が文字化けの主因——ここを潰せばトラブルは激減します。

タイトルとURLをコピーしました