Java InputStreamReader缓冲问题

Java InputStreamReader缓冲问题,java,buffer,character-encoding,decode,inputstreamreader,Java,Buffer,Character Encoding,Decode,Inputstreamreader,不幸的是,我从一个有两种字符编码的文件中读取数据 有一个标题和一个正文。标头始终为ASCII格式,并定义正文编码的字符集 头不是固定长度的,必须通过解析器来确定其内容/长度 该文件也可能相当大,因此我需要避免将整个内容带入内存 所以我从一个输入流开始。我首先使用带有ASCII的InputStreamReader将其包装,然后解码标题并提取正文的字符集。一切都好 然后我用正确的字符集创建一个新的InputStreamReader,将它放到同一个InputStream上,然后开始尝试读取正文 不幸的

不幸的是,我从一个有两种字符编码的文件中读取数据

有一个标题和一个正文。标头始终为ASCII格式,并定义正文编码的字符集

头不是固定长度的,必须通过解析器来确定其内容/长度

该文件也可能相当大,因此我需要避免将整个内容带入内存

所以我从一个输入流开始。我首先使用带有ASCII的InputStreamReader将其包装,然后解码标题并提取正文的字符集。一切都好

然后我用正确的字符集创建一个新的InputStreamReader,将它放到同一个InputStream上,然后开始尝试读取正文

不幸的是,javadoc证实了这一点,InputStreamReader可能会选择提前阅读以提高效率。因此,标题的读数会影响身体的部分/全部

有人对解决这个问题有什么建议吗?手动创建一个CharsetDecoder并一次输入一个字节,这是一个好主意吗(可能包装在自定义阅读器实现中?)

提前谢谢

编辑:我的最终解决方案是编写一个没有缓冲的InputStreamReader,以确保我可以在不咀嚼正文部分的情况下解析标题。虽然这不是非常有效,但我用BufferedInputStream包装原始InputStream,这样就不会有问题了

// 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();
    }
}
//只消耗所需字节数的InputStreamReader
//它不做任何预读。
公共类InputStreamReaderUnbuffered扩展读取器
{
专用最终字符集解码器字符集解码器;
私有最终输入流InputStream;
私有最终ByteBuffer ByteBuffer=ByteBuffer.allocate(1);
公共InputStreamReaderUnbuffered(InputStream InputStream,Charset Charset)
{
this.inputStream=inputStream;
charsetDecoder=charset.newDecoder();
}
@凌驾
public int read()引发IOException
{
布尔值=假;
while(true)
{
int b=inputStream.read();
如果(b==-1)
{
如果(阅读中)
抛出新IOException(“意外的流结束,字节被截断”);
返回-1;
}
byteBuffer.clear();
byteBuffer.put((字节)b);
byteBuffer.flip();
CharBuffer CharBuffer=charsetDecoder.decode(byteBuffer);
//虽然这在理论上是可能的,但这将违反无缓冲性质
//所以我们抛出一个异常
if(charBuffer.length()>1)
抛出新IOException(“从一个字节解码多个字符!”);
if(charBuffer.length()==1)
返回charBuffer.get();
中间阅读=正确;
}
}
公共整数读取(char[]cbuf,int off,int len)引发IOException
{
对于(int i=0;i
为什么不使用2
InputStream
s?一个用于读取标题,另一个用于读取正文


第二个
InputStream
应该
跳过
头字节。

我的第一个想法是关闭流并重新打开它,在将流提供给新的
InputStream阅读器之前,使用
InputStream#skip
跳过头


如果您确实不想重新打开文件,您可以使用获取多个文件流,尽管您可能必须使用在文件中具有多个位置(因为您不能假设您可以使用
reset
重置位置,因此可能不受支持).

我建议使用新的
InputStreamReader
从头开始重新读取流。可能假设支持
InputStream.mark

以下是伪代码

  • 使用
    InputStream
    ,但不要包装
    阅读器
    围绕它
  • 读取包含标头和 储存在
    ByteArrayOutputStream
  • 创建
    ByteArrayInputStream
    from
    ByteArrayOutputStream
    和解码 标题,此时间换行
    ByteArrayInputStream
    使用ASCII字符集进入
    读取器
  • 计算非ascii码的长度 输入,并读取该字节数 进入另一个
    ByteArrayOutputStream
  • 通过tearrayinputstream创建另一个
    
    从第二
    
    ByteArrayOutputStream
    并将其包装 带
    读取器
    和 标题
  • 这更容易:

    正如您所说,您的标题始终是ASCII格式的。因此,直接从InputStream中读取头,当您使用完头后,使用正确的编码创建读取器并从中读取

    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
        }
    }
    

    如果包装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));
    

    也许我错了,但从那一刻起我就认为文件在同一时间只能有一种编码类型。@Roman:你可以对文件做任何你想做的事情;它们只是字节序列。因此,您可以写出一组表示为ASCII的字节,然后再写出一组表示为UTF-16的字节,甚至更多表示为UTF-32的字节。我并不是说这是一个好主意,尽管OP的用例肯定是合理的(毕竟,你必须有某种方式来指示文件使用什么编码)。@Mike Q-好主意InputStreamReaderUnbuffered。我建议使用sepa