一个允许你反悔的hook
Java I/O系统是一个典型的Decorator模式的实现,它以InputStream/OutputStream为基本核心,通过继承关系,不断为该核心添加新的功能,如文件流、缓冲、加解密等。对I/O系统设计模式感兴趣的话,可以参考developerWorks上的一篇文章:从Java类库看设计模式。Java I/O默认是不缓冲流的,所谓“缓冲”就是先把从流中得到的一块字节序列暂存在一个被称为buffer的内部字节数组里,然后你可以一下子取到这一整块的字节数据,没有缓冲的流只能一个字节一个字节读,效率孰高孰低一目了然。有两个特殊的输入流实现了缓冲功能,一个是我们常用的BufferedInputStream,像读文件我们常用
while ((b = in.read()) != - 1 )
{
}
in.close();
这是我们几乎不用查什么 JDK文档 就能信手拈来的代码段,写的时候也应该思考一下套一个 BufferedInputStream 的意义何在。另一个就是我们不怎么看到的 PushbackInputStream (其对应的字符流模式为 PushbackReader )。
在通常状态下, “ 流 ” 意味着 “ 一次性 ” ,就是说你进行了一次操作后它的状态就变了,譬如读,无论是文件还是 socket ,你读的过程中一个潜在的 “ 读指针 ” 一样的东东就在移动,你无法在读以后再重新定位(当然 RandomAccessFile 是另一种情况),如果你以前奇怪为什么数据库操作中 ResultSet 里 get 某个字段以后就不能再第二次 get 它了,这里或许是个解释。但好在 PushbackInputStream 给了我们第二次读的机会。我们先来区别一下 “ 监听 ” 和 “ 截获 ” 的概念, “ 监听 ” 就是把得到的消息 copy 一份,原始消息并不作任何改变地传递到目的地;而 “ 截获 ” 则是先把消息 “ 扣押 ” 下来,不让其自动转给目标,而是先进行一些处理以后在转发给目标(如果是网络安全专业的背景知识,大概知道 “ 监听 ” 是对 “ 机密性 ” 的攻击,而 “ 截获 ” 不仅是对 “ 机密性 ” 还是对 “ 完整性 ” 的攻击)。有的朋友大概对 hook 这个名词有些了解,它是一种 Windows 的一种消息处理机制,似乎就是一种消息截获手段,但我对 Windows 编程一窍不通 //shy ;此外,如果你熟悉 Servlet 的话,也能找到像 Filter 这样的处理机制,在对每个 HTTP 请求 / 应答进行转发之前,先在里头耍一点花招,确定哪些予以转发,哪些屏蔽掉,这也算是 “ 截获 ” 吧。通过上面的介绍,我们不妨把 PushbackInputStream 看成是对输入流的一种 “ 截获 ” 手段,其中最重要的方法是 unread :
public void unread( byte []b) throws IOException
public void unread( byte []b, int off, int len) throws IOException
PushbackInputStream 是对二进制流的处理,字符流下相对应的就是 PushbackReader 。
有什么用?
学过编译的话就容易理解了,比如从左向右扫描字符流“for(int i=0;i<10;i++)”,扫描到“for”是不是就可以说是个关键字了呢?不行,说不定后面是“for1”,那就是个变量而不是关键字了,知道看到“(”才恍然大悟,哦,我可以安全地说“看到for关键字”了,但“(”还得归还给输入流,因为需要后面继续扫描。在上下文相关语言里,就更需要这种补偿机制。又如,在解析HTML文档的时候,我需要根据它的“meta”标签的“charset”属性来决定使用哪种字符集进行解析,但HTML可不是“charset”而是“<html>”开头的哦!所以需要通过PushbackInputStream缓冲前面一段内容,等取到字符集名称后在把读到的流全部归还,再用指定的字符集进行解析。