連載
» 2003年11月07日 00時00分 公開

基礎から学ぶサーブレット/JSP(9):Javaでファイルや画像を扱う (2/4)

[山田祥寛,@IT]

アクセスログを記録する―FileWriter/BufferedWriter・StringBuffer・Calendar/Date/SimpleDateFormatクラス―

 テキストファイルはニュートラルなフォーマットです。RDBと異なり、大量のデータをさまざまな角度から処理する用途には向きませんが、複数のプラットフォーム間でデータを交換したい、どのような環境でも読み書きできる形式でデータを保存したいという場合には最適な媒体といえます。何から何までRDBにこだわる必要はないのです。

 リスト2は、java.io.FileWriter、BufferedWriterクラスを利用して、アクセスログをタブ区切りテキストの形式で記録する一例です。

リスト2 bundle.jsp
<%@ page contentType="text/html;charset=Shift_JIS"
          import="java.io.*,java.util.*,java.text.*" %>
 <%
 FileWriter objFw=new FileWriter(application.getRealPath("sample.log"),true);
 BufferedWriter objBw=new BufferedWriter(objFw,10);
 StringBuffer objSb=new StringBuffer();
 Calendar objCal=Calendar.getInstance();
 SimpleDateFormat objFmt=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
 objSb.append(objFmt.format(objCal.getTime()));objSb.append("\t");
 objSb.append(request.getServletPath());       objSb.append("\t");
 objSb.append(request.getRemoteAddr());        objSb.append("\t");
 objSb.append(request.getHeader("referer"));   objSb.append("\t");
 objSb.append(request.getHeader("user-agent"));objSb.append("\t");
 objBw.write(objSb.toString());
 objBw.newLine();
 objBw.close();
 %>

 リスト2の実行結果は以下のとおりです。

「.jsp」ファイルによって記録されたログファイル 「.jsp」ファイルによって記録されたログファイル

 リスト2の内容を説明しましょう。Javaでファイル書き込み用の機能を提供するのは、FileWriterクラスの役割です。第1引数にファイル名の絶対パスを、第2引数に追記書き込みかどうかを指定します。もしも第2引数にfalseを指定した場合、ログファイルは1回ごとにクリアされ、蓄積されませんので注意してください。

 また、FileWriterクラスはそれ単体でも書き込み可能ですが、一般的にBufferedWriterクラスと併せて使用するのが通例です。というのも、FileWriterクラスが提供するファイル書き込みの機能は極めて非効率なものであるからです。1バイト受け取っては1バイト書き込みに行くFileWriterクラスの機能では、特に大量の文字列を書き込む用途には不向きです。しかし、BufferedWriterクラスは受け取った文字列をいったんバッファと呼ばれるメモリ領域にプールしたうえでまとめて書き込みを行うことで、より効率的な書き込みを行います。初期状態で確保されるバッファの容量は、BufferedWriterオブジェクトのコンストラクタの第2引数で自由に指定することができます。

 書き込むログデータは、StringBufferオブジェクト上で編集します。現在の日付、パス、IPアドレス、リンク元、ユーザーエージェントをタブ(\t)区切りで編集した後、文字列に変換したものをBuffereWriter#writeメソッドで書き込みます。newLineメソッドは、BufferedWriterオブジェクトに対して改行文字を書き込みます。

StringBufferオブジェクトを使用する意味

 文字列を単に連結していくだけならば、いつもどおりのStringオブジェクトと「+」演算子で十分ではないかと思われた方もいらっしゃるかもしれません。しかし、このように何回にもStringオブジェクトを連結していく操作は、パフォーマンス上推奨されないものです。

 というのも、Stringオブジェクトとは「固定文字列」であるからです。つまり、一度Stringオブジェクトに格納された文字列は変更することができません。「+」演算子で加工されたと見えるような場合にも、その実、内部的には新しいStringオブジェクトを生成しているにすぎません。古いオブジェクトは内部的に破棄されます。それがどれだけ無駄なことかお分かりになるでしょうか。

 しかし、StringBufferオブジェクトは最初から文字列を格納するための一定のバッファ領域を確保します。つまり、appendメソッドによって文字列を追記する際にも、「いまある」StringBufferオブジェクトを加工しているにすぎません。連結に際して生成されるオブジェクトは常に1つなのです。

 Stringオブジェクトが固定長文字列を扱うオブジェクトだとするならば、StringBufferオブジェクトは可変長文字列を対象としたオブジェクトであるといえます。

