DataInputStream

来源:互联网 发布:2017年淘宝客市场规模 编辑:程序博客网 时间:2024/05/29 09:15

DataInputStream 用途

DataInputStream 与 BufferedInputStream 一样,继承自 FilterInputStream ,它提供了对流进行数据读写的功能:支持从流中直接读出 int ,long , double ,utf8 等等功能。下面谈一下它的一些实现细节。

读取 int , long

无论32位还是64位机器/编译器下,int 都是32位,long 都是 64 位,那么从流中读取一个 int ,一个 long 应该会这样实现:

对于 readInt 的实现是没有问题的,但是 readLong 呢?

主要有两个问题:

1.缺陷:循环内部,每个 b 都是一个字节,八位;而 b&0xffff表示取 b 的低 16 位(如果有多于或等于 16 位的话)。因此,这里使用 b & 0xff 就足够了,不需要使用 0xffff。

2.错误:(b&0xffff)<<(leftBits-=8)这句表示将 b 的低八位左移 leftBits-8 位,而编译器并不知这个值应该被保存为 long 型。实际上,此值被编译器默认为保存为 int ,所以当左移后对应的值超出了 int 表示范围则无法得到正确的值,故正确的写法应该是:


读取 utf

DataInputStream 读取 utf8 流(方法 readUTF)是这样实现的:

1.读取流开头的两个字节作为无符号整数,此值表示后面有效的 utf 字节流总个数;

2.继续往后读 utf 字节流,按 utf-8 编码规则反解析得到字符;

这里可能会觉得很奇怪,readUTF 为什么取出开头两字节作为流长度呢?这其实是 DataOutputStream.writeUTF 决定的:

在写入字符串时,先取出每个字符,根据每个字符的编码值大小求得需要几个字节存储该编码值(如 [0 ,127] 需要一个字节,[128 , 0x07FF] 两字节,(0x07FF , ?) 三字节),累计得到需要写入的 utf8 字节流的长度,作为两个字节写入。再边转化每个字符边写入。摘录代码如下:


所以,DataInputStream 与 DataOutputStream 配套使用才能保证正确性。

这里有几个问题:

1).2 字节保存字节流长度是否显得不够 ?

2). 为什么写 utf8 字节流需要保存长度?这样做的动机是什么?

3). 上面 DataOutputStream 的 writeUTF 的实现中,为什么不直接使用 String.getByte 方法获得 utf8 字节流呢?

一一回答之:

1). 2 字节保存字节流长度确实略显不够,但是 writeUTF 的实现中已经限制了写入的 utf8 字节流的长度不大于65535。这是两个字节表示无符号整数的范围。所以程序的逻辑是没有问题的。那么,如果我非要写入长度大于 65535 的 utf8 字节流呢?那就可以连续多次调用 writeUTF ,分多次写入,这样就能写入任意长度的 utf8 字节流。这有一个意想不到的好处:隔离字节流。

2). 实际上,在 1) 中已经回答了这个问题,保存长度有一个作用,就是当往流存储器(如文件)写入多次 utf8 流时这就相当有用了:可以分多次读出这些流。由于每次写入字节流时都在前面附加了字节流的长度,所以每次读出时使用这个长度往后读字节流能够保证正确性。另外,还有一个好处,设想,有一个流存储器无法很快速地得到它里面存放的字节流的总长度:可以通过遍历字节流取得总长度,然而,更快的做法是读出流最前面的两个字节即是!

3). 主要是性能问题没有使用 String.getByte 方法。String.getByte 是一个"综合性"的方法,它可以取出任意编码的字符串的字节流,它使用了很多的类,如 StringCoding,StringEncoder,CharBuffer,ByteBuffer,等等,而且实现的比较复杂。而 writeUTF 中使用最本质最底层的方法,可以预计 String.getByte 方法获得 utf8 字符串的字节流的最底层必然也会有这样的方法,writeUTF 使用的方法必然要比层层包裹下的实现更为高效。


读一行 readLine

该方法是从字节流中读出一行,返回字符串。该方法已经被 deprecated。我们先看它的实现:

遍历读(read)字节流中的每一个字节放入缓存数组 buf,如果 buf 满了,则对 buf 扩充 128 个字节,再拷入原 buf 数组。对当前读入的字节做如下处理:

1). 判断如果是 \n 则直接将读到的字节流构成 String 并返回。

2). 如果是 \r 则读下一个字节,如果是 \n 按 1) 方式返回;否则将读到的这个非 \n 字节压入字节流(PushbackInputStream.unread)以期望下次继续读。

该实现有两个问题:

1).比较低效:一次一次地调用 read 方法:对于读文件而言,每一个 read 就是一个系统调用,十分耗费时间。上面的实现使用了128 作为缓存长度,其实可以每次读128个字节流,判断这个流里面有没有 \r 或者 \n:

如果有,则直接返回 \r\n 或 \r  或 \n 前面的字节流,且移动流读取指针的位置指向 \r\n 或 \r 或 \n 的后面一个字节。如果没有则继续向后读 128 个字节流。如此循环。

2). 实现有错误:没有兼顾字节流的编码,对于非 asscii 编码的字节流无法正确解析。如 UTF8 编码下,一个字符可能占三个字节,而 readLine 的处理方式是一个字节一个字节地将原三个字节读散了,于是这三个字节代表的语义被充分误解了:原来代表一个字符,结果现在强行被代表三个字符,这显然是不正确的!因为这一点,该方法被抛弃了。在实现的源代码里面,作了这样的注释:尽量使用 BufferedReader 的 readLine 方法。此类此方法待时再作分析。


0 0
原创粉丝点击