7

これまで Java IO API を身近に体験したことがなく、今は本当にイライラしています。それがどれほど奇妙で複雑で、単純なタスクを実行するのがどれほど難しいか、信じがたいです。

私の仕事: 2 つの位置 (開始バイト、終了バイト)pos1pos2. これらの 2 バイト間の行 (開始バイトを含み、終了バイトは含まない) を読み取り、それらを UTF8 String オブジェクトとして使用する必要があります。

たとえば、ほとんどのスクリプト言語では、次のような非常に単純な 1-2-3 ライナーになります (Ruby では、Python、Perl などでも本質的に同じになります)。

f = File.open("file.txt").seek(pos1)
while f.pos < pos2 {
  s = f.readline
  # do something with "s" here
}

Java IO API ではすぐに地獄に落ちます ;) 実際、\n通常のローカル ファイルから行 ( で終わる)を読み取る方法は 2 つあります。

  • および_ getFilePointer()_ seek(long pos)_ _ _ read*()_ read()_ >かなり遅い)。
  • BufferedReaderには優れreadLine()たメソッドがあり、 でシークすることもできskip(long n)ますが、ファイル内の現在の位置は言うまでもなく、既に読み取られた偶数バイトを特定する方法はありません。

私は次のようなものを使用しようとしました:

    FileInputStream fis = new FileInputStream(fileName);
    FileChannel fc = fis.getChannel();
    BufferedReader br = new BufferedReader(
            new InputStreamReader(
                    fis,
                    CHARSET_UTF8
            )
    );

...そして、fc.position()現在のファイル読み取り位置を取得fc.position(newPosition)して設定するために使用しますが、私の場合はうまくいかないようです:BufferedReaderによって行われたバッファの事前充填の位置を返すように見えます-これらカウンターは 16K 単位で切り上げられているようです。

私は本当にそれをすべて自分で実装する必要がありますか?

  • ファイル内の位置を取得/設定できるようにする
  • バッファファイルの読み込み操作
  • UTF8 文字列の読み取りを許可します (または、少なくとも「次まですべてを読み取る」などの操作を許可します\n)

すべてを自分で実装するよりも簡単な方法はありますか? 私は何かを監督していますか?

4

7 に答える 7

6

randomaccessfilesを使用してutf-8を読み取るためにこのコードを書きました

//File: CyclicBuffer.java
public class CyclicBuffer {
private static final int size = 3;
private FileChannel channel;
private ByteBuffer buffer = ByteBuffer.allocate(size);

public CyclicBuffer(FileChannel channel) {
    this.channel = channel;
}

private int read() throws IOException {
    return channel.read(buffer);
}

/**
 * Returns the byte read
 *
 * @return byte read -1 - end of file reached
 * @throws IOException
 */
public byte get() throws IOException {
    if (buffer.hasRemaining()) {
        return buffer.get();
    } else {
        buffer.clear();
        int eof = read();
        if (eof == -1) {
            return (byte) eof;
        }
        buffer.flip();
        return buffer.get();
    }
}
}
//File: UTFRandomFileLineReader.java


public class UTFRandomFileLineReader {
private final Charset charset = Charset.forName("utf-8");
private CyclicBuffer buffer;
private ByteBuffer temp = ByteBuffer.allocate(4096);
private boolean eof = false;

public UTFRandomFileLineReader(FileChannel channel) {
    this.buffer = new CyclicBuffer(channel);
}

public String readLine() throws IOException {
    if (eof) {
        return null;
    }
    byte x = 0;
    temp.clear();

    while ((byte) -1 != (x = (buffer.get())) &amp;&amp; x != '\n') {
        if (temp.position() == temp.capacity()) {
            temp = addCapacity(temp);
        }
        temp.put(x);
    }
    if (x == -1) {
        eof = true;
    }
    temp.flip();
    if (temp.hasRemaining()) {
        return charset.decode(temp).toString();
    } else {
        return null;
    }
}

private ByteBuffer addCapacity(ByteBuffer temp) {
    ByteBuffer t = ByteBuffer.allocate(temp.capacity() + 1024);
    temp.flip();
    t.put(temp);
    return t;
}

public static void main(String[] args) throws IOException {
    RandomAccessFile file = new RandomAccessFile("/Users/sachins/utf8.txt",
            "r");
    UTFRandomFileLineReader reader = new UTFRandomFileLineReader(file
            .getChannel());
    int i = 1;
    while (true) {
        String s = reader.readLine();
        if (s == null)
            break;
        System.out.println("\n line  " + i++);
        s = s + "\n";
        for (byte b : s.getBytes(Charset.forName("utf-8"))) {
            System.out.printf("%x", b);
        }
        System.out.printf("\n");

    }
}
}
于 2011-04-14T09:26:26.090 に答える
6
import org.apache.commons.io.input.BoundedInputStream

FileInputStream file = new FileInputStream(filename);
file.skip(pos1);
BufferedReader br = new BufferedReader(
   new InputStreamReader(new BoundedInputStream(file,pos2-pos1))
);

気にしなければpos2、Apache Commons IO は必要ありません。

于 2010-11-29T15:57:46.147 に答える
1

@Ken Bloom の場合 Java 7 バージョンを簡単に説明します。注: これが最も効率的な方法だとは思いません。私はまだ NIO.2 について頭を悩ませています。Oracle はここでチュートリアルを開始しています。

