Java IO与NIO的相关有关问题
流(Stream)是最早的Java对IO的抽象,而通道(Channel)是NIO对新Java对IO的抽象,通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是 InputStream
或者 OutputStream
的子类),
而 通道
可以用于读、写或者同时用于读写。流和通道的基本单位都是字节,但是流是以字节数组作为缓冲区中介,而通道是以ByteBuffer来作为缓冲区中介。
流中包含的字节如流水一样,一旦流过去,就无法再使用。但由于流的实现是抽象类,在其子类中可以选择覆写父类的某些操作,所以子类输入输出流可能会有额外的控制操作,以便实现流的部分内容的重新读取,如流的标记(mark)和重置功能(reset),但并不是每一种流都有这两种功能,需要有相应的实现,可通过markSupported方法来判断是否支持标记功能。因为流无法复用,假如有一个流有多个接收者,那怎么办?上面介绍的标记和重置功能是其中一个方法,也可以把流中的全部数据保存到一个字节数组中,在不同的接受着中数据传递是通过这个字节数组来完成,而不是使用流的对象。其中流的标记和重置功能也是通过第二种方法实现的,只是这个字节数组的保存和操作是又流的某些子类来实现,如BufferedInputStream类。
调用流的read方法时,如果没有足够的数据可以用,则read方法会被阻塞,直到当前的流成功的完成准备为止。从流中读取的数据并不是马上进入目的介质,而是先放入字节数组等缓冲区,等合适的时机再执行实际的写入操作。当然可以通过调用flush方法强制写入,注意在缓冲区满或者流关闭的时候,也会自动执行实际的写入操作。
字节流是处理字节的,Java IO还有一种处理字符的字符流,即java.io.Reader类和java.io.Writer类及其子类,字符类一般是通过字节流InputStream类和OutputStream类来创建,对应的是InputStreamReader类和OutputStreamWriter类,只要指定编码格式就行了。
//使用流来实现文件的复制 public static void copyFileByStream(String src,String dest) throws IOException{ FileInputStream in=new FileInputStream(src); File file=new File(dest); if(!file.exists()) file.createNewFile(); FileOutputStream out=new FileOutputStream(file); int c; byte buffer[]=new byte[1024]; //每次读取的字节数 while((c=in.read(buffer))!=-1){ out.write(buffer); } in.close(); out.close(); } //使用ByteBuffer作为缓冲区来实现文件的复制 public static void copyFileByByteBuffer(String src,String dest) throws IOException{ ByteBuffer buffer = ByteBuffer.allocateDirect(32*1024);//分配ByteBuffer的容量大小 FileInputStream in=new FileInputStream(src); FileOutputStream out=new FileOutputStream(dest); FileChannel s = in.getChannel(); FileChannel d = out.getChannel(); while(s.read(buffer)>0||buffer.position()!=0){ //通过在通道上使用ByteBuffer来传输,不需要记录每次实际读取的字节数 buffer.flip(); d.write(buffer); buffer.compact(); } } //使用通道的传输方法来实现文件的复制 public static void copyFileByChannelTransfer(String src,String dest) throws IOException{ ByteBuffer buffer = ByteBuffer.allocateDirect(32*1024); FileInputStream in=new FileInputStream(src); FileOutputStream out=new FileOutputStream(dest); FileChannel s = in.getChannel(); FileChannel d = out.getChannel(); s.transferTo(0, s.size(), d);//直接从一个通道传输到另一个通道。 }由以上可以看出,使用ByteBuffer类不需要像流一样记录每次实际读取的字节数,只要分配一个固定大小的ByteBuffer缓冲区就行了,文件通道的transferTo方法使得数据传输更加简单。
对大文件的操作一般使用ByteBuffer的子类MappedByteBuffer,该类将系统的内存地址映射到要操作的文件上,操作这些内存地址就相当于读取文件的内容,这样就大大提高了操作文件的性能。具体用法看相关文档。
一辆从 A 开往 B 的公共汽车上,路上有很多点可能会有人下车。司机不知道哪些点会有哪些人会下车,对于需要下车的人,如何处理更好? 1. 司机过程中定时询问每个乘客是否到达目的地,若有人说到了,那么司机停车,乘客下车。 ( 类似阻塞式 ) 2. 每个人(相当于套接字通道)告诉售票员(相当于Selector)自己的目的地(相当于套接字的事件),然后睡觉,司机(相当于CPU)只和售票员交互,到了某个点由售票员通知乘客下车。 ( 类似非阻塞 ),很显然,每个人要到达某个目的地可以认为是一个线程,司机可以认为是 CPU 。在阻塞式里面,每个线程需要不断的轮询,上下文切换,以达到找到目的地的结果。而在非阻塞方式里,每个乘客 ( 线程 ) 都在睡觉 ( 休眠 ) ,只在真正外部环境准备好了才唤醒,这样的唤醒肯定不会阻塞。 |