日付データを操作するCalendar/Dateクラス

  日付データを保持し、操作するための一般的な手段を提供するのはCalendarクラスの役割です。Calendarクラスは日付情報の取得や設定、演算など、一般的な日付操作の手段を提供します。以下に、Calendarクラスにおける主なメソッドを挙げておくことにしましょう。

Calendarオブジェクトの主なメソッド
メソッド 概要
add(日付フィールド,値) 指定した日付フィールドの値を指定値だけ加算
equals(Calendarオブジェクト) 指定されたCalendarオブジェクトと等しいか
get(日付フィールド) 引数で指定された日付情報を取得
getTime() Dateオブジェクトを取得
set(年,月,日[,時,分[,秒]]) 指定された日時で設定(Calendarオブジェクトのデフォルトは、現在の日時)
set(日付フィールド,値) 指定した日付フィールドの値を指定値で設定

 なお、日付フィールドに指定できる値は以下のとおりです。

指定可能な日付フィールド
メソッド 概要
Calendar.YEAR
Calendar.MONTH 月(0〜11) ※1〜12でない点に注意!
Calendar.DATE
Calendar.DAY_OF_WEEK 曜日(1〜7)
Calendar.AM_PM 午前(0)/午後(1)
Calendar.HOUR 時(0〜11)
Calendar.HOUR_OF_DAY 時(0〜23)
Calendar.MINUTE
Calendar.SECOND
Calendar.MILLISECOND ミリ秒

Calendarクラスをインスタンス化する場合、通常のクラスのようにnew演算子ではなく、Calendar.getInstanceメソッドを使用しなければならない点に注意してください。これは、Calendarクラスが「抽象(abstract)クラス」であるからです。抽象クラス——すなわち、APIリファレンス上で修飾子にabstractと付くクラスをインスタンス化する場合には、new演算子を使用することはできません。


public abstract class Calendar
extends Object
implements Serializable, Cloneable → Calendarクラスのシグニチャ  

 なお、日付情報の比較を行う場合には、Dateオブジェクトを使用します。Dateオブジェクトは、Calendar#getTimeメソッドを介して取得することができます。

Dateクラスの主なメソッド
メソッド 概要
after(Dateオブジェクト) 日付が指定されたDateオブジェクトより未来である場合にtrue
before(Dateオブジェクト) 日付が指定されたDateオブジェクトより過去である場合にtrue
getTime() 1970/1/1からの経過ミリ秒を取得

 CalendarとDateオブジェクト、似たもの同士で同名のgetTimeメソッドがありますが、それぞれに異なる役割ですので、間違えないように注意してください。

日付データを加工するSimpleDateFormatクラス

 日付データを単純に出力したいというのであれば、Calendar/DateクラスでもtoStringメソッドを介することで取りあえずのデータを出力することはできます。しかし、いまさらいうまでもなく、日付データはさまざまな要素から形成されます。日付の部分だけ「2003年10月4日」のように出力したい場合もあれば、時刻の部分だけ「23:35:13」のように表示したいケースがあるかもしれません。同じ日付でも「10/04/2003」のような形式で見せたいということもあるかもしれません。

 そのような複合的な情報の集積である日付データを操作するために、JavaではSimpleDateFormatという日付加工用の専用クラスを用意しています。使い方は極めて簡単です。以下のリストのように日付フォーマットを指定し、formatメソッドを実行するだけです。formatメソッドの引数にはCalendarオブジェクトではなく、Dateオブジェクトを指定しなければならない点に注意してください。

SimpleDateFormat objFmt=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
 objSb.append(objFmt.format(objCal.getTime()));

 日付フォーマット式として指定可能な識別子は、以下のとおりです。

識別子 概要
G 紀元(AD/BC)
y
M
d
E 曜日(月〜土)
a AM/PM
h 時間(1〜12)
H 時間(0〜23)
m
s
S ミリ秒
D 通年で何日目か
w 通年で何週目か
W 通月で何週目か

 これら識別子をベースとして、あとは任意の文字列を含めた日付フォーマット式を生成します。なお、例えば「M」1文字でも月は表示されますが、「MM」とすることで月が1けたの場合に「08」「09」のようにけたそろえすることができます。

テキストファイルへの書き込みには、FileWriter/BufferedWriterクラスを併用するのが一般的です。FileWriterはファイル書き込みの基本的な手段を、BufferedWriterクラスは書き込み処理を効率化する手段を、それぞれ提供します。


日付データの操作・比較にはCalendar/Dateクラスを使用します。日付データをより出力を意識した形式に変換したい場合には、SimpleDateFormatクラスを利用します。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。