Javaに関する情報を記述します。
新しく書いたものが上になるように並べてあります。
よく忘れるのでメモ。
Javaの引数に以下を付けると、JPDA経由でデバッグが可能。
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y
これで起動すると、プログラムは一時停止状態になる。
あとは Eclipse で Debug -> Remote Java Application などを使えば
Eclipseの外から起動したプログラムについてデバッグ可能。
Webアプリの場合は、サーバアプリケーションの起動時に指定すればOK。
なお、suspend=n にするとプログラムは一時停止することなく通常通り起動する。
あとは必要なタイミングで Attach すればよい。
Tomcat などもこの方法で起動しておけばいつでもリモートデバッグが出来る。
Cなどと違ってJPDA指定無しで起動したプログラムにAttachは不可能なので
予め指定して軌道しておく必要がある。
JPDA指定で起動してもプログラムが重くなったりはほとんどしない(多分)。
ただしAttach時には当然重くなる。
Javaは標準で文字のcharset(SHIFT_JISなど)に対応しています。
しかし、言語レベルで対応してるから安心、などと思っていたら大間違い。
特にWeb系では、多くの人が一度くらいハマったことがあるはずです。
今回紹介する Commons Fileupload もその一つ。
これは、Webのファイルアップロードを扱うライブラリです。
ちなみに今回使ったのは 1.1.1
です。
<input type="text" name="user"> <input type="file" name="bgfile">
↑このようなHTMLから送信した場合を考えます。
FileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); List<FileItem> items = upload.parseRequest(request); for (FileItem item : items) { if (item.isFormField()) { // file以外の要素 String fieldName = item.getFieldName(); // "user" String fieldValue = item.getString(); // ユーザが入力した値 } else { // file要素 String fieldName = item.getFieldName(); // "bgfile" String fileName = item.getName(); // アップロードしたファイル名 byte[] fileContents = item.get(); // アップロードしたファイル内容 } }
こんな感じで使います。
file要素は、日本語のファイル名でも取得に成功します。
しかし問題は、file以外の要素の場合です。
例で言うと、fieldValue
の値が文字化けしてしまいます。
ソースを追ってみると、常に ISO-8859-1 でデコードされているようです。
これを解消するには、以下のようにします。
String fieldValue = item.getString(encoding);
ここで指定するエンコーディングは、HTTPリクエストの文字セットです。
つまり、こうです。
String fieldValue = item.getString(request.getCharacterEncoding());
なんか非常に無駄な事をしているような気がします。
本来なら、エンコーディング指定無しの getString
が同等の処理をするのが
筋だと思うのですが…
Javaからプログラム言語を始めた人にとって、ファイナライザという存在は理解しにくいかもしれません。
C++をやっていた人ならば「デストラクタと似たようなもの」と説明すればわかりやすいでしょう。
で、ファイナライザとは。
「オブジェクトが消滅するときに呼び出されるメソッド」のことです。
例えば、何らかの後処理が必要なときなどに使用します。
実装するには、Objectクラスで定義されている finalize() メソッドをオーバーライドします。
まず最初に言っておかなければならない事があります。
ファイナライザは、どうしても必要ではない限り実装すべきではありません。
なぜなら、ファイナライザを実装したインスタンスが存在すると
ガーベッジ・コレクション(以下GC)のパフォーマンスが大幅に落ちるからです。
これは、様々な場所で言及されています。
以上を踏まえた上で、ファイナライザを実装する場合の注意点を挙げます。
まず、ファイナライザは「いつ呼び出されるかわからない」という事です。
C++ならば、明示的にdeleteを呼び出すことでオブジェクトは破棄されます。
しかし、GCを採用しているJavaにはそのような処理が備わっていません。
仮にインスタンス(の参照)に null を代入しても、その場でファイナライザが実行されるわけではありません。
インスタンスは、次にGCが実行されたときに
JavaVMが「どこからも使用されていない」と判断されて初めて破棄されます。
それは、あなたが望むタイミングに限り無く近いかもしれないし、あるいはとてつもなく後だったりするかもしれません。
ですので、インスタンスに対して確実に後処理を行いたい場合
ファイナライザに頼ってはいけません。
代わりに、特定のメソッドを呼び出してもらいます。
例えば、データベース接続を閉じたい場面で明示的に close() メソッドを呼び出してもらうのです。
こうすれば確実に、そして望んだタイミングで後処理を実行させることが出来ます。
先程例に挙げたデータベース接続のように、有限のリソースと1対1で対応するようなインスタンスの場合
そのクラスにはファイナライザを実装することが望ましいでしょう。
もし、明示的に close() を呼び出すのを忘れてしまったら
この接続は永久に(もしかしたらJavaVMを終了させた後も)残ったままになってしまうからです。
close() のような後処理メソッドを実装することも当然ですが、
「最後の砦」としてファイナライザを実装することはパフォーマンスを犠牲にしてでも必要な事でしょう。
ところが、データベース接続クラスにファイナライザを実装していないベンダもいます。
例えば超大手のO社(笑)。
パフォーマンス向上のため?いやいや、それは無いでしょう。
だったらもう少しマシなJDBCドライバを用意してるはずですから。
なので、もしDB接続を開放し忘れてしまうと
JavaVMを終了させても接続が残ってしまいます。
つまり、この接続は「永久に誰からも利用されないまま」です。
テストコードを書いているときなど、DB接続を開放し忘れたり
例外が発生してプログラムが終了してしまうことなどはよくある事なのです。
この場合、DB接続が溜まり続けていってしまい
限界を超えるとOracleサーバは接続オーバーのエラーを返します。
こうなってしまうと、これを解決するにはサーバを再起動するしかありません。
もし開発者にサーバ再起動の権利が与えられていなかったら、
管理者がDBサーバを復旧するまで開発をストップするしかないのです。
まったく、ひどい仕様ですね。
MySQLなどは、うっかり接続を閉じ忘れても
JavaVMが終了するタイミングでファイナライザがDB接続を全て開放してくれます。
さらに、Ctrl-CなどでJavaVMを強制終了した場合にはファイナライザ自体が呼ばれないので
DB接続が残ったままになってしまいますが、
一定時間(MySQLなら8時間)アクセスの無い接続は自動的に切断される為、こういった場面にも対応できます。
これにより、DB接続が延々と溜まり続けてしまうという事態はほとんど発生しなくなります。
「MySQLってすげー」?いや、これが普通なのですよ。
ほとんど全ての開発者は、こういった対応を望んでいるのですから。
通常、グラフィックイメージを作成するときは
java.awtパッケージ以下のクラス群を利用します。
しかし、これらのクラスはラップされているので処理速度に若干の不安が残ります。
高速に描画したいときには、どうすれば良いのでしょうか。
Windows以前からパソコンを使っていた人にしてみれば答えはすぐに判るかもしれません。
グラフィックイメージは、言ってみればピクセルの集合なので
これを直接操作してしまえば良いのです。
まず、BufferedImageクラスのインスタンスを生成します。
new BufferedImage(width, height, imageType, colorModel);
幅と高さ、色情報をパラメータとして渡します。
pngなどの画像ファイルからBufferedImageを生成することも出来ます。
「pngを使う」などを参考にして下さい。
まずは、BufferedImageインスタンスが保持するDataBufferフィールドを取得します。
DataBuffer buffer = image.getRaster().getDataBuffer();
DataBufferは抽象(abstract)クラスであり、実際にはこのサブクラスを利用することになります。
if (buffer instanceof DataBufferByte) { byte[] pixels = ((DataBufferByte)buffer).getData(); } else if (buffer instanceof DataBufferInt) { int[] pixels = ((DataBufferInt)buffer).getData(); }
こんな感じでピクセルデータの配列を取得します。
ピクセルを加工するには以下のようにします。
byte getPixel(int x, int y) { return pixels[y * width + x]; } void setPixel(int x, int y, byte col) { pixels[y * width + x] = col; }
ピクセルデータは1次元配列なので、2次元(XY)座標から1次元に変換しています。
上の例では1ピクセルが1byteに対応していましたが、そうでは無い場合も存在します。
image.getRaster().getNumDataElements()
により、1ピクセル当たりの要素数を取得できます。
例えばこれが「3」の場合などは、先程のメソッドを以下のように定義する必要があります。
byte[] getPixel(int x, int y) { return new byte[] { pixels[(y * width + x) * 3], pixels[(y * width + x) * 3 + 1], pixels[(y * width + x) * 3 + 2] }; } void setPixel(int x, int y, byte[] cols) { pixels[(y * width + x) * 3 ] = cols[0]; pixels[(y * width + x) * 3 + 1] = cols[1]; pixels[(y * width + x) * 3 + 2] = cols[2]; }
pixelsの内容を変更すれば、BufferedImageが表すイメージの内容も変更されたことになるので
あとはこれを画面に出力するなりファイルに保存するなり
お好きなようにしましょう。
この方式ならば、例えば (1000×1000)程度のイメージの全ピクセルを
ループしながら塗っていくといった作業も一瞬にして済ませる事が可能です。
png形式の画像を扱ってみます。
Javaには標準でこれらのライブラリが付属していますから比較的簡単です。com.sun.imageio.plugins.png
パッケージを使います。
3つの手順が必要です。以下に例を示します。
PNGImageReader imageReader = new PNGImageReader(new PNGImageReaderSpi()); imageReader.setInput(new FileImageInputStream(new File("test.png"))); BufferedImage image = imageReader.read(0);
まず、PNGImageReaderインスタンスを生成します。
次に、読み込むpngのFileオブジェクトをパラメータにしてFileImageInputStreamオブジェクトを生成します。
最後に、PNGImageReaderインスタンスのreadメソッドを呼び出します。
このメソッドは戻り値としてBufferedImageを返すので、これを利用することができます。
readメソッドは、呼び出しの度にBufferedImageインスタンスを生成します。
これは、大量のpng読み込みを行う際には大変なコストが掛かることを意味します。
Javaでは大容量メモリの確保にはそれなりの時間が掛かるからです。
これを回避する為に、予めBufferedImageインスタンスをこちら側で用意してそれを使わせる事も可能です。
以下に例を示します。
BufferedImage image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_BYTE_INDEXED); ImageReadParam param = new ImageReadParam(); param.setDestination(image); BufferedImage image = imageReader.read(0, param);
readメソッドの第二パラメータに ImageReadParam
インスタンスを渡します。
こうすることによって、readメソッドはこちら側で用意した image
インスタンスを利用するようになります。
読み込みとほぼ同じです。
PNGImageWriter imageWriter = new PNGImageWriter(new PNGImageWriterSpi()); imageWriter.setOutput(new FileImageOutputStream(new File("test.png"))); imageWriter.write(image);
書き出したい画像を image
インスタンスに格納してwriteメソッドを呼び出します。