- 技術 💻
「~」が「?」になる原因と直し方|波ダッシュ問題
「10時~12時」の「~」が「?」になるのは、文字コードの変換に「Shift_JIS」を使っているためです。 Windows で使われている日本語の文字コードは、正確には Windows-31J(MS932・cp932) という Shift_JIS の拡張で、「~」などいくつかの文字は、Shift_JIS と Unicode への対応のしかた(変換表)が違います。
直し方は、Shift_JIS と書いている所を Windows-31J(cp932)にすることです。
| 言語・ソフト | 「Shift_JIS」の代わりに使う名前 |
|---|---|
| Java | windows-31j(別名 MS932) |
| Python | cp932 |
| MySQL | cp932(sjis ではなく) |
| HTML・メールの宣言など | 相手のシステムの仕様に合わせる(下で説明) |
確認環境:Java 21、Python 3.11、MySQL 8.0.46(確認日 2026年10月11日)。同じ文字を Shift_JIS と Windows-31J(cp932)で変換して比べました。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「「~」が「?」になる原因と直し方|波ダッシュ問題」
再現:「~」を Shift_JIS に変換すると「?」になる
String input = "10時~12時";
System.out.println(new String(input.getBytes("Shift_JIS"), "Shift_JIS"));
// 10時?12時 ← 「~」が ? になった
System.out.println(new String(input.getBytes("windows-31j"), "windows-31j"));
// 10時~12時 ← 正しく戻る
Python では、? にならずにエラーになりました。
>>> "10時~12時".encode("shift_jis")
UnicodeEncodeError: 'shift_jis' codec can't encode character '~' in position 3: illegal multibyte sequence
>>> "10時~12時".encode("cp932")
b'10\x8e\x9e\x81\x6012\x8e\x9e'
Java は黙って ?(0x3F)に置き換え、Python はエラーで止まります。 Java のほうが気づきにくく、CSV や帳票に ? が出て初めて分かります。
なぜ起きるのか:同じ「0x8160」が、別の文字に対応している
Shift_JIS の 0x8160 は「~」の形の文字です。この 0x8160 を Unicode のどの文字に対応させるかが、2つの文字コードで違います。
| 0x8160 を読むと | Unicode の文字 |
|---|---|
| Shift_JIS として読む | U+301C(WAVE DASH「〜」) |
| Windows-31J(cp932) として読む | U+FF5E(FULLWIDTH TILDE「~」) |
Windows で入力・保存された「~」は、Windows-31J の変換表では U+FF5E です。これを Shift_JIS に変換しようとしても、Shift_JIS の変換表には U+FF5E が無いため、「?」になります。見た目はほとんど同じ別の文字なので、画面では区別がつきません。MySQL 公式マニュアルの sjis と cp932 の変換表でも、同じ違いが示されています。
化ける文字の対応表
Java で変換した結果です(3F は「?」に置き換えられたことを示します)。
| 文字(Unicode) | Shift_JIS | Windows-31J |
|---|---|---|
| ~ U+FF5E(全角チルダ) | 3F | 8160 |
| 〜 U+301C(波ダッシュ) | 8160 | 3F |
| - U+FF0D(全角ハイフンマイナス) | 3F | 817C |
| − U+2212(マイナス) | 817C | 3F |
| ∥ U+2225(平行) | 3F | 8161 |
| ‖ U+2016(二重縦線) | 8161 | 3F |
| ― U+2015(水平線) | 3F | 815C |
| ¢ U+FFE0(全角セント) | 3F | 8191 |
| ¬ U+FFE2(全角否定) | 3F | 81CA |
| ① U+2460(丸数字) | 3F | 8740 |
| ㈱ U+3231 | 3F | 878A |
| 髙 U+9AD9(はしごだか) | 3F | FBFC |
| 文字の種類 | 何が起きるか |
|---|---|
| ~ - ∥ ― ¢ £ ¬ | 変換表の違い。Windows で入力したものは Shift_JIS で ? |
| ① ㈱ 髙 などの機種依存文字 | Shift_JIS にはそもそも無い。Windows-31J にはある |
丸数字・㈱・はしごだかも、Shift_JIS では「?」になります。Windows で作られたデータを扱うなら、Windows-31J を使うのが基本です。
言語によって、変換表が少しずつ違う
Python の cp932 と Java の windows-31j で、同じ文字を比べると、結果が違うものがありました。
| 文字 | Java windows-31j |
Python cp932 |
|---|---|---|
| 〜 U+301C(波ダッシュ) | 3F(?) | 8160(~ と同じ値に変換) |
| 髙 U+9AD9 | FBFC | EEE0 |
| ― U+2015 | 815C | 815C |
| — U+2014(EM DASH) | 3F | エラー |
Java の Windows-31J は「〜」(U+301C)を ? にしますが、Python の cp932 は 0x8160 に変換しました。 U+301C の「〜」を含むデータ(ほかのシステムから来たものなど)を Java で Windows-31J に変換すると、? になります。複数の言語・システムをまたぐなら、変換の前に、文字をそろえる処理を入れます(下の直し方3)。
ありがちな場面:Excel の CSV を Shift_JIS で読む
byte[] fromExcel = "10時~12時".getBytes("windows-31j"); // Excel が保存した CSV
String read = new String(fromExcel, "Shift_JIS"); // Shift_JIS で読む
// read = "10時〜12時"(U+301C) equals("10時~12時") → false
? にはならず、見た目も同じなのに、文字が U+301C に変わっていました。 この文字列を DB に保存して、画面(Windows)から「~」で検索すると、一致しません。読むときも書くときも Windows-31J を使います。
new String(fromExcel, "windows-31j").equals("10時~12時") // true
直し方
1. 文字コードの名前を Windows-31J・cp932 にする
| 言語・ライブラリ | 書き方 |
|---|---|
| Java(文字列・入出力) | "windows-31j" または "MS932"(同じもの) |
Java(Charset) |
Charset.forName("windows-31j") |
| Python | open(path, encoding="cp932")、s.encode("cp932") |
| MySQL の列・テーブル | CHARACTER SET cp932 |
Java では MS932 は windows-31j の別名です(確認環境でも Charset.forName("MS932").name() は windows-31j)。SJIS は Shift_JIS の別名なので、SJIS と書いている所も直します。
2. MySQL は sjis ではなく cp932
(sjis の列に「10時~12時」を INSERT)
ERROR 1366 (HY000): Incorrect string value: '\xEF\xBD\x9E12\xE6...' for column 's_sjis' at row 1
(cp932 の列)
10時~12時 31308E9E816031328E9E ← 保存できた
sjis の列には、Windows の「~」(U+FF5E)を保存できませんでした。 同じ 0x8160 として保存されたデータでも、sjis の列から読むと「〜」(U+301C)、cp932 の列から読むと「~」(U+FF5E)になりました。新しく作るなら、文字コードの問題がない utf8mb4 にするのが一番です(関連記事の「Incorrect string value」で解説)。
3. 変換する前に、文字をそろえる
ほかのシステムから来たデータに、U+301C の「〜」やマイナス記号が混ざる場合は、Windows-31J に変換する前に置き換えます。
static String toWindows31jSafe(String s) {
return s.replace('〜', '~') // 〜 → ~
.replace('−', '-') // − → -
.replace('‖', '∥') // ‖ → ∥
.replace('¢', '¢') // ¢ → ¢
.replace('¬', '¬'); // ¬ → ¬
}
Unicode の正規化(NFKC)を使う方法もありますが、確認環境では NFKC で「~」(U+FF5E)は半角の ~ に変わり、「〜」(U+301C)は変わりませんでした。見た目を保ったまま置き換えたいなら、上のように明示的に置き換えます。
4. 相手のシステムが「Shift_JIS」を指定している場合
取引先のシステムや帳票の仕様で「Shift_JIS」と指定されていても、実際には Windows-31J のことが多いです。「~」や丸数字を送ってよいか、相手に確認します。本当に Shift_JIS(JIS X 0208 の範囲だけ)なら、送る前に機種依存文字を置き換えるか、エラーにします。
確認の手順(チェックリスト)
| 順番 | 確認すること | 方法 |
|---|---|---|
| 1 | 化ける文字 | ~ - ∥ ― ¢ ¬、① ㈱ 髙 か |
| 2 | 文字コードの指定 | Shift_JIS・SJIS・sjis と書いている所を探す |
| 3 | 読む側・書く側の両方 | 読むときも書くときも windows-31j・cp932 |
| 4 | DB | MySQL の列が sjis なら cp932(できれば utf8mb4) |
| 5 | ほかのシステムから来たデータ | U+301C・U+2212 などを置き換えてから変換 |
| 6 | 検索で一致しない | 文字のコード(U+FF5E か U+301C か)を確かめる |
よくある質問
Q. 文字のコード(U+FF5E か U+301C か)は、どう確かめますか。
A. Java なら Integer.toHexString(s.charAt(i))、Python なら hex(ord(c)) です。MySQL なら HEX() で保存されたバイトを確かめます。
Q. UTF-8 にすれば起きませんか。 A. UTF-8 どうしなら起きません。Shift_JIS・Windows-31J との変換が入る所(Excel の CSV、以前からのシステムとの連携、帳票)で起きるので、その変換の箇所で Windows-31J を使います。
まとめ
- 「Shift_JIS」で変換すると、Windows の「~」(U+FF5E)は「?」になる
- 原因は変換表の違い。0x8160 は Shift_JIS では U+301C、Windows-31J では U+FF5E
- - ∥ ― ¢ ¬、① ㈱ 髙 も同じく化ける
- 直し方は Java は
windows-31j、Python はcp932、MySQL はcp932(できれば utf8mb4) - Shift_JIS で読むと、? にならずに U+301C に変わり、検索で一致しなくなる
- Java は黙って ?、Python はエラー。言語によって変換表も少し違う
参考・出典
確認日はいずれも 2026年10月11日です。
- MySQL 8.0 Reference Manual「The cp932 Character Set」:https://dev.mysql.com/doc/refman/8.0/en/charset-cp932.html
- Java SE 21「Supported Encodings」:https://docs.oracle.com/en/java/javase/21/intl/supported-encodings.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「「~」が「?」になる原因と直し方|波ダッシュ問題」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。