ITエンジニアの成長ブログ

ITエンジニアとして行う勉強の発信&日々の生活で体験した楽しいことをゆるく発信

Java内部の文字コードはUTF-16らしい

タイトル通りですが、Java内部の文字コードUTF-16です。

Java関連の記事や書籍で時々見かけていたこの事実ですが、正直それで一体なんなの?という感じでした。

ファイルを読み込みしたり、書き込みしたりする場合に意識しておけばだいたいOKかなという程度の理解で、今までJavaを使っていて特に問題になることはありませんでした。

でも、そういえばUTF-16サロゲートペアの問題あるよな。。。と思い軽く調べました。

UTF-16サロゲートペアとは?

UTF-16は数ある文字コードの1つで、2バイトで文字を表します。しかし、2バイトだと「65535通り」(2の16乗)しか表せないので、実はこれだけでは世界中の文字を表すには十分ではありません。

そのため、表せない文字は既存の2バイトの領域以外の部分を使用して、合計4バイトで表そうみたいな動きになり、その仕組みが「サロゲートペア」ということらしいです(参考の方々の記事がとてもわかりやすかったので、詳細はそちらをどうぞ)。

Javaの文字列クラスStringでサロゲートペアの文字の長さを確認する

ということで、簡単にUTF-16サロゲートペアの話をしました。

そこで、Javaの文字列クラスStringでサロゲートペアの文字の長さはいったいどのように判定されるのか確認してみたいと思います。java.lang.Stringクラスのlength()メソッドでいくつか確認したいと思います。

まずは、アルファベット英語の文字の長さを図ります。「aiueo」はどうなるのか。

public class Sample {
	public static void main(String[] args) {
		String str = "aiueo";
		System.out.println(str + "のlengthは、" + str.length());
	}
}


はい、こちらはString.length()の結果は、もちろん5になりました。別に何も違和感はありません。

「aiueo」のString.length()


次は、日本語のひらがなの長さを図ります。「あいうえお」はどうなるのか。

public class Sample {
	public static void main(String[] args) {
		String str = "あいうえお";
		System.out.println(str + "のlengthは、" + str.length());
	}
}


はい、こちらもString.length()の結果は、5になりました。いい感じですね。

「あいうえお」のString.length()

それでは、やっとサロゲートペアの文字を試してみます。サロゲートペア文字は、「𠀋」で試してみます。「𠀋𠀋𠀋𠀋𠀋」はどうなるのか。

public class Sample {
	public static void main(String[] args) {
		String str = "𠀋𠀋𠀋𠀋𠀋";
		System.out.println(str + "のlengthは、" + str.length());
	}
}


なんと!String.length()の結果が、10になってしまいました。5ではない。。何かしら文字数で処理する実装があった場合、今回のようなサロゲートペアの文字がある場合は障害になってしまうかもしれません。正直知らなかったので驚きました。

「𠀋𠀋𠀋𠀋𠀋」のString.length()

java.lang.StringのlengthメソッドのJavaDocを見てみる

StringのlengthメソッドをJavaDocで見てみると以下のように記載されています。
String (Java Platform SE 8)

String.length()メソッドのJavaDoc

「長さは文字列内のUnicodeコード単位の数に等しくなります。」という記載があります。

今回検証したサロゲートペア文字「𠀋」は、UTF-16エンコーディングすると「D840 DC0B」(16進数)になります。

おそらく、Unicodeコード単位というと「D840」(2Byte)で1文字判定となっているはずなので、今回の文字は1文字(𠀋)だけど、長さが2として判定されるようになっていると思われます。

おわりに

いかがでしょうか。私自身今まで文字列をカウントするような処理をJavaで実装したことはなかったのですが、サロゲートペアを意識しなければならないことを初めて知りました。

Java内部の文字コードUTF-16であることの導入として、今回はこの辺で終わりたいと思います。
最後までお読みいただきありがとうございました。

OSDN、SourceForgeなどの言葉の整理

完全に個人的なメモです。OSDN、SourceForgeなどの言葉の意味するところが少し混乱したので、個人的に整理しました。

SourceForgeとは?

オープンソースソフトウェア(OSS)の開発・ダウンロードサイトとして、公開されているアメリカの老舗サイト。

過去に色々と事件があって、何かと問題があった(調べればすぐに出てきます)。

OSDN(Open Source Development Network)

こちらもSourceForge同様、オープンソースソフトウェアプロジェクト向けのホスティングサイト。日本向け。

昔は、SourceForgeの姉妹サイトとしてSourceForge.jpという名前だったが、SourceForgeが起こした色々な事件によって袂を分かち、2015年にOSDNという名前に変更したという経緯がある。

2022年に中国の企業に買収されたよう。ここらへんから、サイトにアクセスできない事象が発生するようになったらしい。

おわりに

最近、OSDNに置かれているEclipseプラグインをダウンロードしようと思ったらエラーになって一向にダウンロードできない事象が発生しました。中国の企業に買収されてからそうなったようなので、もしかしたら今後の見通しはかなり悪いかもしれません。。

