11

残念ながら、2 種類の文字エンコーディングを持つファイルからデータを読み取っています。

ヘッダーとボディがあります。ヘッダーは常に ASCII であり、本文がエンコードされる文字セットを定義します。

ヘッダーは固定長ではなく、その内容/長さを判別するためにパーサーを実行する必要があります。

ファイルも非常に大きくなる可能性があるため、コンテンツ全体をメモリに取り込まないようにする必要があります。

そこで、単一の InputStream から始めました。最初に ASCII の InputStreamReader でラップし、ヘッダーをデコードして本文の文字セットを抽出します。すべて良い。

次に、正しい文字セットを使用して新しい InputStreamReader を作成し、それを同じ InputStream にドロップして、本文の読み取りを試みます。

残念ながら、javadoc はこれを確認しており、InputStreamReader が効率的な目的で先読みを選択する可能性があります。したがって、ヘッダーの読み取りは、本文の一部/すべてを噛み砕きます。

この問題を回避するための提案はありますか? 手動で CharsetDecoder を作成し、一度に 1 バイトずつフィードするのは良い考えでしょうか (おそらくカスタムの Reader 実装にラップされていますか?)

前もって感謝します。

編集:私の最終的な解決策は、バッファリングのない InputStreamReader を記述して、本文の一部を噛むことなくヘッダーを解析できるようにすることでした。これは非常に効率的ではありませんが、生の InputStream を BufferedInputStream でラップするので、問題にはなりません。

// An InputStreamReader that only consumes as many bytes as is necessary
// It does not do any read-ahead.
public class InputStreamReaderUnbuffered extends Reader
{
    private final CharsetDecoder charsetDecoder;
    private final InputStream inputStream;
    private final ByteBuffer byteBuffer = ByteBuffer.allocate( 1 );

    public InputStreamReaderUnbuffered( InputStream inputStream, Charset charset )
    {
        this.inputStream = inputStream;
        charsetDecoder = charset.newDecoder();
    }

    @Override
    public int read() throws IOException
    {
        boolean middleOfReading = false;

        while ( true )
        {
            int b = inputStream.read();

            if ( b == -1 )
            {
                if ( middleOfReading )
                    throw new IOException( "Unexpected end of stream, byte truncated" );

                return -1;
            }

            byteBuffer.clear();
            byteBuffer.put( (byte)b );
            byteBuffer.flip();

            CharBuffer charBuffer = charsetDecoder.decode( byteBuffer );

            // although this is theoretically possible this would violate the unbuffered nature
            // of this class so we throw an exception
            if ( charBuffer.length() > 1 )
                throw new IOException( "Decoded multiple characters from one byte!" );

            if ( charBuffer.length() == 1 )
                return charBuffer.get();

            middleOfReading = true;
        }
    }

    public int read( char[] cbuf, int off, int len ) throws IOException
    {
        for ( int i = 0; i < len; i++ )
        {
            int ch = read();

            if ( ch == -1 )
                return i == 0 ? -1 : i;

            cbuf[ i ] = (char)ch;
        }

        return len;
    }

    public void close() throws IOException
    {
        inputStream.close();
    }
}
4

6 に答える 6

3

ここに疑似コードがあります。

  1. を使用InputStreamしますが、a をラップしない Readerでください。
  2. ヘッダーを含むバイトを読み取り、それらを に格納します ByteArrayOutputStream
  3. ヘッダーから作成ByteArrayInputStreamByteArrayOutputStreamてデコードします。今回は ASCII 文字セットでラップByteArrayInputStream します。Reader
  4. 非 ASCII 入力の長さを計算し、そのバイト数を別の に読み込みますByteArrayOutputStream
  5. ByteArrayInputStream 2番目から 別のものを作成し、ヘッダーから文字セットを使用してByteArrayOutputStreamラップします。Reader
于 2010-04-13T17:06:31.813 に答える
3

なぜ 2 InputStreams を使わないのですか?1 つはヘッダーの読み取り用、もう 1 つは本文の読み取り用です。

2 番目InputStreamskipヘッダー バイトです。

于 2010-04-13T17:02:55.880 に答える
1

InputStream をラップし、すべての読み取りを一度に 1 バイトに制限すると、InputStreamReader 内のバッファリングが無効になるようです。

この方法では、InputStreamReader ロジックを書き直す必要はありません。

public class OneByteReadInputStream extends InputStream
{
    private final InputStream inputStream;

    public OneByteReadInputStream(InputStream inputStream)
    {
        this.inputStream = inputStream;
    }

    @Override
    public int read() throws IOException
    {
        return inputStream.read();
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException
    {
        return super.read(b, off, 1);
    }
}

構築するには:

new InputStreamReader(new OneByteReadInputStream(inputStream));
于 2015-02-25T18:23:52.617 に答える
1

私が最初に考えたのは、ストリームを閉じて再度開きInputStream#skip、ストリームを new に渡す前にヘッダーをスキップするために使用することInputStreamReaderです。

本当にファイルを再度開きたくない場合は、ファイル記述子を使用してファイルに複数のストリームを取得できますが、ファイル内に複数の位置を持つためにチャネルを使用する必要がある場合があります (想定できないため)で位置をリセットできますがreset、サポートされていない場合があります)。

于 2010-04-13T17:03:20.553 に答える
1

新しい で最初からストリームを読み直すことをお勧めしますInputStreamReaderInputStream.markおそらくそれがサポートされていると仮定します。

于 2010-04-13T17:06:02.423 に答える
1

さらに簡単です:

あなたが言ったように、ヘッダーは常にASCIIです。そのため、ヘッダーを InputStream から直接読み取り、それが完了したら、正しいエンコーディングで Reader を作成し、そこから読み取ります

private Reader reader;
private InputStream stream;

public void read() {
    int c = 0;
    while ((c = stream.read()) != -1) {
        // Read encoding
        if ( headerFullyRead ) {
            reader = new InputStreamReader( stream, encoding );
            break;
        }
    }
    while ((c = reader.read()) != -1) {
        // Handle rest of file
    }
}
于 2010-06-29T08:43:27.330 に答える