HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

Stringオブジェクトに関する罠

Stringオブジェクトに関しても、Booleanオブジェクトと同様に不変オブジェクトであるが、同一の文字列を表現する異なるStringオブジェクトを生成できてしまう。
生成されてもすぐに捨てられるのなら問題はあまりないのだが、セッションデータとして保持し続けるなど比較的長い時間生存するオブジェクトとなる場合には注意が必要。同じ文字列を表すStringオブジェクトが1つしかないのと10万個あるのとでは大違いだから。
で、Javaの基本的なクラスのメソッドなどで「同一の文字列を表現する異なるStringオブジェクト」が生成されるケースを少し調べてみた。
まぁ、どれもそういう結果になるのは当然なんだけど、ちゃんと意識しないと無駄なメモリを使っちゃうんだぞ、と。

シリアライズ/デシリアライズ

例えば、JBossCacheのように複数のサーバー間でのオブジェクトのやり取りにシリアライズ/デシリアライズを使うようなケースが該当する。

PipedInputStream pin = new PipedInputStream();
PipedOutputStream pout = new PipedOutputStream(pin);
ObjectOutput out = new ObjectOutputStream(pout);
ObjectInput in = new ObjectInputStream(pin);

String s1 = "Hello";
out.writeObject(s1);
String s2 = (String) in.readObject();
System.out.println(
        System.identityHashCode(s1)
        + " <==> "
        + System.identityHashCode(s2));
System.out.println("same? " + (s1 == s2));

これを実行すると次のようになる。

6413875 <==> 21174459
same? false

String#split(String pattern)

分割した結果、配列の複数の要素が同じ文字列を表すStringオブジェクトとなるケースを考えてみる。

String str = "a:a:a";
String[] ss = str.split(":");
for (String s : ss) {
    System.out.printf("%8s: %10d %10d\n", "\"" + s + "\"",
            System.identityHashCode(s),
            System.identityHashCode(s.intern()));
}

これを実行すると次のようになる。

     "a":     827574   17510567
     "a":    6718604   17510567
     "a":    8918002   17510567

String#substring(int beginIndex, int endIndex)

同じStringオブジェクトに対して、同じインデックスを指定するsubstring()メソッド呼び出しを複数

String str = "Hello World!!";
System.out.printf("%10d\n", System.identityHashCode(str.substring(3, 7)));
System.out.printf("%10d\n", System.identityHashCode(str.substring(3, 7)));
System.out.printf("%10d\n", System.identityHashCode(str.substring(3, 7)));
System.out.printf("%10d\n", System.identityHashCode(str.substring(3, 7)));

これを実行すると次のようになる。

  30771886
   8637543
  14718739
  14577460

Integer#toString(int num)

Integerクラスを使うのはあくまでも代表として。
まぁ、staticメソッドなので当然と思う度合いは高いだろうけど。

System.out.printf("%10d\n", System.identityHashCode(Integer.toString(1)));
System.out.printf("%10d\n", System.identityHashCode(Integer.toString(1)));
System.out.printf("%10d\n", System.identityHashCode(Integer.toString(1)));
System.out.printf("%10d\n", System.identityHashCode(Integer.toString(1)));

これを実行すると次のようになる。

  22474382
   4699264
  26954214
   1102920

Integer#toString()

こっちはインスタンスメソッド。同一オブジェクトに対して複数回呼んでみる。

Integer num = new Integer(1);
System.out.printf("%10d\n", System.identityHashCode(num.toString()));
System.out.printf("%10d\n", System.identityHashCode(num.toString()));
System.out.printf("%10d\n", System.identityHashCode(num.toString()));
System.out.printf("%10d\n", System.identityHashCode(num.toString()));

これを実行すると次のようになる。

  14737862
    444956
  29303146
  19581314

で‥

これだけ並べておけば、大概のケースで「同一の文字列を表現する異なるStringオブジェクト」が生成されるんだということを意識付けできるだろう(謎)