jdk1.4引入,提供分块IO操作
NIO即New IO,分为标准输入输出NIO, 网络编程NIO
标准输入输出NIO
buffer和channel是NIO的核心对象,其中数据放入buffer中,应用程序即channel必须通过buffer来读写数据。
Buffer使用步骤:
写入->调用flip()->读取->clear()/compact()方法清空
注:clear清除整个缓冲区,compact清除已读数据
-- buffer类型:Byte,Char,Double,Float,Int,Long,ShortBuffer
Channel用于读写数据,可看作流。
特点:双向读写,可异步读写,读写必须通过buffer
--channel类型:File、Datagram(udp)、SocketChannel(tcp)、ServerSocket(tcp连接)Channel
文件中读取
//1 获取通道
FileInputStream fin = new FileInputStream("E:\\hello.txt");
FileChannel fc = fin.getChannel();
//2 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//3 将数据从通道读到缓冲区
int res = fc.read(buffer);
写入文件
Byte[] message = new Byte[]{1, 2, 3};
//1 获取一个通道
FileOutputStream out = new FileOutputStream("E:\\hello.txt");
FileChannel fc = out.getChannel();
//2 创建缓冲区,将数据写入缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
for (Byte i: message) {
buffer.put(i);
}
buffer.flip();
//3 缓冲区数据写入通道
fc.write(buffer);
文件拷贝(读写结合)
//1 声明源文件和目标文件
FileInputStream fi = new FileInputStream(new File("E:\\src.txt"));
FileOutputStream fo = new FileOutputStream(new File("E:\\dst.txt"));
//2 获得传输通道channel
FileChannel inChannel = fi.getChannel();
FileChannel outChannel = fo.getChannel();
//3 获得容器buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
//3 判断是否读完文件
int eof = inChannel.read(buffer);
if (eof == -1) {
break;
}
//4 重设一下buffer的position=0,limit=position
buffer.flip();
//5 开始写
outChannel.write(buffer);
//6 写完要重置buffer,重设position=0,limit=capacity
buffer.clear();
}
inChannel.close();
outChannel.close();
fi.close();
fo.close();
注意点
无数据时,xx.read(buffer) 返回 -1。
网络编程NIO
NIO第三个对象Selector
Selector selector = Selector.open();
Selector可单线程处理多个channel,可监听IO事件的发生,可将Channel注册到selector上
但Channel必须有异步模式(FileChannel没所以不行,SocketChannel可以),注册方法如下:
channel.configureBlocking(false);
SelectionKey key =channel.register(selector,SelectionKey.OP_READ);
selector四个常量(感兴趣监听的事件类型) interest set:
1. SelectionKey.OP_CONNECT
2. SelectionKey.OP_ACCEPT
3. SelectionKey.OP_READ
4. SelectionKey.OP_WRITE
注:可用 xxx | xxxx 接收多种事件。
判断是否interest set
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
判断事件就绪Ready set
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
获取channel和selector
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
给selector附加一个对象
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
或
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
获取就绪channel
Set
selectedKeys = selector.selectedKeys(); Iterator
keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
完整例子
package test_2018_05_08;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class MultiPortEcho {
private int ports[];
private ByteBuffer echoBuffer = ByteBuffer.allocate(1024);
public MultiPortEcho(int ports[]) throws IOException {
this.ports = ports;
go();
}
private void go() throws IOException {
// 1. 创建一个selector,select是NIO中的核心对象
// 它用来监听各种感兴趣的IO事件
Selector selector = Selector.open();
// 为每个端口打开一个监听, 并把这些监听注册到selector中
for (int i = 0; i < ports.length; ++i) {
//2. 打开一个ServerSocketChannel
//其实我们没监听一个端口就需要一个channel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);//设置为非阻塞
ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress(ports[i]);
ss.bind(address);//监听一个端口
//3. 注册到selector
//register的第一个参数永远都是selector
//第二个参数是我们要监听的事件
//OP_ACCEPT是新建立连接的事件
//也是适用于ServerSocketChannel的唯一事件类型
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Going to listen on " + ports[i]);
}
//4. 开始循环,我们已经注册了一些IO兴趣事件
while (true) {
//这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时
// select() 方法将返回所发生的事件的数量。
int num = selector.select();
//返回发生了事件的 SelectionKey 对象的一个 集合
Set selectedKeys = selector.selectedKeys();
//我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件
//对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。
Iterator it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
//5. 监听新连接。程序执行到这里,我们仅注册了 ServerSocketChannel
//并且仅注册它们“接收”事件。为确认这一点
//我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件
if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
//6. 接收了一个新连接。因为我们知道这个服务器套接字上有一个传入连接在等待
//所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// 7. 讲新连接注册到selector。将新连接的 SocketChannel 配置为非阻塞的
//而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector上
SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
it.remove();
System.out.println("Got connection from " + sc);
} else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
// Read the data
SocketChannel sc = (SocketChannel) key.channel();
// Echo data
int bytesEchoed = 0;
while (true) {
echoBuffer.clear();
int r = sc.read(echoBuffer);
if (r <= 0) {
break;
}
echoBuffer.flip();
sc.write(echoBuffer);
bytesEchoed += r;
}
System.out.println("Echoed " + bytesEchoed + " from " + sc);
it.remove();
}
}
// System.out.println( "going to clear" );
// selectedKeys.clear();
// System.out.println( "cleared" );
}
}
static public void main(String args2[]) throws Exception {
String args[] = {"9001", "9002", "9003"};
if (args.length <= 0) {
System.err.println("Usage: java MultiPortEcho port [port port ...]");
System.exit(1);
}
int ports[] = new int[args.length];
for (int i = 0; i < args.length; ++i) {
ports[i] = Integer.parseInt(args[i]);
}
new MultiPortEcho(ports);
}
}