next up previous contents
Next: プロセスからのストリーム Up: Stream Previous: バイト・アレーへの出力 ----- ByteArrayOutputStream

StreamTokenizer

いま、ストリームの中を、"public class " と続く情報が流れているとします。 引数無しのメソッド read() を繰り返し使うだけでは、この情報は、'p','u','b', 'l','i','c',.... と続く文字の連続としてしか認識されないかもしれません。 バッファに一括して読み込むメソッド read()でも、今度は、バッファの中に紛れて、 "public class " という情報を取り出すことが難しくなるかもしれません。

確かに、いずれの方法でも、ちょっと工夫をすれば、'public' , 'class' といった 「語句」を切り出すことは出来るのですが、入力されたデータの「文字列」や「数値」 で処理が分かれると言うのは、もっとも基本的なプログラムのパターンなわけで、 こうした処理を毎回、自分で書かなければというのは、とても不便です。

例えば、Cのプログラムで、getcharだけで、文字列と数値の入ったデータを処理し なければならないとしたら、どうなるでしょう。おそらく、誰かが scanfに似た関数を 作ることになると思います。

StreamTokenizerは、ストリームの中を流れる情報を、「語句」や「数値」といった 意味の固まりごとに、切り出す働きをします。こうした固まりを、「トークン(token)」 と呼びます。

サンプル

今、次のような内容のファイルがあったとします。

       丸山 93
       植田 97
       姫宮 100
       藤木 95

この時、このファイルの内容を、そのまま表示するJava のプログラムをつくる ことは、簡単です。

ただし、人の名前を String 型の情報として、数字の部分をint 型の情報として 認識したうえで、表示するようなプログラムを作ることは、ちょっと骨が 折れます。(繰り返しになりますが、「出来ない」わけではありません。面倒だし 誤りも起き易いということです。)

StreamTokenizer クラスを使うと、次のように、簡単に書けてしまいます。

import java.io.*;

class readFile extends StreamTokenizer {

  readFile(InputStream in ){
    super(in);
  }

  public static void main (String argv[]){
     try {
       int ret ;
       FileInputStream fin = new FileInputStream(argv[0]);
       readFile rt = new readFile(fin);

       while( (ret = rt.nextToken()) != TT_EOF ){
          switch ( ret ) {
              case TT_WORD   : System.out.println("Text  : " + rt.sval);
                         break ;
              case TT_NUMBER : System.out.println("Number: " + rt.nval);
                         break ;
          }
       }
     } catch (Exception e ){
          System.err.println("Exception :" + e);   
     }
  }
}

このプログラムは、ある意味では、Cでのscanfを使ったプログラムよりも、強力です。 次のようなデータの場合、scanfでは、うまくデータを読めないことが起きるのですが、 このプログラムでは、大丈夫です。

         93 丸山 
       植田 97
       姫宮 100
         95 藤木

先のプログラムの出力はこうなります。

       Number: 93
       Text  : 丸山
       Text  : 植田
       Number: 97
       Text  : 姫宮
       Number: 100
       Number: 95
       Text  : 藤木

nextToken()

StreamTolenizerクラスで、最も中心的な働きをするメソッドが、この nextToken() です。このメソッドは、int を返します。この nextTokenの返値は、プログラムで 必要とされるストリーム中のデータそのものではなく、そのデータが、「文字列」で あるか、「数値」であるかといったデータの種類を表しています。 それでは、データそのものは何処にあるのでしょうか? ここが、そのメソッドの 面白い所なのですが、返値の値に応じて、それぞれ他の所にしまわれています。

先の例で現れた、TT_EOF, TT_WORD, TT_NUMBER 等の、TT_ で始まる変数(定数) は、メソッド nextToken() の返値を表しています。 たとえば、返値 TT_WORD は、データの固まりが、「文字列」であることを示すと ともに、その文字列データが、変数 sval に格納されていることを示しています。 同じように、返値 TT_NUMBER は、データの固まりが、「数値」であることを示し、 かつ、その数値データは、変数 nval に格納されることになります。

nextToken()の返値がTT_EOFの時は、ストリームが終わりにきたということを 表します。

   /** 
     * The End-of-file token. 
     */
    public static final int TT_EOF = -1;

    /** 
     * The End-of-line token. 
     */
    public static final int TT_EOL = '\n';

    /** 
     * The number token.  This value is in nval. 
     */
    public static final int TT_NUMBER = -2;

    /** 
     * The word token.  This value is in sval. 
     */
    public static final int TT_WORD = -3;