今後、OSNDはどうなるのかはとりあえず様子を見てみようと思います。

今回はこの辺で失礼いたします。最後までお読みいただきありがとうございました。

キーボードの日本語配列と英語配列について

先日、以下の記事を書いたときにキーボードの日本語配列英語配列について気になったので軽く調べました。
AWS EC2 Windows Serverのキーボードが英語レイアウトから変更できなかった話 - ITエンジニアの成長ブログ

キーボードの日本語配列英語配列の違い

キーボードの日本語配列英語配列の違いは、主に記号の位置とその他日本語変換関連のキーの有無です。

まず、前提として数字と英字の部分にはどちらも違いがありません。ここはどちらの配列でも同じなので混乱することはありません。以下で、日本語配列英語配列で異なる部分を2つ簡単に紹介します。

まず、記号の位置ですが、例えば日本語配列ではPの右隣には「@」(アットマーク)が配置されています。しかし、英語配列では「[」(角括弧左)です。英語配列で「@」(アットマーク)は、数字の2の位置のようです。このように記号類は、他にも多くが位置が異なるため慣れないと大変かもしれません。

また、日本語変換関連のキーですが、「全角/半角」「無変換」「変換」「カタカナ ひらがな」などがあると思います。これらのキーは、英語配列では存在しないようです。

英語配列で日本語は入力できないかと言うと、そうではないようで他のキー入力で代替できるそうです(入力方法等の詳しいことには、この記事では触れないでおきます)。

まとめ

日本に住んでいる人が普通にキーボードを購入するときには、ほぼ必ず日本語配列のはずです。おそらく、キーボードのレイアウトについて意識することもないと思います。

しかし、上記の通り日本語配列英語配列のキーボードにはいくつか違いがあります。そのため、キーボードにもそういう種類の違いがあるのだな、というくらいで頭の隅に置いておくのが良さそうです。

因みに、日本語配列より英語配列の方がプログラミングをするときに記号関係が入力しやすいという記事をいくつか見かけました。慣れの問題なのかもしれませんが、英語配列はなんだが少し憧れます。

今回はこの辺で失礼いたします。最後までお読みいただきありがとうございました。

Java 8で空文字列による、数値からの文字列変換は速度が遅いらしい

タイトルの通りなのですが、Java 8では空文字列で数値から文字列に変換する方法は、速度が遅いらしいという情報を入手したので、確認したいと思います。

今回は、数値を文字列に変換する以下の2つの方法の速度を計測して比較してみようと思います。

  • 計測方法1:String.valueOf(<数値>)
  • 計測方法2:<数値> + ""(空文字列)

計測するためのプログラム

簡易な、Javaのプログラムで実際に速度を計測してみたいと思います。数値から文字列に変換する処理を100万回実行した速度を計測するためのプログラムです。

public class Sample {
	public static void main(String[] args) {

		long start = System.currentTimeMillis();

		for (int i = 0; i < 1_000_000; i++) {
			//数値→文字列変換処理をここに実装
		}

		long end = System.currentTimeMillis();
		System.out.println((end - start) + "ms");
	}
}

計測方法1:String.valueOf(<数値>)

まずは、String.valueOf(<数値>)のやり方で実施してみます。以下のプログラムを5回実施して、その平均を取得します。

public class Sample {
	public static void main(String[] args) {

		long start = System.currentTimeMillis();

		for (int i = 0; i < 1_000_000; i++) {
		    String s = String.valueOf(i);
		}

		long end = System.currentTimeMillis();
		System.out.println((end - start) + "ms");
	}
}

結果は、「75ms」でした。

計測方法2:<数値> + ""(空文字列)

次は、<数値> + ""(空文字列)のやり方で実施してみます。以下のプログラムを5回実施して、その平均を取得します。

public class Sample {
	public static void main(String[] args) {

		long start = System.currentTimeMillis();

		for (int i = 0; i < 1_000_000; i++) {
		    String s = i + "";
		}

		long end = System.currentTimeMillis();
		System.out.println((end - start) + "ms");
	}
}

結果は、「104.4ms」でした。

まとめ

まとめると以下の通りです。今回は処理自体が簡単でしたので、そこまで差がはっきりと出たわけではありませんが、事前情報の通り文字列による変換の方が遅いことが分かりました。

  • 計測方法1:String.valueOf(<数値>) → 「75ms」
  • 計測方法2:<数値> + ""(空文字列) → 「104.4ms」

また、これはあくまでJava 8による実行結果です。他のバージョン、たとえばJava 9では空文字列による変換処理が効率化されたようなので、環境によっては問題ないかもしれません。

空文字列による変換は、可読性が良いので私自身よく使っていたのですが、このような速度を意識した実装をしなくてはならない状況では意識して使い分ける必要がありそうです。

今回はこの辺で失礼いたします。最後までお読みいただきありがとうございました。

Javaで1つのクラスに同名のクラスをインポートする場合

今更ですが、メモ程度にタイトルの通りJavaで1つのクラスに同名のクラスをインポートする場合にどうするかを紹介したいと思います。

同名のクラスをインポートするとどうなるか。

同名のクラスとしてよく紹介される、「java.util.Date」と「java.sql.Date」で試してみます。

以下のように、「java.util.*」、「java.sql.*」という形でimportして、Dateクラスを使うと「型 Date はあいまいです」というエラーメッセージが表示されます。

同名のクラスをインポートするとエラー

それは、そのはずで「java.util.Date」と「java.sql.Date」どちらのDateクラスかをコンパイラが判別できないからですよね。

java.util.Date」を明示的にimportして、「java.sql.*」という形でimportするとどうなるか。

java.util.Date」を明示的にimportして、「java.sql.*」という形でimportすると特にエラーは発生しなくなりました。

java.util.Date」のみを明示的にimport

明示的に「java.util.Date」というimportをすると、ソース上のDateクラスは明示的に指定したクラスを指すことになるようです。
また、「java.sql.*」は一度も使われていないという判定になり、警告メッセージが付与されています。

java.sql.*」は一度も使われていない警告メッセージ

java.util.Date」、「java.sql.Date」どちらもソースで使いたい場合はどうするか。

java.util.Date」、「java.sql.Date」のどちらも明示的にimportすると以下のようにエラーになります。

java.util.Date」、「java.sql.Date」どちらも明示的にimport

エラーメッセージは、「インポート java.sql.Date は、別の import 文と一致しません」という文言です。これはおそらく、「インポート java.sql.Date は、別の import 文と一致します」だと思うのですが、とにかく同じクラス名の複数のimport文は記載できない模様です。

なので、どちらかはimportを諦めてクラスを使う場所でパッケージも指定する形(完全修飾クラス名)で定義すれば良さそうです。

java.sql.Dateを完全修飾クラス名で定義

ここでは、「java.util.Date」はimportをしてるので使う側では単純にDateと定義し、「java.sql.Date」は完全修飾クラス名で定義しています。

Javaで処理時間を計測する方法

Javaで何らかの処理時間を計測するプログラムを以下に置いておきます。完全に個人的なメモです。この方法では、ミリ秒で計測します。

public class Sample {
	public static void main(String[] args) {
		long start = System.currentTimeMillis();

		// 計測する処理をここに記載

		long end = System.currentTimeMillis();
		System.out.println((end - start)  + "ms");

	}
}

正確に調べるためには、ナノ秒を使うのが良いとかJVMのウォーミングアップが必要とか、色々とあるようですが、簡単に調べる方法としては上記のやり方でまずは良さそうです。

JDK 9から追加された「JShell」がとても便利だ

少し前に、Javaの入門書にあたる以下の書籍を購入しました。
gihyo.jp

この書籍で、「JShell」というとても便利なツールが紹介されていました。

このツールはJDK 9から標準で追加されているREPL(Read-Evaluate-Print-Loop)ツールと呼ばれるもので、コマンドラインベースでJavaの動作確認をとても手軽に行うことができるものです。

今まで、Javaで"Hello World!"という文字列を表示するようなプログラムを実行するためには、最低でも以下のようなコードが必要でした。このコードで実際に出力を意味する部分は「System~」の1行のみで、他はおまじないで書かなくてはならないところです。

public class Sample {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}


そして、Javaコンパイルをして実行をする必要があるのでWindows環境ではコマンドプロンプトで以下のような手続きをする必要があります。

コマンドプロンプトJavaを実行


このように、Javaでは今までは簡単な動作確認でもやや面倒な手続きを実行しなくてはなりませんでした。

しかし、この「JShell」を使えばとっても簡単にJavaを実行することができます。以下ではコマンドプロンプトで「JShell」を起動し、Javaで"Hello World!"という文字列を表示するプログラムを実行しています。

jshellを起動して、簡単なプログラムを実行

見ての通り、先に紹介した古典的なやり方で記載したおまじない部分の記載がいっさい不要なので、実際にやりたいことのみを実行している感じがわかると思います。

「JShell」は他にも色々とできることがありますが、それらは別の方々の記事をご覧いただければ分かりますので、今回は簡単な紹介までにしておきます。

おわりに

いかがでしょうか。私自身、正直JDK 8でほぼ止まっていたのでこのようなツールが出ていることを全く知りませんでした。

このように随時新しい機能は増えていると思うので、これからはチェックを怠らないようにしていきたいところです(なかなか難しいですが。。)。

今回はこの辺で失礼いたします。最後までお読みいただきありがとうございました。

参考

JShellで始めるJava入門 - Javaの世界を覗いてみる #Java - Qiita
上記は記事の冒頭で紹介した書籍の著者の一人が書かれた記事です。「JShell」の機能をとても詳しく紹介されていますので、こちらの記事を見れば使い方は分かると思います。