也就是new IO,自Java1.4开始。是基于Block的IO,比基于流的IO快。
它是面向缓冲区的 (Buffered Oriented), 非阻塞IO。
面向流和面向缓冲区
面向流的操作是单向操作。比如InputStream, OutputStream,没有双向的。
面向缓冲区的IO,在文件和程序间有一个类似管道的Channel和一个类似火车的Buffer。火车是双向的,反映的NIO也是双向的。
同时,程序只处理缓冲区,而不处理Channel。
核心组件
Channel Buffer Selector.
基于Channel 和 Buffer就可以完成数据处理。
Buffer
相当于一个数组。对应primitive 类型,每种类型都有各自的Buffer (没有布尔) 。
最常用的是ByteBuffer
创建Buffer可以在堆上、直接内存里。也可以把普通的byte数组包装一下。
public class BufferTest { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100); ByteBuffer byteBuffer1 = ByteBuffer.allocate(100); byte[] bytes = new byte[10]; ByteBuffer wrap = ByteBuffer.wrap(bytes); } }
常用方法
put get
public class BufferTest { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(10); byteBuffer.put((byte) 10); byteBuffer.put((byte) 20); byteBuffer.put((byte) 30); System.out.println(byteBuffer.get(1)); byte[] array = byteBuffer.array(); System.out.println(Arrays.toString(array)); } }
核心变量属性
Capacity Limit Position Mark
Capacity -> 数组长。能容纳元素的最大数量。
Limit -> 缓冲区中可以操纵的数据大小
Position -> 下一个要被读/写的下标(位置)
例子
Capacity是设定后不变的,Limit和Position会变。
这是我写的一个例子。在这个例子中,长度为10的buffer的前5个字节存储了hello这个字符串。现在Capacity Limit都还是10, Position变成了5, 因为下一个读写位置是5。
如果我现在调用一次flip() 则limit会变成旧的position值 (5) ,position会变成0。flip就是变成读模式。注意读模式不是读东西,而是读取了数据再变成读模式。
public class BufferTest { public static void main(String[] args) { ByteBuffer byteBuffer = ByteBuffer.allocate(10); byteBuffer.put("hello".getBytes(StandardCharsets.UTF_8)); byteBuffer.flip(); byte[] bytes = new byte[byteBuffer.limit()]; byteBuffer.get(bytes); System.out.println(new String(bytes)); } }
clear()可以清空缓冲区。但是缓冲区里的数据还在,这些数据处于被遗忘的状态,只是不能再用get读取了。
mark()可以记录上一次读写操作后的position位置,如果想还原,可以调用reset()方法。
Channel
Channel是个接口,常用的实现类有FileChannel DatagramChannel SocketChannel ServerSocketChannel
分别对应 文件 UDP TCP
例子 FileChannel png复制
public class ChannelTest { private static final String FILEPATH = "E:\\java-projects\\module5\\original.png"; private static final String COPIED = "E:\\java-projects\\module5\\copy.png"; public static void main(String[] args) throws Exception{ FileInputStream fileInputStream = new FileInputStream(FILEPATH); FileOutputStream fileOutputStream = new FileOutputStream(COPIED); FileChannel channel1 = fileInputStream.getChannel(); FileChannel channel2 = fileOutputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); while (channel1.read(byteBuffer) != -1) { //精华全在这。读取完之后position会走到末尾,需要flip变成读模式。 byteBuffer.flip(); channel2.write(byteBuffer); //写完数据,position又动了,clear把position还原到头部 byteBuffer.clear(); } fileOutputStream.close(); fileInputStream.close(); } }
例子 网络编程
客户端
public class NetworkChannel { public static void main(String[] args) throws Exception{ SocketChannel channel = SocketChannel.open(); channel.connect(new InetSocketAddress(9999)); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); byteBuffer.put("hello world".getBytes(StandardCharsets.UTF_8)); byteBuffer.flip(); channel.write(byteBuffer); channel.close(); } }
服务端
public class NetworkServerChannel { public static void main(String[] args) throws Exception{ ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(9999)); serverSocketChannel.configureBlocking(false); while (true) { SocketChannel clientChannel = serverSocketChannel.accept(); if (clientChannel != null) { ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int len = clientChannel.read(byteBuffer); System.out.println(new String(byteBuffer.array(), 0, len)); break; } else { System.out.println("做点别的事"); Thread.sleep(100); } } } }
多路复用
假设三个客户端程序连接三个不同的端口,没多路复用的话你需要三个线程,监听三个端口。如果客户端多了,你需要海量的线程。线程切换很费资源。
有了多路复用器,你只需要一个selector线程去同时监听多个端口。具体到NIO,一个selector可以监听多个Channel。(FileChannel不行,它没继承SelectableChannel)
注册
使用Channel.register(Selector sel,int ops)方法,将一个通道注册到一个选择器时。
第一个参数:指定通道要注册的选择器是谁
第二个参数:指定选择器需要查询的通道操作
可以供选择器查询的通道操作,从类型来分,包括以下四种:
(1)可读 : SelectionKey.OP_READ
(2)可写 : SelectionKey.OP_WRITE
(3)连接 : SelectionKey.OP_CONNECT
(4)接收 : SelectionKey.OP_ACCEPT
如果Selector对通道的多操作类型感兴趣,可以用“位或”操作符来实现:int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ;
例子
public class SelectorTest { public static void main(String[] args) throws Exception { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel1 = ServerSocketChannel.open(); ServerSocketChannel serverSocketChannel2 = ServerSocketChannel.open(); ServerSocketChannel serverSocketChannel3 = ServerSocketChannel.open(); serverSocketChannel1.bind(new InetSocketAddress(7777)); serverSocketChannel2.bind(new InetSocketAddress(8888)); serverSocketChannel3.bind(new InetSocketAddress(9999)); //与selector一起使用的时候必须设置非阻塞 serverSocketChannel1.configureBlocking(false); serverSocketChannel1.register(selector, SelectionKey.OP_ACCEPT); serverSocketChannel2.configureBlocking(false); serverSocketChannel2.register(selector, SelectionKey.OP_ACCEPT); serverSocketChannel3.configureBlocking(false); serverSocketChannel3.register(selector, SelectionKey.OP_ACCEPT); //会阻塞。直到至少有一个channel就绪。 while (selector.select() > 0) { //就绪的集合保存在SelectedKeys集合中 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); if (key.isAcceptable()) { //服务器的channel对象 ServerSocketChannel channel = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = channel.accept(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int len = clientChannel.read(byteBuffer); System.out.println(new String(byteBuffer.array(), 0, len)); clientChannel.close(); } iterator.remove(); } serverSocketChannel1.close(); serverSocketChannel2.close(); serverSocketChannel3.close(); } } }