Home日記コラム書評HintsLinks自己紹介
 

2004年9月のアレ

まあ日記というのか、そういうものだ。

← 2004-08 | 日記一覧 | 2004-10 →

2004年9月9日

裏表 に、PHP のロックの話を書いたのだけど、 flock ってどういう動作かというのが割とややこしい。 基本は、こんな感じなのだろうか?

function n_lock($key)
{
    // /tmp があり、nobody が write できる前提とする
    $lockfile = sprintf("/tmp/%s.lock", $key);
    $handle = fopen($lockfile, "a");
    flock($handle, LOCK_EX);
    return $handle;
}

function n_unlock($handle)
{
    fclose($handle);
}

PHP のマニュアルによれば、 fopen のモードに "b" を付けることを強く推奨するようだが、 とりあえず LINUX なので無視する。 先のプログラムの問題があるとすれば、 何かトラブルが発生した場合に、 flock で戻ってこないことはありそうだ。 回避するとなると、こんな感じ?

    int $retry = 0;
    int $retryover = 5;
    while (flock($lockfile_handle, LOCK_EX | LOCK_NB) == FALSE) {
        $retry++;
        if ($retry > $retryover) {
            return 0;
        usleep(100000); // 0.1秒待つ
        }
    }

ロック解除の方は、 ハンドルを受け取って、単に fclose している。 PHP のリファレンスマニュアルの fclose の項には、 単にクローズするとしか書かれていない。 unlock しなくていいのだろうか? という疑問があるかもしれないが、 一般に、fclose した場合には、 同時に unlock も行われることになっている。 システムコールの仕様上、 fclose したが flock したままだ、というのはあり得ないはずなのだ。

ということで、単に fclose してみたのだが、 PHP がもし flock していると fclose に失敗する、 というような奇天烈な実装をしていると、 このコードは期待通り動かないかもしれない。 もっとも、実際に試してみたが、これで特に問題はなさそうだ。

2004年9月7日

Java2 SE 1.4.0 のリファレンスマニュアルで、 String クラスの indexOf メソッドの説明が、次のようになっている。

指定されたインデックス以降で、指定された部分文字列がこの文字列内で最初に出現する位置のインデックスを返します。返される整数は、次の最小値 k になります。
k >= Math.min(fromIndex, str.length()) && this.startsWith(str, k)
このような k の値が存在しない場合、-1 が返されます。

この解説が間違っていたのだ。 このネタ、Cマガジンに使うことにした。

§

Java で、Webサイトの日本語のコンテンツを get する方法。 簡単そうだが一筋縄ではいかない。 問題は文字のエンコーディングである。 最悪の場合、 http header 部分で指定されているエンコーディングと、 ページに META で記述されているエンコーディングが異なっていることがある。 もっとも、その場合は該当サイトの責任というか、 文字化けしても別に差し支えないような気もするのだが。

