HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

BufferedReaderとLineNumberReader

BufferedReader#readLine() で実装されている、入力を行に分割するアルゴリズムを再利用する手立てはないものか、と調べていたら、LineNumberReader#read() が使えそうということに。


Read a single character. Line terminators are compressed into single newline ('\n') characters.
JavaDocを見て初めて知ったが、LineNumberReaderってBufferedReaderを継承しているのだな。
ということで、ついでなので、read(char) や read(char, int, int) の動作も調べてみる。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.StringReader;

public class Main {

    public static void main(String[] args) throws IOException {
        String[] strs = {
            "a" + "\n"   + "a" + "\n\n"     + "a",
            "b" + "\r"   + "b" + "\r\r"     + "b",
            "c" + "\r\n" + "c" + "\r\n\r\n" + "c",
        };
        for (String s : strs) {
            test(s);
        }
    }

    private static void test(String s) throws IOException {
        System.out.println("=== \"" + s.replace("\r", "\\r").replace("\n", "\\n") + "\" ===");

        System.out.printf("    %-16s%n", "BufferedReader");
        printNumOfLines(new BufferedReader(new StringReader(s)));
        testRead1(new BufferedReader(new StringReader(s)));
        testRead2(new BufferedReader(new StringReader(s)));
        testRead3(new BufferedReader(new StringReader(s)));

        System.out.printf("    %-16s%n", "LineNumberReader");
        printNumOfLines(new LineNumberReader(new StringReader(s)));
        testRead1(new LineNumberReader(new StringReader(s)));
        testRead2(new LineNumberReader(new StringReader(s)));
        testRead3(new LineNumberReader(new StringReader(s)));
    }

    private static void printNumOfLines(BufferedReader reader) throws IOException {
        int nlines = 0;
        while (reader.readLine() != null) {
            ++nlines;
        }
        reader.close();

        System.out.printf("        %-24s: %2d lines%n", "# of lines", nlines);
    }

    private static void testRead1(Reader reader) throws IOException {
        System.out.printf("        %-24s: ", "read()");
        int ch;
        while ((ch = reader.read()) >= 0) {
            System.out.printf(" %02x", ch);
        }
        System.out.println();
        reader.close();
    }

    private static void testRead2(Reader reader) throws IOException {
        System.out.printf("        %-24s: ", "read(char[])");
        char[] buf = new char[1024];
        int len;
        while ((len = reader.read(buf)) >= 0) {
            for (int i = 0; i < len; ++i) {
                System.out.printf(" %02x", (int) buf[i]);
            }
        }
        System.out.println();
        reader.close();
    }

    private static void testRead3(Reader reader) throws IOException {
        System.out.printf("        %-24s: ", "read(char[],int,int)");
        char[] buf = new char[1024];
        int len;
        while ((len = reader.read(buf, 0, buf.length)) >= 0) {
            for (int i = 0; i < len; ++i) {
                System.out.printf(" %02x", (int) buf[i]);
            }
        }
        System.out.println();
        reader.close();
    }

}

入力として与えるのは、LF("\n")、CR("\r")、CR+LF("\r\n")のそれぞれを行区切りとした4行分のテキスト。
JDK 5.0 Update 22 での実行結果。

=== "a\na\n\na" ===
    BufferedReader  
        # of lines              :  4 lines
        read()                  :  61 0a 61 0a 0a 61
        read(char[])            :  61 0a 61 0a 0a 61
        read(char[],int,int)    :  61 0a 61 0a 0a 61
    LineNumberReader
        # of lines              :  4 lines
        read()                  :  61 0a 61 0a 0a 61
        read(char[])            :  61 0a 61 0a 0a 61
        read(char[],int,int)    :  61 0a 61 0a 0a 61
=== "b\rb\r\rb" ===
    BufferedReader  
        # of lines              :  4 lines
        read()                  :  62 0d 62 0d 0d 62
        read(char[])            :  62 0d 62 0d 0d 62
        read(char[],int,int)    :  62 0d 62 0d 0d 62
    LineNumberReader
        # of lines              :  4 lines
        read()                  :  62 0a 62 0a 0a 62
        read(char[])            :  62 0d 62 0d 0d 62
        read(char[],int,int)    :  62 0d 62 0d 0d 62
=== "c\r\nc\r\n\r\nc" ===
    BufferedReader  
        # of lines              :  4 lines
        read()                  :  63 0d 0a 63 0d 0a 0d 0a 63
        read(char[])            :  63 0d 0a 63 0d 0a 0d 0a 63
        read(char[],int,int)    :  63 0d 0a 63 0d 0a 0d 0a 63
    LineNumberReader
        # of lines              :  4 lines
        read()                  :  63 0a 63 0a 0a 63
        read(char[])            :  63 0d 0a 63 0d 0a 0d 0a 63
        read(char[],int,int)    :  63 0d 0a 63 0d 0a 0d 0a 63

1つ目の入力では当然のことながら違いが見えないが、2つ目と3つ目は、read() については LineNumberReader では改行記号がすべてLF("\n")に統一されていることが見て取れる。しかし、read(char) や read(char, int, int) では BufferedReader での動作をそのまま受け継いでおり、改行記号もそのまま。なんだこの中途半端っぷりは‥
まぁ、とりあえず LineNumberReader#read() を使用すれば、LF("\n") だけを考慮して行末であることを判定できる(実はgetLineNumber()を使っても判定できないことはないが、行番号を別途覚えておく必要がある)ということが分かったのでよしとする。