また、これは Java 7 の新しい ARM 構文 (ファイルベースのリソースの例外処理を処理する) を使用していないことに注意してください。私が持っている最新の openJDK ビルドでは機能しませんでした。しかし、人々が構文を見たい場合は、私に知らせてください。

/* 
 * Paths uses the default file system, note no exception thrown at this stage if 
 * file is missing
 */
Path file = Paths.get("C:/Projects/timesheet.txt");
ByteBuffer readBuffer = ByteBuffer.allocate(readBufferSize);
FileChannel fc = null;
try
{
    /*
     * newByteChannel is a SeekableByteChannel - this is the fun new construct that 
     * supports asynch file based I/O, e.g. If you declared an AsynchronousFileChannel 
     * you could read and write to that channel simultaneously with multiple threads.
     */
    fc = (FileChannel)file.newByteChannel(StandardOpenOption.READ);
    fc.position(startPosition);
    while (fc.read(readBuffer) != -1)
    {
        readBuffer.rewind();
        System.out.println(Charset.forName(encoding).decode(readBuffer));
        readBuffer.flip();
    }
}
于 2010-11-29T16:45:12.690 に答える
0

この混乱は、UTF-8 エンコーディングと 2 バイト文字の可能性によって引き起こされていると思います。

UTF8 では、1 文字のバイト数は指定されていません。あなたの投稿から、あなたはシングルバイト文字を使用していると思います。たとえば、412 バイトは 411 文字を意味します。ただし、文字列が 2 バイト文字を使用している場合は、206 文字になります。

元の java.io パッケージは、このマルチバイトの混乱をうまく処理できませんでした。そのため、特に文字列を処理するクラスを追加しました。このパッケージには、2 つの異なるタイプのファイル ハンドラーが混在しています (命名法が整理されるまで、混乱する可能性があります)。ストリームクラスは、変換なしで直接データ I/O を提供します。リーダークラスは、マルチバイト文字を完全にサポートする文字列にファイルを変換します。それは問題の一部を明確にするのに役立つかもしれません。

UTF-8 文字を使用していると述べているので、リーダー クラスが必要です。この場合、FileReader をお勧めします。FileReader の skip() メソッドを使用すると、X 文字を渡してからテキストの読み取りを開始できます。または、一度にすべてのテキストを取得できるので、オーバーロードされた read() メソッドを好みます。

「バイト」が個々の文字であると想定する場合は、次のようにしてみてください。

FileReader fr = new FileReader( new File("x.txt") );
char[] buffer = new char[ pos2 - pos ];
fr.read( buffer, pos, buffer.length );
...
于 2010-11-29T15:49:53.683 に答える
0

ここでのパーティーに遅れましたが、自分のプロジェクトでこの問題に遭遇しました。

Javadocs と Stack Overflow を何度も調べた結果、簡単な解決策を見つけたと思います。

ここで呼び出している RandomAccessFile の適切な場所を探した後raFile、次の操作を行います。

FileDescriptor fd = raFile.getFD();
FileReader     fr = new FileReader(fd);
BufferedReader br = new BufferedReader(fr);

br.readLine()そうすれば、心ゆくまで呼び出すことができるはずです。これは、 を呼び出すよりもはるかに高速ですraFile.readLine()

私が確信していないことの 1 つは、UTF8 文字列が正しく処理されるかどうかです。

于 2014-07-13T15:01:14.500 に答える
0

a で開始し、またはをRandomAccessFile使用して ~ の間のバイト配列を取得します。読み取ったデータを という名前の変数に保存したとしましょう。readreadFullypos1pos2rawBytes

次に、BufferedReader使用して作成します

new BufferedReader(new InputStreamReader(new ByteArrayInputStream(rawBytes)))

readLineその後、を呼び出すことができますBufferedReader

BufferedReader警告: これは、すべてをメモリにプリロードするため、適切な場所自体をシークできる場合よりもおそらく多くのメモリを使用します。

于 2010-11-29T15:43:02.280 に答える
-1

Java IO API は非常に柔軟です。残念ながら、柔軟性のために冗長になる場合があります。ここでの主なアイデアは、ラッパー パターンを実装する多くのストリーム、ライター、およびリーダーがあるということです。たとえば、BufferedInputStream は他のすべての InputStream をラップします。出力ストリームについても同様です。

ストリームとリーダー/ライターの違いは、ストリームはバイトを処理するのに対し、リーダー/ライターは文字を処理することです。

幸いなことに、一部のストリーム、ライター、およびリーダーには、コーディングを簡素化する便利なコンストラクターがあります。ファイルを読みたい場合は、ただ言う必要があります

    InputStream in = new FileInputStream("/usr/home/me/myfile.txt");
    if (in.markSupported()) {
        in.skip(1024);
        in.read();
    }

あなたが恐れているほど複雑ではありません。

チャネルは別のものです。いわゆる「新しいIO」またはnioの一部です。新しい IO はブロックされません。これが主な利点です。インターネットで「nio Java tutorial」を検索して、それについて読むことができます。ただし、通常の IO よりも複雑であり、ほとんどのアプリケーションでは必要ありません。

于 2010-11-29T15:30:26.370 に答える