available 的理解和一些使用
一、序言
这里主要介绍一下我们在读文件流,如何转换成byte[],从而引出这几种写法的一些原理和应用场景的区别。
二、测试代码
2.1 我们先看代码,代码的作用是获得将文件流转换成byte 数组,我们暂时不管它的正确性,代码也精简了很多。
// 这个用缓冲流 public static byte[] getByte1(String name) throws IOException{ FileInputStream is = null; is = new FileInputStream(new File(name)); byte[] buff = new byte[1024*4]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = -1; while((len = is.read(buff)) != -1){ baos.write(buff,0,len); } return baos.toByteArray(); } // 这个用一次性读入 public static byte[] getByte2(String name) throws IOException{ InputStream is = null; is = new FileInputStream(new File(name)); byte[] buff = new byte[is.available()]; is.read(buff); return buff; }
从上面代码看出,我们的区别是getByte1 拿到输入流对象了之后,再用输出流进行接收,然后再写到byte数组里面,而getByte2 是直接建立一个文件大小的byte 数组,一次性读入。从我的理解是:第二种方法的速度将优于第一种写法,其他方面我们稍后分析,先看看为什么优于第一种。
首先,他们都用了FileInputStream 的read(byte[]) 方法,只是说每次读入字节数不一样,方法一是建立一个缓冲区的大小,方法二是一次全部读入,但是再看baos.write(buff,0,len); 的源码:
// 这是write 方法 public synchronized void write(byte b[], int off, int len) { if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } // 我们可以看出,他其实是内部用了一个byte[] buf 进行存放我们缓存区的 . // 流数据数 ,长度不够了就会进行扩容,也就是数组的复制操作 int newcount = count + len; if (newcount > buf.length) { // 这里我们可以看出,为什么最好用2的次方最好了,当我们的流没有填./满缓冲区的时 // 候, 会多余一部分空间,当然下面的代码还会处理那部分 buf = Arrays.copyOf(buf, Math.max(buf.length << 1, newcount)); } // 处理多余空间 System.arraycopy(b, off, buf, count, len); count = newcount; }
我们结合上面可以看出,如果文件稍微大一点,比如1G (小于JVM 内存限制),我们的缓冲区一般设置都是按MB 进行,那么会进行大量循环,大量数组的复制移位操作,而方法二,相当于一次就分配了1G空间,并且根据文件大小进行匹配的,每次分配跟准确,不会进行其他操作,速度会快一些。
三、那么为什么我们不一直采取第二种方式呢?
先看看is.available() 的API
public int available() throws IOException返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。下一个调用可能是同一个线程,也可能是另一个线程。一次读取或跳过此估计数个字节不会受阻塞,但读取或跳过的字节数可能小于该数。 注意,有些 InputStream 的实现将返回流中的字节总数,但也有很多实现不会这样做。试图使用此方法的返回值分配缓冲区,以保存此流所有数据的做法是不正确的。 如果已经调用 close() 方法关闭了此输入流,那么此方法的子类实现可以选择抛出 IOException。 类 InputStream 的 available 方法总是返回 0。 此方法应该由子类重写。 返回: 可以不受阻塞地从此输入流读取(或跳过)的估计字节数;如果到达输入流末尾,则返回 0。
在InputStream 里面,始终是返回0的,那么我们来看看FileInputStream 对该方法的重写:
// native 方法~。~ public native int available() throws IOException; // 关于此方法在FileInputStream的实现,我们可以参考openjdk // 或者 http://www.codeforge.cn/read/199045/io_FileInputStream.c__html 去下载 // - -很遗憾,在源码里面我没找到我想要的答案,我发了一个帖子: // http://www.iteye.com/problems/100891 // 当然听群里朋友介绍,available 仅仅是返回不阻塞流里面的字节长度,和JDK描述一致。 // 也就是说只会获得流里面的长度,而不是文件长度。
另外进过测试,FileInputStream available() 在获取流的可读字节数的时候,即使超过JVM限制,比如10G文件,也是可以获取大小的,也就是说要么是直接可以获取的文件大小的描述,而socket 是通过协议层进行规定,也能获取的。
通常我们一般不能直接使用available方法的,因为上面提到available 读取的是不阻塞时流的长度。比如再网络传输时,我可能一个比较大的文件,当我打开一个流,如果这个流数据阻塞,还没传输过来,当你用该方法时,返回的总是0,或者说文件比较大,我分断传输,一段只能传输1G,那么你获得的大小只能该段流的大小,并不是文件的大小。
三、哪些地方可以使用available?
1.在本地文件文件中,这里我一般是直接使用的,比如:
InputStream is = null; is = new FileInputStream(new File(name)); byte[] buff = new byte[is.available()]; // 我的理解是本地文件获取的数值,应该是从文件描述里面获取的,也没啥阻塞,我个人认为可以直接// 使用.因为速度快,方便我们建立合适的缓存区
2.网络中的文件
a.比如web 中http 里面的文件流里面,第一种情况有content-length,那么小文件的情况,我们是直接可以通过request 获取该属性,也就知道文件的大小了,
b.在某些情况下,比如文件比较大,采用采用分段方式,长连接的方式发送,不能一次知道文件大小,http 的header 里面就没有上面的content-length 属性,而是变成了Transfer-Encoding: chunked属性,这表示分段发送信息,但是对整个文件的接受,可以通过一些标志位,或者一些超时限制等方法处理,这里不具体研究了。
c.socket 传输文件,这玩意我看了下源码,是没重写available方法的,也就是说我们不能通过这个知道流的字节数,一般情况下,我们可以先发送一段自定义的header 过去,描述文件大小,然后再循环持续获取流信息。
四、返回字节数组
1.直接读入
// 这种方式上面已经提过 InputStream is = null; is = new FileInputStream(new File(name)); byte[] buff = new byte[is.available()]; is.read(buff); // 但是不排除因为某些原因,导致数据流没过来,在出现问题的情况下,可以利用阻 // 塞特性,先读入一个字节。 int len = is.read(); byte[] buff = new byte[is.available()+1]; buff[0] = (byte)len; is.read(buff,1,buff.length);
2.利用缓冲区
// 这个在某些地方需要利用输出流的时候 ByteArrayOutputStream baos = new ByteArrayOutputStream(); int len = -1; while((len = is.read(buff)) != -1){ baos.write(buff,0,len); // 刷新一下,不然在缓冲区未满的时候不会写入 baos.flush(); } return baos.toByteArray();
3.在一些网络中,比如要抓取网络流信息信息情况下
HttpURLConnection con = .... con.connect(); InputStream in = (InputStream) con.getContent(); int total = ..// 获取总大小 byte buff[] = new byte[1024*8] // 缓冲区大小 int count = 0; while(count < total){ // 可以持续的对buff 进行复制,和上面byteOut.. 源码类似 count += in.read(buff); // 里面可以加些超时限制,和一些自己的处理 } // 当然可以直接转换才Reader 处理
小结:
1. 对于available的使用,群里有朋友是完全舍弃的,我的理解还是觉得存在即为合理,在某些地方使用上,还是能节约一些时间,可能我代码上有些偏执的追求吧。
2,。上面大概解释了一些用法,不够全面,不对的地方请指点啦。
上一篇: 源码分析--ThreadLocal
下一篇: 反射(一)----原理机制和基本运用