Java NIO(New IO Non Blocking IO)
NIO可以以更加高效的方式进行读写操作
IO | NIO |
---|---|
面向流的(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(无) | 选择器(Selector) |
传统的方式
NIO方式
缓冲区:在Java NIO中负责数据的传输,缓冲区就是数组。用于存储不同数据类型的数据
根据数据类型不同(boolean除外,byte,short,char,long,int,float,double都有对应的),提供了相应类型的缓冲区
管理方式基本一致,都是通过allocate() 获取缓冲区
最常用的 ByteBuffer
用于存取数据的两个核心方法
- put 存入数据到缓冲区中
- get 获取缓冲区的数据
缓冲区的四个核心属性
-
capacity 容量,表示缓冲区中最大存储数据的容量 一旦声明不能改变
-
limit 界限 ,表示缓冲区中可以操作数据的大小 limit后面的数据是不能进行读写的
-
position 位置,表示缓冲区中正在操作数据的位置 position<=limit<=capacity
-
mark 标记,用于记录position的位置,用reset() 恢复到 mark的位置
@Test
public void test1(){
String string = "abcd";
//分配一个指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
System.out.println("----------------allocate----------------");
System.out.println("缓冲区的大小是:"+byteBuffer.capacity());
System.out.println("可以操作数据的大小是:"+byteBuffer.limit());
System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
//利用put存入数据
byteBuffer.put(string.getBytes());
System.out.println("----------------put----------------");
System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
//调用filp方法从写数据模式切换到读数据模式
Buffer flip = byteBuffer.flip();
System.out.println(flip);
System.out.println("----------------flip----------------");
System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
//利用get读取缓冲区中的数据
byte[] bytes = new byte[byteBuffer.limit()];
byteBuffer.get(bytes);
System.out.println("读取的数据是:"+new String(bytes,0,bytes.length));
System.out.println("----------------get----------------");
System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
//rewind 可重复读数据
byteBuffer.rewind();
System.out.println("----------------rewind----------------");
System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
//clear清空缓冲区,但是缓冲区中的数据依然存在,只是这些数据处于被遗忘状态
byteBuffer.clear();
System.out.println("----------------clear----------------");
System.out.println("当前缓冲区的大小是:"+byteBuffer.capacity());
System.out.println("当前可以操作数据的大小是:"+byteBuffer.limit());
System.out.println("当前正在操作数据的位置是:"+byteBuffer.position());
//判断缓冲区中是否还有剩余的数据
if (byteBuffer.hasRemaining()){
//获取缓冲区中还可以操作的数量
System.out.println("缓冲区中还可以操作的数量"+byteBuffer.remaining());
}
}
----------------allocate----------------
缓冲区的大小是:1024
可以操作数据的大小是:1024
当前正在操作数据的位置是:0
----------------put----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:1024
当前正在操作数据的位置是:4
java.nio.HeapByteBuffer[pos=0 lim=4 cap=1024]
----------------flip----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:4
当前正在操作数据的位置是:0
读取的数据是:abcd
----------------get----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:4
当前正在操作数据的位置是:4
----------------rewind----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:4
当前正在操作数据的位置是:0
----------------clear----------------
当前缓冲区的大小是:1024
当前可以操作数据的大小是:1024
当前正在操作数据的位置是:0
缓冲区中还可以操作的数量1024
直接缓冲区与非直接缓冲区
- 非直接缓冲区:通过 allocate()方法分配的缓冲区,将缓冲区建立在JVM的内存中
- 直接缓冲区:通过allocateDirect()方法分配的缓冲区,将缓冲区建立在操作系统上的物理内存中,可以提高效率
省略了 copy 的过程,所以提升了效率
通道
用于源节点与目标节点的连接,在NIO中,主要针对于负责缓冲区中数据的传输,通道本身是不存储任何数据的,配合缓冲区进行传输
通道主要实现类
- java.nio.channels.Channel 接口:
- FileChannel 本地的
- SocketChannel TCP
- ServerSocketChannel TCP
- DatagramChannel UDP
获取通道
-
Java 针对支持通道的类提供了getChannel() 方法
-
本地 IO
- FileInputStream/FileOutputStream
- RandomAccessFile
-
网络 IO
- Socket
- ServerSocket
- DatagramSocket
-
-
在 JDK1.7 中的 NIO.2 针对各个通道提供了静态方法 open()
-
在 JDK1.7 中的 NIO.2的Files工具类的 newByteChannel() 方法
完成文件的复制
-
利用通道(非直接缓冲区)
public class test1 { //利用通道完成文件的复制 @Test public void channelTest(){ FileInputStream inputStream = null; FileOutputStream outputStream = null; FileChannel inChannel = null; FileChannel outChannel = null; try { inputStream = new FileInputStream(""); outputStream = new FileOutputStream(""); //获取通道 inChannel = inputStream.getChannel(); outChannel = outputStream.getChannel(); //传输数据 //分配一个指定大小的缓存区 ByteBuffer buffer = ByteBuffer.allocate(1024); int len = 0; //将通道中的数据存入缓冲区 while ((len=inChannel.read(buffer))!=-1){ //将缓冲区的数据写入通道 buffer.flip(); outChannel.write(buffer); //清空缓冲区 buffer.clear(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if (outChannel==null){ try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (inChannel==null){ try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (outputStream==null){ try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (inputStream==null){ try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
直接缓冲区的文件复制(内存映射的方式)
public class test2 { @Test public void copyTest(){ FileChannel inChannel = null; FileChannel outChannel = null; MappedByteBuffer inMapBuffer =null; MappedByteBuffer outMapBuffer = null; try { //获取通道 inChannel = FileChannel.open(Paths.get(""), StandardOpenOption.READ); outChannel = FileChannel.open(Paths.get(""), StandardOpenOption.READ, StandardOpenOption.WRITE); //复制的操作 inMapBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size()); outMapBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size()); //直接对缓冲区进行数据的读写操作 byte[] bytes = new byte[1024]; inMapBuffer.get(bytes); outMapBuffer.put(bytes); } catch (IOException e) { e.printStackTrace(); }finally { if (outChannel==null){ try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (inChannel==null){ try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
通道之间的数据传输(直接缓冲区的方式)
transferFrom()
transferTo()
public class Test3 { //通道之间的数据传输 @Test public void copyTest(){ FileChannel inChannel = null; FileChannel outChannel = null; try { inChannel = FileChannel.open(Paths.get(""), StandardOpenOption.READ); outChannel = FileChannel.open(Paths.get(""),StandardOpenOption.READ,StandardOpenOption.WRITE); //inChannel.transferTo(0,inChannel.size(),outChannel); outChannel.transferFrom(inChannel,0,inChannel.size()); } catch (IOException e) { e.printStackTrace(); }finally { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
分散于聚集
分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
聚集写入(Gathering Writes):将多个缓冲区的数据都聚集到通道中
public class Test4 {
//分散和聚集的方式
public void copyTest(){
RandomAccessFile randomAccessFile1 = null;
RandomAccessFile randomAccessFile2 = null;
try {
randomAccessFile1 = new RandomAccessFile("","rw"); //rw是读写模式
//获取通道
FileChannel fileChannel = randomAccessFile1.getChannel();
//分配指定大小的缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(1024);
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
ByteBuffer buffer3 = ByteBuffer.allocate(1024);
//分散读取
ByteBuffer[] byteBuffers = new ByteBuffer[]{buffer1,buffer2,buffer3};
for(ByteBuffer byteBuffer : byteBuffers){
byteBuffer.flip();
}
System.out.println(new String(byteBuffers[0].array(),0,byteBuffers[0].limit()));
System.out.println("-----------------------");
System.out.println(new String(byteBuffers[1].array(),0,byteBuffers[1].limit()));
System.out.println("-----------------------");
System.out.println(new String(byteBuffers[2].array(),0,byteBuffers[2].limit()));
//聚集写入
randomAccessFile2 = new RandomAccessFile("","rw");
FileChannel file2Channel = randomAccessFile2.getChannel();
try {
file2Channel.write(byteBuffers);
} catch (IOException e) {
e.printStackTrace();
}
try {
fileChannel.read(byteBuffers);
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
try {
randomAccessFile2.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
randomAccessFile1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字符集(Charset)
- 编码:字符串—>字节数组
- 解码:字节数组—>字符串
NIO的非阻塞式网络通信
-
使用NIO完成网络通信的三个核心
-
通道(Channel)负责连接
- SocketChannel
- ServerSocketChannel
- DatagramChannel
-
缓冲区(Buffer)负责数据的存取
-
选择器(Selector)是 SelectableChannel 的多路复用器,用于监控 SelectableChannel 的 IO 状况的
-
-
使用NIO完成网络通信
-
阻塞式
public class SocketChannelTest { //客户端 @Test public void client(){ //获取通道 SocketChannel socketChannel =null; FileChannel inChannel = null; try { //获取通道 socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9090)); //发送数据 //首先创建缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //读取文件并发送到服务端去 inChannel = FileChannel.open(Paths.get(""), StandardOpenOption.READ); while ((inChannel.read(byteBuffer))!=-1){ byteBuffer.flip(); socketChannel.write(byteBuffer); byteBuffer.clear(); }; } catch (IOException e) { e.printStackTrace(); }finally { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } try { socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } //客户端 @Test public void server(){ ServerSocketChannel socketChannel = null; FileChannel outChannel = null; try { //获取通道 socketChannel = ServerSocketChannel.open(); outChannel = FileChannel.open(Paths.get(""),StandardOpenOption.READ,StandardOpenOption.WRITE); //绑定端口号 socketChannel.bind(new InetSocketAddress(9090)); //获取客户端的连接的通道 SocketChannel accept = socketChannel.accept(); //接收客户端的数据,并保存 //创建缓存区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //边读边写 while ((outChannel.read(byteBuffer))!=-1){ byteBuffer.flip(); outChannel.write(byteBuffer); byteBuffer.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { try { outChannel.close(); } catch (IOException e) { e.printStackTrace(); } try { socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
非阻塞式
public class NonBlockingNIOTest { @Test public void client(){ SocketChannel socketChannel = null; try { //获取通道 socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9090)); FileChannel fileChannel = FileChannel.open(Paths.get(""), StandardOpenOption.READ); //切换成非阻塞式 socketChannel.configureBlocking(false); //分配指定大小的缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //发送数据给服务端 while (fileChannel.read(byteBuffer)!=-1){ byteBuffer.flip(); socketChannel.write(byteBuffer); byteBuffer.clear(); } } catch (IOException e) { e.printStackTrace(); }finally { try { socketChannel.close(); } catch (IOException e) { e.printStackTrace(); } } } @Test public void server(){ //获取通道 ServerSocketChannel serverSocketChannel = null; try { //获取通道 serverSocketChannel = ServerSocketChannel.open(); //切换成非阻塞式 serverSocketChannel.configureBlocking(false); //绑定连接 serverSocketChannel.bind(new InetSocketAddress(9090)); //获取选择器 Selector selector = Selector.open(); //将通道注册到选择器上,指定监听事件:接收事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //通过选择器轮询获取已经准备就绪的事件 while (selector.select()>0){ //获取当前选择器中所有的注册的选择键(已经就绪的监听事件) Set<SelectionKey> selectionKeys = selector.selectedKeys();//包含了所有注册的事件 //使用迭代器 Iterator<SelectionKey> selectionKey = selectionKeys.iterator(); while (selectionKey.hasNext()){ //获取准备就绪的事件 SelectionKey key = selectionKey.next(); //判断是什么事件准备就绪 if (key.isAcceptable()){ //若是接收状态就绪,获取客户端链接 SocketChannel socketChannel = serverSocketChannel.accept(); //切换非阻塞模式 socketChannel.configureBlocking(false); //将该通道注册到选择器上 socketChannel.register(selector,SelectionKey.OP_READ);//监听读就绪事件 }else if (key.isReadable()){ //获取当前选择器上读就绪状态的通道 SocketChannel socketChannel = (SocketChannel) key.channel(); //读取数据 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); while ((socketChannel.read(byteBuffer))!=-1){ byteBuffer.flip(); socketChannel.write(byteBuffer); byteBuffer.clear(); } } //取消选择键 selectionKey selectionKey.remove(); } } } catch (IOException e) { e.printStackTrace(); } } }
此时开启服务器,服务器会一直等待,哪个客户端准备就绪,就会接受哪个客户端的数据
-