应该很简单:我有一个 InputStream,我想在其中窥视(不读取)前两个字节,即我希望 InputStream 的“当前位置”在我窥视后仍然为 0。最好和最安全的方法是什么?
答案- 正如我所怀疑的,解决方案是将其包装在提供可标记性的 BufferedInputStream 中。谢谢拉斯穆斯。
应该很简单:我有一个 InputStream,我想在其中窥视(不读取)前两个字节,即我希望 InputStream 的“当前位置”在我窥视后仍然为 0。最好和最安全的方法是什么?
答案- 正如我所怀疑的,解决方案是将其包装在提供可标记性的 BufferedInputStream 中。谢谢拉斯穆斯。
对于一般的 InputStream,我会将其包装在 BufferedInputStream 中并执行以下操作:
BufferedInputStream bis = new BufferedInputStream(inputStream);
bis.mark(2);
int byte1 = bis.read();
int byte2 = bis.read();
bis.reset();
// note: you must continue using the BufferedInputStream instead of the inputStream
您可能会发现 PushbackInputStream 很有用:
http://docs.oracle.com/javase/6/docs/api/java/io/PushbackInputStream.html
使用 BufferedInputStream 时,请确保 inputStream 尚未缓冲,双缓冲会导致一些严重难以发现的错误。此外,您需要以不同的方式处理 Reader,如果 Reader 被缓冲,则转换为 StreamReader 和 Buffering 会导致字节丢失。此外,如果您使用阅读器,请记住您不是在读取字节,而是在默认编码中读取字符(除非设置了显式编码)。一个缓冲输入流的例子,你可能不知道是 URL url;url.openStream();
我没有任何关于此信息的参考资料,它来自调试代码。对我来说发生问题的主要情况是从文件读取到压缩流的代码。如果我没记错的话,一旦您开始通过代码进行调试,Java 源代码中就会有一些注释表明某些事情并不总是正常工作。我不记得使用 BufferedReader 和 BufferedInputStream 的信息来自哪里,但我认为即使是最简单的测试也会立即失败。记住要测试这一点,您需要标记超过缓冲区大小(对于 BufferedReader 与 BufferedInputStream 不同),当正在读取的字节到达缓冲区末尾时会出现问题。请注意,源代码缓冲区大小可能与您在构造函数中设置的缓冲区大小不同。自从我这样做以来已经有一段时间了,所以我对细节的回忆可能有点不对劲。测试是使用 FilterReader/FilterInputStream 完成的,将一个添加到直接流中,并将一个添加到缓冲流中以查看差异。
我在这里找到了 PeekableInputStream 的实现:
http://www.heatonresearch.com/articles/147/page2.html
本文中显示的实现的想法是它在内部保留了一个“偷看”值的数组。当您调用 read 时,首先从 peeked 数组返回值,然后从输入流返回。当您调用 peek 时,会读取这些值并将其存储在“peeked”数组中。
由于示例代码的许可证是 LGPL,它可以附加到这个帖子:
package com.heatonresearch.httprecipes.html;
import java.io.*;
/**
* The Heaton Research Spider Copyright 2007 by Heaton
* Research, Inc.
*
* HTTP Programming Recipes for Java ISBN: 0-9773206-6-9
* http://www.heatonresearch.com/articles/series/16/
*
* PeekableInputStream: This is a special input stream that
* allows the program to peek one or more characters ahead
* in the file.
*
* This class is released under the:
* GNU Lesser General Public License (LGPL)
* http://www.gnu.org/copyleft/lesser.html
*
* @author Jeff Heaton
* @version 1.1
*/
public class PeekableInputStream extends InputStream
{
/**
* The underlying stream.
*/
private InputStream stream;
/**
* Bytes that have been peeked at.
*/
private byte peekBytes[];
/**
* How many bytes have been peeked at.
*/
private int peekLength;
/**
* The constructor accepts an InputStream to setup the
* object.
*
* @param is
* The InputStream to parse.
*/
public PeekableInputStream(InputStream is)
{
this.stream = is;
this.peekBytes = new byte[10];
this.peekLength = 0;
}
/**
* Peek at the next character from the stream.
*
* @return The next character.
* @throws IOException
* If an I/O exception occurs.
*/
public int peek() throws IOException
{
return peek(0);
}
/**
* Peek at a specified depth.
*
* @param depth
* The depth to check.
* @return The character peeked at.
* @throws IOException
* If an I/O exception occurs.
*/
public int peek(int depth) throws IOException
{
// does the size of the peek buffer need to be extended?
if (this.peekBytes.length <= depth)
{
byte temp[] = new byte[depth + 10];
for (int i = 0; i < this.peekBytes.length; i++)
{
temp[i] = this.peekBytes[i];
}
this.peekBytes = temp;
}
// does more data need to be read?
if (depth >= this.peekLength)
{
int offset = this.peekLength;
int length = (depth - this.peekLength) + 1;
int lengthRead = this.stream.read(this.peekBytes, offset, length);
if (lengthRead == -1)
{
return -1;
}
this.peekLength = depth + 1;
}
return this.peekBytes[depth];
}
/*
* Read a single byte from the stream. @throws IOException
* If an I/O exception occurs. @return The character that
* was read from the stream.
*/
@Override
public int read() throws IOException
{
if (this.peekLength == 0)
{
return this.stream.read();
}
int result = this.peekBytes[0];
this.peekLength--;
for (int i = 0; i < this.peekLength; i++)
{
this.peekBytes[i] = this.peekBytes[i + 1];
}
return result;
}
}