终于,这两天的考试熬过去了, 兴致冲冲的来整理笔记来, 这篇博客是我近几天的NIO印象笔记汇总,记录了对Selector及Selector的重要参数的理解,对Channel的理解,常见的Channel,对NIO事件驱动的编程模型的理解,NIO与传统IO的对比,NIO的TCP/IP编程的实践.
Channel 什么是Channel这个概念绝对是一级概念,Channel是一个管道,用于连接字节缓冲区和另一端的实体, 这个字节缓冲区就是ByteBuffer, 另一端的实体可以是一个File 或者是 Socket ;
或者基于IO的网络编程, 数据的交互借助于InputStream或者是OutputStream, 而Channel可以理解成对Stream的又一层封装;在这种编程模型中 服务端想和客户端进行交互,就需要从服务端自己的ServerSocketChannel中获取前来连接的客户端的SocketChannel,并把他注册关联上感性趣的事件且自己的Selector选择器上, 这样一旦客户端把Buffer中的数据推送进channel, 服务端就可以感知,进而处理
文件通道: FileChannel
套接字通道
服务端: ServerSocketChannel
客户端: SocketChannel
数据包通道: DataGramSocket
Channel 与 StreamChannel的NIO编程模型中一大组件,它类似IO中的Stream,但是两者也有本质的区别;
为什么说是类似呢? 看下面的两段代码, 需求是磁盘上的文件进行读写
在IO编程中,我们第一步可能要像下面这样获取输入流,按字节把磁盘上的数据读取到程序中,再进行下一步操作
FileInputStream fileInputStream = new FileInputStream("123.txt");在NIO编程中,目标是需要先获取通道,再基于Channel进行读写
FileInputStream fileInputStream = new FileInputStream("123.txt"); FileChannel channel = fileInputStream.channel();对用户来说,在IO / NIO 中这两种都直接关联这磁盘上的数据文件,数据的读写首先都是获取Stream和Channel,所以说他们相似;
但是: 对于Stream来说,所有的Stream都是单向的,对我们的程序来说,Stream要么只能是从里面获取数据的输入流,要么是往里面输入数据的输出流,因为InputStream和outputStream都是抽象类,在java中是不支持多继承的, 而通道不同,他是双向的,对一个通道可读可写
怎么理解 Channel可以是双向的?如上图,凡是同时实现了readable,writeable接口的类,都双向的通道. 下面是典型的例子
SocketChannel 在NIO网络编程中,服务端可以通过ServerSocketChannel获取客户端的SocketChannel 这个SocketChannel可以read() 客户端的消息存入Buffer, 往客户端 write()buffer里的内容 socketChannel1.read(byteBuffer); socketChannel1.write(byteBuffer);对于一个channel,我们既能从中获取数据,也能往外read数据
基于channel的文件拷贝方式和传统的IO拷贝的竞速效率最低的按字节拷贝
public static void text4() throws IOException { System.out.println("开始: ... "); FileInputStream fis = new FileInputStream("123.txt"); FileOutputStream fos = new FileOutputStream("output123.txt"); int read=0; long start =0; while((read=fis.read())!=-1){ fos.write(read); } System.out.println("耗时: "+(System.currentTimeMillis()-start) ); fis.close(); fos.close(); }一个3901KB的文件的拷贝,在我的机器上跑出了 1561097384707 的好成绩; 实属无奈,擦点以为编译器卡死
以NIO,channel+buffer的模型,拷贝文件
try ( FileInputStream fis = new FileInputStream("123.txt"); FileOutputStream fos = new FileOutputStream("output123.txt"); ){ //1.获取通道 FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel(); //2.分配指定大小的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); long start = System.currentTimeMillis(); //3.将通道中的数据缓冲区中 while (inChannel.read(buffer) != -1) { buffer.flip();//切换成都数据模式 //4.将缓冲区中的数据写入通道中 outChannel.write(buffer); buffer.clear();//清空缓冲区 } System.out.println("总耗时:" + (System.currentTimeMillis() - start)); } catch (Exception e) { e.printStackTrace(); }速度明显提升 大约平均耗时 110
NIO+零拷贝 复制文件
// 直接获取通道 FileChannel inChannel2 = FileChannel.open(Paths.get("123.txt"), StandardOpenOption.READ); FileChannel outChannel2 = FileChannel.open(Paths.get("output123.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE); //内存映射文件 MappedByteBuffer inMappedBuf = inChannel2.map(FileChannel.MapMode.READ_ONLY, 0, inChannel2.size()); MappedByteBuffer outMappedBuf = outChannel2.map(FileChannel.MapMode.READ_WRITE, 0, inChannel2.size()); //直接对缓冲区进行数据读写操作 byte[] dst = new byte[inMappedBuf.limit()]; long start = System.currentTimeMillis(); inMappedBuf.get(dst); outMappedBuf.put(dst); System.out.println("耗费的时间为:" + ( System.currentTimeMillis() - start)); inChannel2.close(); outChannel2.close();