kagamihogeの日記

kagamihogeの日記です。

java.lang.StringBuffer探訪

JDK探訪第二回。基本的にターゲットのコードはJDK1.4をベースにしています。

昨日はStringを見て回ったので今日はStringBuffer。意外・・・というほどでもないかもしれないけど、Stringとは色々と接点がありました。あと驚いたのが、1.4と1.5で両クラスに修正が入っている点。昔からあるクラスにも修正入るもんなんだねぇ。*1

昨日と同じように印象に残った部分についてメモ。

シフト演算で割り算

StringBuffer#reverseのループはこんな風になっている。

for (int j = (n-1) >> 1; j >= 0; --j) {
...

(n-1)を右に1ビットずらすって何?と思ったけど、要は文字列の真中のインデックスを出している。2進数で1桁減らすと、2で割ったのと同じ効果・・・と昔大学の授業で計算問題やったようなやらなかったような。

10進数で1桁減らすと10で割ったのと同じ意味になるんだから、考えて見たら当たり前のことですな。

StringBufferとStringの文字列共有

この項目は1.4では通じるけど、1.5は関係がないので注意。

昨日、Stringのoffsetとcountの意味がわからん、と書いたけど少なくともcountの存在意義はStringBuffer見ることでわかった。

カギはStringのStringBufferを引数にとるコンストラクタ

public String (StringBuffer buffer) {
synchronized(buffer) {
buffer.setShared();
this.value = buffer.getValue();
this.offset = 0;
this.count = buffer.length();
}
}

こんな感じにStringBufferからStringを生成するけど、this.value = buffer.getValue();の行が重要。StringBufferと同じ実メモリ上の文字列を参照するようにStringのインスタンスを生成する。これは、Stringはインスタンス単位ではimmutableだけど、メモリ単位で見ると必ずしもimmutableにはならないことを示している。

デバッガで確認するとわかるけど、たとえばStringBuffer#append(char)でバッファ文字列を変更するとString側で保持している文字列も変更される。ただし、Stringはoffsetとcountでchar[]のうちどこからどこまでを実際に使用するかを決めるので、Stringのimmutableなインタフェース通してのアクセスなら別の文字列を見ているように見えるので問題にならない。

とはいえ、StringBuffer#insert()やdelete()が呼ばれるとStringのoffsetとcountだけでは対処できなくなる。そのため、StringBufferはinsert()やdelete()が呼ばれたときなどの適当なタイミングでStringBufferとStringのインスタンスが別々のメモリ領域を見に行くようにコピーをしている。

なぁんか面倒なことしてるんだなぁ・・・というのが印象でした。

JDK1.5のStringBufferを引数にとるStringのコンストラクタ

てなわけでJDK1.5の方はどうなっているかというと・・・

public String(StringBuffer buffer) {
String result = buffer.toString();
this.value = result.value;
this.count = result.count;
this.offset = result.offset;
}

StringBufferとStringで文字列を共有しているかどうかを示すsetShared()の呼び出しがなくなり、StringBuffer#toString()で新しい領域に文字列をコピーするようにしている。*2

*1:1.5から導入されたStringBuilderの影響がおおきいのだと思われる(未調査)

*2:StringBuffer#toString()の実装も1.4と1.5で違うのだけどここでは割愛