URLconnection を指定して、コンテンツの文字列を返すメソッドの例。

    private String getContents(URLConnection uc) {
        String str = null;
        BufferedReader br = null;

        try {
            InputStream raw = uc.getInputStream();
            InputStream buffer = new BufferedInputStream(raw);
            Reader r = new InputStreamReader(buffer, getCharsetString());
            br = new BufferedReader(r);

            str = readContents(br);

            br.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return str;
    }

    private String readContents(BufferedReader reader) {
        final int size = 1024 * 100; // 100K
        StringBuffer buf = new StringBuffer();
        char[] charbuf = new char[size];

        try {
            int n;
            while ((n = reader.read(charbuf, 0, size)) > 0) {
                buf.append(charbuf, 0, n);
            }
        } catch (IOException e) {
            System.err.println(e);
        }

        return buf.toString();
    }

メソッド getCharsetString は、例えば Shift_JIS のような文字列を返す。 何も指定しないで、次のように書くことはできる。

            Reader r = new InputStreamReader(buffer);

ただし、これだとデフォルトエンコーディングで文字が変換されてしまうので、 面白くない。 とはいっても、 getCharsetString は一体何を返せばいいか。 http のヘッダ部分でエンコーディングとか文字コードセットが指定されていればいいが、 実際にアクセスすると、 無指定の場合が多いことが分かる。 ということは、 HTML のコンテンツの中から META タグを拾ってきて、 そこから charset を get するのがいい。 実際はこんな感じで書かれている。

<meta HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=Shift_JIS">

しかし、この charset はどうすれば get できるのか。 もちろん、コンテンツを parse すれば何とかなりそうだが、 前述のメソッドは、コンテンツを get する処理なのである。 コンテンツを get するには charset を知る必要があるし、 charset を知るためには、その前にコンテンツを get しなければならない。 いわゆるバケツの穴パターン【謎】なのだ。

そこで、とりあえずエンコーディングなしで、 無変換でデータを書いてしまえ、という発想が出てくるのだが、 「無変換」というエンコーディングを指定する方法が分からないのだ。 何も指定しないとデフォルトになってしまうし。

ということで、 Reader を使うのを諦めて、 InputStream の状態でバイトデータを処理することにした。 バイトデータを受け取ったら、 とりあえずデフォルトエンコーディングで String にしてみる。 この状態で charset が見つかったら、 その charset で再度エンコーディングする。 こんな感じ。

    /**
     * 指定した URLConnection のコンテンツを get する。
     */
    private String getContents(URLConnection uc) {
        String str = null;
        BufferedReader br = null;

        try {
            InputStream raw = uc.getInputStream();
            InputStream buffer = new BufferedInputStream(raw);
            str = readContents(buffer);
            buffer.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return str;
    }

    private String defaultCharSet = "Shift_JIS";

    private String readContents(InputStream buffer) {
        final int size = 1024 * 100; // 100K
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] charbuf = new byte[size];

        try {
            int n;
            while ((n = buffer.read(charbuf, 0, size)) > 0) {
                baos.write(charbuf, 0, n);
            }
        } catch (IOException e) {
            System.err.println(e);
        }

        // とりあえず default encoding で String にしてみる
        String str = "";

        setCharsetString(defaultCharset);

        try {
            str = baos.toString(defaultCharset);
        } catch (UnsupportedEncodingException e3) {
            return "";
        }

        int pos;
        if ((pos = str.indexOf("charset=")) > -1) {
            int posQuote = str.indexOf('"', pos);
            String charsetString = str.substring(pos + 8, posQuote);
            if (! charsetString.equals(defaultCharset)) {
                try {
                    str = baos.toString(charsetString);
                    setCharsetString(charsetString);
                } catch (UnsupportedEncodingException e2) {
                    System.out.println("Unknow charset: " + charsetString);
                }
            }
        }

        try {
            baos.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        return str;
    }

charset の検出はタグとか全部無視して単に charset という文字列を探すという、 いわゆる杜撰な作戦。 parse も何もしない。 まだ文字コードが仮の状態なので、ヘタに parser とか呼び出しても例外発生するだけだし、常識的に META の文字コード指定があれば、 最初の方に出てくるはずなので、だいたいこれでうまくいく。 ただ、META の指定がなくて、 BODY 側に偶然 charset という単語が出てくるとヤバい。

ByteArrayOutputStream の toString を使って文字コード変換しているのがミソ。 ヘンな charset が指定されていると、 JDK 1.4 では処理できないとかいうことでコケるのだが、 だいたいこれでうまくいくようだ。

2004年9月4日

とあるページに初心者向けのJavaの問題が出ていた。 Java である必要は全くないのだが一つ気になったのでメモっておくのだが、 最初の問題は、0から9まで表示するというもの。

    for (int i = 0; i < 10; i++) {
        System.out.print(i + " ");
    }

改行しなくていいのかな、とかいうことに気付く必要はない。

もう一つは、0から20まで、1つおきに表示。 つまり、0, 2, 4, …, 20、と表示したい。

    for (int i = 0; i < 21; i += 2) {
        System.out.print(i + " ");
    }

後の方を見て気付いたのである。 「0から9まで表示する」というのであれば、 次のように書かなければならない。

    for (int i = 0; i <= 9; i++) {
        System.out.print(i + " ");
    }

結果は同じになるのだが、 実は最初の書き方は、 0から9まで 表示するプログラムではなくて、 0から10より小さい数の最大まで 表示するプログラムらしいのである。 先に書いておくと、 1つおきの表示は、次のように書きたい。

    for (int i = 0; i <= 20; i += 2) {
        System.out.print(i + " ");
    }

しかし、一般によく見かけるループ処理は、 最初の書き方のように「<」を使っているものが多い。 仮に、問題の文が何もなくて、 次のようにコードが出てきたとする。

    for (int i = 0; i < 10; i++) {
        System.out.print(i + " ");
    }

これは何なのかというと、 0から10より小さい数の最大まで と解釈するのではなく、 0から始まって10回繰り返す という処理なのだ。 だから「10」という数字がコードに出てくるのである。 という発想からいけば、 0から始まって、10個の偶数を表示する、 というプログラムが書ける。

    for (int i = 0; i < 10; i++) {
        System.out.print(i * 2 + " ");
    }

どうでもいいのでは、という意見もあるかもしれないし、 実際そうかもしれないのだが、 この発想の差が利いてくるのは、 保守する必要がある場合である。 このように書いておけば、

    for (int i = 0; i <= 20; i += 2) {
        System.out.print(i + " ");
    }

「仕様が変わったので36まで表示してくれ」と言われたときに、 修正すると、こうなる。

    for (int i = 0; i <= 36; i += 2) {
        System.out.print(i + " ");
    }

簡単である。 こっちも簡単なのだが、最初に紹介した書き方の場合。

    for (int i = 0; i < 21; i += 2) {
        System.out.print(i + " ");
    }

これに対して36まで表示しろと言われると、 多分、まず殆どの人は、20という数字を探す(そう考えてみれば、 マジックナンバーが直接書かれていること自体がよくないかもしれない)。 20まで表示するプログラムなのだから、 考え方は自然である。 しかし20という数字は、 目で追っても、サーチの機能を使っても、出てこない。 さらに、 この「21」が表示する範囲を与えているのだと理解したとしても、 うっかり次のように書いてしまうリスクがある。

    for (int i = 0; i < 36; i += 2) {
        System.out.print(i + " ");
    }

このようなリスクは、「<=」で書いた場合には殆ど無視できる。 ある数値を与えられて、 それに応じた処理を行う場合は、 その数値がそのまま使われることが望ましいのである。

2004年9月2日

jugem からメールが来ていて、 スタイルシートが無効になってしまう件。 管理メニューで「CSSの保存」を実行しろという。 何も編集しないで「CSSの保存」を実行したら、期待通りの表示になった。

要するにシステムトラブルか、 ソフトウェアをアップデートした時のバグらしい。 バグるのは別に構わないし一定の理解があるつもりだが、 こういう事実を即時公表してくれないのは困る。

まあ無料で無制限コースだし、 あまり高望みするなという説もあるだろう。 @niftyのホームページ、 たった20MBじゃなぁ、とか思っていたが、 Bフレッツコースの会員は100MBまで無料にしてくれるそうだ。 一気に5倍ですか。何年かやってきて、 細かくケチッて何とか10MBでやりくりして、限界、 といった感じだったから、100MBになれば随分違うな。確かに。 もっとも、自分的にはオリジナルの曲データとか置きたいし、 それでも足りないような気はするが、 例のオリジナルの曲データ、1曲2~4MB程度なので、 100MBまで使えるのなら置いてもいいかも。

§

Sun の Looking Glass 3D のセミナーに出るので用賀に行く。 用賀はかなり久しぶりなのだが、 TSUTAYAがあるとか、何となく覚えていた。 明日から半額キャンペーンか。

2004年9月1日

9月カー。

§

何か例によっての頭痛なのだが、 バファリン飲んでもしばらくおさまらなかった。 夜になって何とか回復したのだが、何だったのだろう?

§

例の web contents management system、 急ピッチで開発中。 1次コンテンツから2次コンテンツにコピーする時に、 非公開部分を削除する処理はだいたい動いた。 しかしこういうの、チェックが大変だ。