在JDK1.4时Sun发布了java.nio这个包,顾名思义N代表NEW,即新一代IO通道,相比较于旧的IO流,新的IO通道是同步非阻塞的,因为旧的IO流越来满足不了现在需求,我们需要一种全新的非阻塞方式的IO通道。所以NIO诞生了。
NIO中主要分为三大模块: Buffer(缓冲区)、Channel(通道)、Selector(选择器)
其中Channel是最重要的一个模块,我们可以使用的通道有(FileChannel、SocketChannel、ServerSocketChannel等),由于Channel是双向的,它允许我们在将资源或文件写出到缓冲区、或者从缓冲区写入。因此在文件操作中,或者是套接字通信中,我们都可以使用Channel来完成。
Buffer是通道中不可缺少的一个组成部分,缓冲区作为数据信息的载体,它通常用来处理一些数据操作上的问题。
Selector是SelectableChannel 对象的多路复用器。我们可以通过某个套接字通道的 register 方法注册该通道时,定义所需要进行的操作OP_ACCEPT
( 用于套接字接受操作的操作集位) OP_CONNECT
( 用于套接字连接操作的操作集位) OP_READ(用于读取操作的操作集位)
OP_WRITE
(用于写入操作的操作集位),当它执行select会阻塞,直到有请求为止,当执行selectedKeys方法时会返回SelectionKey类型的Set集合,通过迭代器迭代我们可以对以上四种状态进行分别处理。
例子一:展示SocketChannel和Selector的使用
import java.io.IOException;
import java.net.InetSocketAddress;
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;
import java.util.concurrent.TimeUnit;
/**
* java.nio.channels.Selector
* SelectableChannel 对象的多路复用器。
* 可通过调用此类的 open 方法创建选择器,该方法将使用系统的默认选择器提供者创建新的选择器。
* 也可通过调用自定义选择器提供者的 openSelector 方法来创建选择器。
* 通过选择器的 close 方法关闭选择器之前,它一直保持打开状态。
* 选择请求的类型(isAcceptable,isConnectable,isReadable,isWritable)分别执行相应的code
*/
public class SelectorTest {
public static void main(String[] args) {
try {
//新线程执行服务端,selector.select()是阻塞线程的
new Thread(new Runnable() {
public void run() {
ChannelServer.open();
}
}).start();
//延迟两秒主(main)线程执行客户端
TimeUnit.SECONDS.sleep(2);
ChannelClient.send();
} catch (Exception e) {
e.printStackTrace();
}
}
//
private static class ChannelServer {
public static void open() {
try {
ServerSocketChannel ssc = ServerSocketChannel.open();
// 调整此通道的阻塞模式。
// 如果向一个或多个选择器注册了此通道,则尝试将此通道置于阻塞模式将导致抛出
ssc.configureBlocking(false);
ssc.socket().bind(new InetSocketAddress(11011));
// 通过调用系统级默认 SelectorProvider 对象的 openSelector 方法来创建新的选择器。
Selector selector = Selector.open();
// 向给定的选择器注册此通道,返回一个选择键
// SelectionKey selectionKey =
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 轮询
while (true) {
// 选择一组键,其相应的通道已为 I/O 操作准备就绪。
// 此方法是阻塞模式的选择操作。
// 仅在至少选择一个通道、调用此选择器的 wakeup 方法,或者当前的线程已中断(以先到者为准)后此方法才返回。
// 返回:
// 已更新其准备就绪操作集的键的数目,该数目可能为零
// 唤醒此方法的方法为wakeUp[selector.wakeup()];
int readyChannels = selector.select();
if (readyChannels == 0)
continue;
// 返回此选择器的已选择键集。
// 可从已选择键集中移除键,但是无法直接添加键。
// 试图向该键集中添加对象会导致抛出 UnsupportedOperationException。
// 已选择键集是非线程安全的。
Set<SelectionKey> selectedKey = selector.selectedKeys();
for (Iterator<SelectionKey> iter = selectedKey.iterator(); iter.hasNext();) {
SelectionKey key = iter.next();
// 测试此键的通道是否已准备好接受新的套接字连接。
if (key.isAcceptable()) {
System.out.println("execute method::key.isAcceptable()");
// 返回为之创建此键的通道。即使已取消该键,此方法仍继续返回通道。
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 接受到此通道套接字的连接。
// 如果此通道处于非阻塞模式,那么在不存在挂起的连接时,此方法将直接返回 null。
// 否则,在新的连接可用或者发生 I/O 错误之前会无限期地阻塞它。
// 不管此通道的阻塞模式如何,此方法返回的套接字通道(如果有)将处于阻塞模式。
SocketChannel client = server.accept();
// 非阻塞
client.configureBlocking(false);
// 用于读取操作的操作集位。
client.register(selector, SelectionKey.OP_READ);
// 测试此键的通道是否已完成其套接字连接操作
} else if (key.isConnectable()) {
System.out.println("execute method::key.isConnectable()");
// 测试此键的通道是否已准备好进行读取。
} else if (key.isReadable()) {
System.out.println("execute method::key.isReadable()");
// 写出文件信息
ByteBuffer bb = ByteBuffer.allocate(1024);
SocketChannel sc = (SocketChannel) key.channel();
sc.read(bb);
bb.flip();
while (bb.hasRemaining()) {
System.out.print((char) bb.get());
}
System.out.println();
// 将操作转移为写操作
SocketChannel sch = (SocketChannel) key.channel();
// 链式的配置注册写操作
sch.configureBlocking(false).register(selector, SelectionKey.OP_WRITE);
// 测试此键的通道是否已准备好进行写入。
} else if (key.isWritable()) {
System.out.println("execute method::key.isWritable()");
SocketChannel sc = (SocketChannel) key.channel();
sc.write(ByteBuffer.wrap("tomorrow is Saturday".getBytes()));
selector.close();
ssc.close();
return;
}
// 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)
iter.remove();
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
private static class ChannelClient {
public static void send() {
try {
// 打开通道
SocketChannel channel = SocketChannel.open();
// 非阻塞
channel.configureBlocking(false);
// 打开选择器
Selector selector = Selector.open();
// 为选择器注册通道
channel.register(selector, SelectionKey.OP_CONNECT);
// 建立连接
channel.connect(new InetSocketAddress("127.0.0.1", 11011));
// 轮询
while (true) {
selector.select();
Set<SelectionKey> key = selector.selectedKeys();
for (Iterator<SelectionKey> iter = key.iterator(); iter.hasNext();) {
SelectionKey selectionKey = iter.next();
// 测试此键的通道是否已完成其套接字连接操作
if (selectionKey.isConnectable()) {
// 连接建立事件,已成功连接至服务器
channel = (SocketChannel) selectionKey.channel();
// 判断此通道上是否正在进行连接操作
if (channel.isConnectionPending()) {
// 完成套接字通道的连接过程。
// 如果已连接了此通道,则不阻塞此方法并且立即返回 true。如果此通道处于非阻塞模式,那么当连接过程尚未完成时,此方法将返回 false。
// 如果此通道处于阻塞模式,则在连接完成或失败之前将阻塞此方法,并且总是返回 true 或抛出描述该失败的、经过检查的异常。
// 可在任意时间调用此方法。如果正在调用此方法时在此通道上调用读取或写入操作,则在此调用完成前将首先阻塞该操作。
// 如果试图发起连接但失败了,也就是说如果调用此方法导致抛出经过检查的异常,则关闭此通道。
channel.finishConnect();
System.out.println("connect success !");
// 写出信息
channel.write(ByteBuffer.wrap("worinixianrenbanban".getBytes("UTF-8")));
// 注册读事件
channel.register(selector, SelectionKey.OP_READ);
}
} else if (selectionKey.isReadable()) {
System.out.println("sad! >_<|");
// 读出操作
SocketChannel sc = (SocketChannel) selectionKey.channel();
ByteBuffer bb = ByteBuffer.allocate(1024);
sc.read(bb);
bb.flip();
while (bb.hasRemaining()) {
System.out.print((char) bb.get());
}
// 关闭操作
selector.close();
channel.close();
// 退出轮询方法
return;
}
}
// key.clear();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
例子二:展示SocketChannel和FileChannel的使用
Server:
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
// ~ File Information
// ====================================================================================================================
//套接字通道 -->本地文件通道
public class UpLoadServer {
public static void main(String[] args) {
try {
// 开启服务
ServerSocketChannel ssc = ServerSocketChannel.open();
// bind端口号
ssc.socket().bind(new InetSocketAddress(10234));
// 是否阻塞
ssc.configureBlocking(true);
while (true) {
System.out.println("等待写入》》》》》》》》》》");
// 等待接收
SocketChannel sc = ssc.accept();
if (sc != null) {
// 写出文件(本地文件通道)
FileChannel fc = new FileOutputStream(
"C:\\Users\\Administrator\\Desktop\\BM_HOLIDAY_APPLY1.jpg").getChannel();
int len = 0;
// 设置缓冲区容量
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 将网络通道内容写入此缓冲区
while ((len = sc.read(buffer)) > 0) {
// 反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。
buffer.flip();
// 从缓冲区写到本地文件
fc.write(buffer);
// 清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。
buffer.clear();
}
sc.close();
fc.close();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
// ~ File Information
// ====================================================================================================================
//本地文件通道-->套接字通道
public class UpLoadClient {
public static void main(String[] args) {
try {
//获取连接
SocketChannel sc = SocketChannel.open(new InetSocketAddress("localhost", 10234));
//设置为阻塞
sc.configureBlocking(true);
//拿到文件通道
FileChannel fc= new FileInputStream(
"E:\\文本+图片\\joker.jpg")
.getChannel();
// 设置缓冲区大小
ByteBuffer buffer = ByteBuffer.allocate(1024);
int ind;
// public abstract int read(ByteBuffer dst) throws IOException
// 将字节序列从此通道中读入给定的缓冲区。
while((ind=fc.read(buffer))>0){
// 反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。
buffer.flip();
// public abstract int write(ByteBuffer src) throws IOException 从接口 WritableByteChannel 复制的描述
// 将字节序列从给定的缓冲区中写入此通道。
sc.write(buffer);
// public final Buffer clear()
// 清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。
buffer.clear();
}
sc.close();
fc.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
以上两个例子简单介绍了NIO中代码的具体使用,如果想深入了解,请查看JDK帮助手册