【发布时间】:2011-02-07 13:24:54
【问题描述】:
不幸的是,我正在从具有两种字符编码类型的文件中读取数据。
有一个标题和一个正文。标头始终为 ASCII 格式,并定义了正文编码的字符集。
标头不是固定长度,必须通过解析器运行以确定其内容/长度。
文件也可能很大,所以我需要避免将整个内容都带入内存。
所以我从单个 InputStream 开始。我最初用带有 ASCII 的 InputStreamReader 包装它,然后解码标题并提取正文的字符集。都很好。
然后我使用正确的字符集创建一个新的 InputStreamReader,将它放在同一个 InputStream 上并开始尝试读取正文。
不幸的是,javadoc 证实了这一点,即 InputStreamReader 可能会出于效率目的选择预读。所以标题的阅读会咀嚼部分/全部正文。
有人对解决这个问题有什么建议吗?会手动创建 CharsetDecoder 并一次输入一个字节,但这是个好主意(可能包含在自定义 Reader 实现中?)
提前致谢。
编辑:我的最终解决方案是编写一个没有缓冲的 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();
}
}
【问题讨论】:
-
也许我错了,但从那一刻起,我认为该文件只能同时具有一种编码类型。
-
@Roman:你可以对文件做任何你想做的事情;它们只是字节序列。所以你可以写出一堆被解释为 ASCII 的字节,然后写出更多被解释为 UTF-16 的字节,甚至更多的字节被解释为 UTF-32。我并不是说这是一个好主意,尽管 OP 的用例肯定是合理的(毕竟,你必须有 一些 方法来指示文件使用什么编码)。
-
@Mike Q - InputStreamReaderUnbuffered 的好主意。我建议一个单独的答案 - 它值得关注:)
-
关于 InputStreamReaderUnbuffered 的解决方案:如果字节缓冲区大小为 1,您如何消耗作为单个字符一部分的 2 个字节?
标签: java buffer character-encoding decode inputstreamreader