StringBufferとStringBuilderの違い

javaには文字列を可変的に扱うためのクラスとして StringBuffer 及び StringBuilder という2つのクラスが用意されています。
この2つのクラスは文字をバッファとして取り扱い、任意のタイミングで任意の文字列を詰め込むことができ、非常に使用頻度の高いクラスとなっています。
されとてこの2つのクラス同じような事できるのですが具体的に、どう異なるかご存知でしょうか?
本記事ではこの2つのクラスの差について調査します。

なお対象としては java8 の API を対象とします。

調査する

公式の java8 API を参照してみましょう。参考リンクについては本記事の末尾に記載しますので参照してください。

と、いきなり回答にたどり着いてしまうのですが StringBuilder のマニュアルを確認すると、以下のような記載があります。

文字の可変シーケンスです。このクラスは、StringBufferと互換性があるAPIを提供しますが、同期化は保証されません。このクラスは、文字列バッファが単一のスレッド(一般的なケース)により使用されていた場合のStringBufferの簡単な代替として使用されるよう設計されています。このクラスは、ほとんどの実装で高速に実行されるので、可能な場合は、StringBufferよりも優先して使用することをお薦めします。

つまり StringBuilder はスレッドセーフではないシンプルなケースに適用でき、それ以外の場合は StringBuffer を使用すると良い、ということです。

シンプルに使い所をまとめると

StringBuffer

スレッドセーフであるので複数スレッドから参照されるような場合に適切
スレッド間の排他制御を実装している。

StringBuilder

シングルスレッドで排他処理の必要がないシンプルな処理の場合に適切
使用できる場合はこちらのクラスを使用したほうがStringBuilderよりも高速に処理が行える

というところですね。

実験してみる

実装レベルでの検証のために下記のようなサンプルコードを用意しました。
ちなみに groovy コードとなりますのでご留意ください。

同じ文字列バッファを共有したスレッドを10個作成してそれぞれのスレッドから100回書き込みを行います。
最終的にバッファの内容がどうなっているかを確認するシンプルなコードです。
これを文字列バッファをStringBufferとStringBuilderそれぞれに切り替えて結果を確認してみます。

import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

class WriteThread implements Runnable {
  int num
  StringBuffer sb

  public WriteThread(int num, StringBuffer sb) {
    this.num = num
    this.sb = sb
  }

  @Override
  public void run() {
    for (int i=0; i<100; i++) {
      this.sb.append("I am thread ${num}.\n")
    }
  }
}

StringBuffer sharedBuilder = new StringBuffer()
ExecutorService executor = Executors.newFixedThreadPool(10)
for (int i=0; i<10; i++) {
  executor.submit(new WriteThread(i, sharedBuilder))
}
executor.shutdown()
executor.awaitTermination(10, TimeUnit.SECONDS)

println sharedBuilder.toString()

StringBuffer

結果の一部のみを抜粋しますが、きれいに文字列が出力できていることが確認できます。

...
I am thread 6.
I am thread 1.
I am thread 3.
I am thread 7.
I am thread 3.
I am thread 1.
I am thread 6.
...

StringBuilder

対して StringBuilder の方は下記のように破損している箇所が見られます。
(※見れられないこともあります)
書き込みが衝突してしまったということです。

...
I am thread 7.
I am thread 9.
ead 8.
thread 9.
ad 1.
ad 0.
I am thread 5.
...

まとめ

とりあえずマルチスレッドなケースが求められる場面では StringBuffer を使いましょう。
にしても名前がややこしいな・・・どっちがどっちだかわからなくなる。

参考

StringBuilder
StringBuffer


GASでスプレッドシートのセルの書式を文字列に設定する

GAS 便利ですよね。
スクリプトでサクッとかける上にもともと拡張性の高いスプレッドシートなのでちょっとした実装でも良いものが作れます。
スプレッドシートなんかのインタフェースはエンジニア以外の人にも馴染み深いのでコストをかけて画面系を実装するよりも適している場合がよくあります。

なのですがGASを利用する際に正しくAPIやその動作を理解していないと、非常に重い動作になってしまうことがあります。
テストしてみたらループ処理なんかでずーっと時間がかかってとまらないスクリプトなんてこともあります。

今回GASでスプレッドシート上のセルの書式を文字列にできないかというところを試行錯誤にして改善してみたので、一つのパフォーマンス改善のアプローチとして参考にしてもらえればと思います。

やりたいこと

具体的にやりたいことはシンプルで、セルの値の先頭に + を入力したいということです。しかしそのままやろうとするとこれがうまくいかない。
デフォルトの状態ではスプレッドシートは先頭に+を認識すると値を数式であると認識して、展開しようとします。
実際には文字列ですので式の値が不正でありエラーとなります。

エクセルですと書式設定で テキスト なんていう書式があるようですが、現在のスプレッドシートの書式にはこのケースを許容してくれる書式は無いようでした。

ではどうするかというと先頭にシングルクォート()を打ち込みます。
こうすることでセルに入力された値をテキストだと認識してくれます。

問題はこれを大量のセルに対して実施したいということ。
ちまちま一つ一つのセルを手作業で編集していたのでは気が遠くなります。

