引言
使用java io
包时,InputStream
类中的有好几个read()
方法,并且返回值也都是int类型,这样就使得初学者很容易搞混,其实虽然返回值都是int类型
,但所表示的意义确是不一样的
有参 read(byte b[], int off, int len)
先看有参的read
方法。
有参的read
比较好理解,将读取到的数据写入字节数组(从字节数组的指定位置开始写入)
- 三个入参
-
b[]
存储读取到的数据的字节数组 -
off
从目标数组b[]
的off
位开始写入,一般都是从0开始 -
len
要读取的字节码长度,一般会是存储数组b[]
的长度
-
这里的int
类型的返回值是实际读取的字节数,如果检测到无数据可读时会返回 -1
无参 read()
/**
* 从输入流读取下一个字节的数据。返回值为`int类型`,值范围在`0-255`之间。如果由于到达流
* 的末尾而没有可用的字节,则返回`-1`。此方法将一直阻塞,直到输入数据可用、检测到流的结
* 尾或引发异常为止
*/
public abstract int read() throws IOException;
从输入流读取下一个字节的数据。返回值为int类型
,值范围在0-255
之间。如果由于到达流的末尾而没有可用的字节,则返回-1
。此方法将一直阻塞,直到输入数据可用、检测到流的结尾或引发异常为止
我们再来看一下这个方法的具体实现
public int read() throws IOException {
if (eof) {
return -1;
}
temp = new byte[1];
int n = read(temp, 0, 1);
if (n <= 0) {
return -1;
}
return temp[0] & 0xff;
}
到这里就很奇怪了,这个方法是返回下一个字节的数据,可是为什么要返回一个int类型
,而不直接返回 byte
类型?并且返回int
类型时还有一个& 0xff
操作,为什么还要执行这个与操作呢?下面就让我们好好分析一下,为何要返回int
类型,而不直接返回byte
类型,以及为何会先执行一个& 0xff
操作
为何要返回int类型
在读取字节时,我们肯定需要一个标识来表示已经读到了字节流末尾,一般会返回-1来标识,但是如果是返回byte类型,就无法标识是否到了文件末尾。所以单凭这点,这里就不能返回byte类
为何要执行与操作& 0xff
再返回int类型
上面解释了为何要返回int
类型,可是貌似返回int
类型也并没有解决问题..
看这么一个情况,万一返回的单个字节以二进制表示是1111 1111
,转换成int
类型会高位补符号位1,也就是1111 1111 1111 1111 1111 1111 1111 1111
,刚好是-1
(Java中数字是以补码形式存储的),所以这里就会产生混乱,我们无法区分返回-1
是不是到了字节流的末尾
那么这个问题该怎么解决呢?
这里要解决的问题其实就是当还没有读到字节流末尾时不能返回-1
,并且二进制字节流也不能改变
所以这里就加入了& 0xff
操作,我们再来看前面返回-1
的例子
1111 1111
高位自动补1后与上0xff
(0000 0000 0000 0000 0000 0000 1111 1111)
1111 1111 1111 1111 1111 1111 1111 1111 & 0000 0000 0000 0000 0000 0000 1111 1111 = 0000 0000 0000 0000 0000 0000 1111 1111
可知执行了& 0xff
操作后,相当于永远不会返回负数了,也就不会存在返回-1和到字节流末尾返回-1冲突的情况,并且实际的二进制结构也没有改变,可以说是完美的解决了这个问题。
上面的
1111 1111
会自动高位补符号位的原因是,当Java检测到byte
要转化或将要转换成高位类型时,会自动补高位符号位
补符号位
这里再解释一下补符号位
我们知道byte
占一个字节8位,而int
类型占4个字节32位,所以byte
类型向上转换成int
类型时需要补符号位,正数补0,负数补1。我们补符号位的目的是为了类型转换后大小和符号位都保持不变。
总结
本篇文章虽然是从IO
的read
方法接入,其实还是跟Java内部的编码格式有关,如数字在Java内存中是以补码存储的,数字类型转换会高位补符号位等等,要彻底搞懂还是要花些时间的。