区切り文字

StreamTokenizer は、Tokenの始まり、あるいは、終わりをどの様に認識するので しょうか? それは、非常に単純なルールで、「区切り文字」と呼ばれる特別の 文字が現れると、それがtokenの区切り目と見なされます。デフォールトで設定 されている代表的な「区切り文字」は、スペース(空白)、タブ、改行記号など です。これらをまとめて、「空白文字」と呼ことがあります。

これは、word がspace で区切られて文を構成している、欧米の言語や、その影響を 受けたプログラム言語にとっては、ちょうど良いのですが、日本語や中国語のような 言語では、こうした方法では、「語句」の切り出しは出来ないことに注意して下さい。

StreamTokenizer クラスの拡大

StreamTokenizer クラスは、そのままでも、いろいろな使い道があるのですが、 それをうまく拡大すると、便利に使えることになります。

次の例は、Java プログラムの中で、Java のいくつかの予約語が、どのくらいの回数 使われているのかを調べるプログラムです。

import java.io.*;

public class readToken extends StreamTokenizer {

  public static final int TT_IF       = -4;
  public static final int TT_ELSE     = -5;
  public static final int TT_TRY      = -6;
  public static final int TT_CATCH    = -7;
  public static final int TT_WHILE    = -8;
  public static final int TT_SWITCH   = -9;
  public static final int TT_BREAK    = -10;
  public static final int TT_CONTINUE = -11;

  readToken(InputStream in ){
    super(in);
  }

  public int nextToken(){
     int ret = 0 ;

     try{
     switch( ret = super.nextToken() ){
        case TT_EOF :
        case TT_EOL :
        case TT_NUMBER :
           return ttype = ret ;
        case TT_WORD :
           if ( sval.equals("if") )       return ttype =  TT_IF ;      
           if ( sval.equals("else") )     return ttype =  TT_ELSE ;   
           if ( sval.equals("try") )      return ttype =  TT_TRY ;  
           if ( sval.equals("catch") )    return ttype =  TT_CATCH ;
           if ( sval.equals("while") )    return ttype =  TT_WHILE ; 
           if ( sval.equals("switch") )   return ttype =  TT_SWITCH ;
           if ( sval.equals("break") )    return ttype =  TT_BREAK ;
           if ( sval.equals("continue") ) return ttype =  TT_CONTINUE ;
     }
     } catch ( Exception e ){
         System.out.println(e);
     }
     return ttype = ret ;
  }

  public static void main (String argv[]){

     int num_if=0;
     int num_else=0;
     int num_try=0;
     int num_catch=0;
     int num_while=0;
     int num_switch=0;
     int num_break=0;
     int num_continue=0;

     try {
       int ret ;
       FileInputStream fin = new FileInputStream(argv[0]);
       readToken rt = new readToken(fin);

       while( (ret = rt.nextToken()) != TT_EOF ){
          switch ( ret ) {
              case TT_IF       :  num_if++ ; break ;
              case TT_ELSE     :  num_else++ ; break ;
              case TT_TRY      :  num_try++ ; break ;
              case TT_CATCH    :  num_catch++ ; break ;
              case TT_WHILE    :  num_while++ ; break ;
              case TT_SWITCH   :  num_switch++ ; break ;
              case TT_BREAK    :  num_break++ ; break ;
              case TT_CONTINUE :  num_continue++ ; break ;
           // case TT_WORD     :  System.out.println(rt.sval);
          }
       }
       System.out.println("if .........." + num_if) ;
       System.out.println("else ........" + num_else) ;
       System.out.println("try ........." + num_try)  ;
       System.out.println("catch ......." + num_catch)  ;
       System.out.println("while ......." + num_while)  ;
       System.out.println("switch ......" + num_switch) ;
       System.out.println("break ......." + num_break) ;
       System.out.println("continue ...." + num_continue) ;

     } catch (Exception e ){
          System.err.println("Exception :" + e);   
     }
  }
}

ここでは、StreamTokenizer クラスの継承が、メソッド nextToken()を書き換えている ことに注目してください。ここが、このクラス拡大のポイントです。

次は、このプログラムの実行結果です。

          75 ews1 maru> java readToken readToken.java
          if ..........11
          else ........3
          try .........5
          catch .......5
          while .......4
          switch ......5
          break .......11
          continue ....3



maruyama@wakhok.ac.jp