GASを使って解決してみた・・・

始めに単純な思いつきで実装してみます。
レンジに関してはあまり深く考えないで良いです。

var valueRange = sheet.getRange('A1:A'); // A列に存在するデータに対して実施
for (var i=0; i<valueRange.getNumRows(); i++) {
  var cell = valueRange.getCell(i, 0);
  var value = cell.getValue();
  if (value == "") {
    continue;
  }
  cell.setValue("'" + values);
}

余裕のある人は一度実装してみてください。
動かしてみるとわかるのですがこの実装、めちゃめちゃ遅いです。

何故かと言うと getValue, setValue というAPIをforループ内で毎回呼び出しているからなんですね。

スプレッドシートは基本的にサーバ側で反映されたデータがブラウザに反映されます。
ですので上記のスクリプトでデータを書き換えている箇所は、ブラウザ上に表示されているデータを書き換えているのではなく実際にはGoogleのサーバ上にあるスプレッドシートのデータを編集しているような設計になっています。
で、ブラウザは逐次その反映されたデータを更新して表示しているということになります。
なのでループ毎に getValue, setValue を呼び出すことがものすごい遅延につながります。

ここまでの話で想像できるかと思いますが、GASの動作を高速化する際にまず考えることは GASのAPI呼び出しをなるべく少なくする ということです。
スプレッドシートを取り上げますがその他のGServiceでも同じことだと思います。
まーRDBでもなんでもバッチ処理にする。ということは基本ですね。。

この点について留意して最適化したコードを実装してみましょう。

処理を最適化する

下記が最適化されたコードになります。

var valueRange = sheet.getRange('A1:A');
var values = valueRange.getValues();
for (var i=0; i<valueRange.getNumRows(); i++) {
  if (values[i][0] == "") {
    continue;
  }
  values[i][0] = "'" + values[i][0];
}
valueRange.setValues(values);

こちらも実行していただけるとわかるのですが、一瞬で実行が完了します。
見ての通り API呼び出しを getValues, setValues にすることでたったの二回に減らすことができています。

APIマニュアルを良く読んで自分がやりたいことが他の方法で実現できないか?とよく考えると意外と様々なAPIが存在しているのでぜひ確認してみてください。
今回はこの方法で目的を達成する事ができました。
それでは。


改めてphpのerror_reportingは心もとないなと感じた

久しぶりに他人が実装したphpコードを改修する機会があり、改めてphpのエラーレポートレベルに関して思うところがありました。

コードがあったほうが話がわかりやすいと思いますので用意しましょう。

はい。例えば、下記のようなコードが合ったとしましょう
fetchに関しては適当なレコードが連想配列で返却されるとイメージしてください。

<?php
ini_set('display_errors', 'On');
error_reporting(E_ALL);

class Car {
	public $engine;
	public $wheel;

	public static function apply($record) {
		$car = new Car();
		$car->engine = $record['engine'];
		$car->wheel = $record['wheel'];
		return $car;
	}
}

$carRecord = $pdo->fetch(PDO::FETCH_ASSOC);
$car = Car::apply($carRecord);

これは問題なく動きます。
ところが開発途中でRDBのテーブルの属性名がイケてないなと思い wheel という属性名を wheels と複数形にしたとします。

ソースコードに関しては変更するのを忘れてしまいました。
だとしても当然スクリプト言語ですから動きます。これは。そういう設計ですから。

Notice errorを表示する設定の場合は、下記のようなエラーを吐いてくれます。

Notice: Undefined index: wheel in hoge.php on line 12

これは文字通り連想配列に存在しないインデックスにアクセスしているというエラーなのですが、エラーレベルってNoticeなんですよね。
これもうアプリケーション動かないじゃないですか
しばらくコンパイラ言語で開発していると少々このギャップに戸惑ったという話。

さらに公式によると error_reporting の初期値は E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED なんですよね。
初期値にNoticeが含まれていないんですよね
これがまた学習レベルの浅いphpユーザがなんたるかと言われることにつながっているような気がしなくもないです。

開発中はできればNoticeエラーをonにすることで、Noticeエラーは積極的に潰していきましょう。

さらに別の側面から見るために下記のようなコードを用意しました。

<?php
ini_set('display_errors', 'On');
error_reporting(E_ALL);

class Car {
	public $engine;
	public $wheel;

	public static function apply($record) {
		$car = new Car();
		$car->engine = $record['engine'];
		$car->wheel = $record['wheel'];
		return $car;
	}
}

$car = new Car();
$car->status = "stop";

phpは未定義のメンバ変数を動的に追加できます。
つまり上記のコードはエラーでもなんでもなくphpでは正しいコードなのです。そういう設計ですから。

これもなかなかしんどいですよ。例えば意図的ではなくタイポなどが原因だったらバグですから。
こういった事象をUTなどでカバーするっていうのは、なんかちょっと無駄なような気がしますし、できたとしてもそれに追従するコストが結構かかります。
ようするに無駄なコストをかけざるをえないんじゃないかと思います。

コンパイラ言語などでコンパイル時に検出できるたぐいの不具合であれば、そちらを使うに越したことはないじゃないか。という結論。