java的NIO
java的NIO主要有3个特性Channel、buffer、selector来保证I/O高可复用性,其中最重要的是buffer和selector操作。详细教材查看 jakob jenkov教材:http://tutorials.jenkov.com/java-nio/index.html
1、channel和buffer
a、 channel:有点像流的管道,NIO从channel里面获取、发送数据。java的I/O已经从底层被NIO实现了一次,所以性能上和纯粹的NIO中使用channel没有太大的区别。
channel的类型主要就下面几种:
FileChannel //文件
DatagramChannel //UDP
SocketChannel //socket
ServerSocketChannel //socket服务
它有2个特点:1、读写控制、2、按着单个byte位来操作。 b、 Buffer:就是在内存开辟的空间,用来临时存放数据。这里的buffer是以bytebuffer为父类实现的HeapByteBuffer。
一个Buffer有3个中有的参数:
position:当前位置
1、写:从当前可以写的地址开始(第一次从0开始),随着写入的增大。写的时候最大为capacity-1。
2、读:从0开始、随着读开始移动增大。最大读取到limit
limit:写模式下为capacity。当转换为读模式,则limit=position(写入的个数)
capacity:一个buffer的固定大小。
如下示意图
例子:
#有一个Buffer是70字节
1、buffer.allocate(70):capacity=70、position=0、limit=70
1、写如40个字节:capacity=70、position=40、limit=70
2、写转化为读:buffer.flip();capacity=70,position=0,limit=40
3、读30字节:capacity=70、position=30、limit=40
4、读转写:
buffer.clear():所有剩余数据都清空。capacity=70,position=0,limit=70
buffer.compact():将剩余的所有数据复制到buffer起始。capacity=70,position=10,limit=70
一个fileChannel的例子
public static void main(String[] args) {
try {
RandomAccessFile aFile = new RandomAccessFile("D:/project/test/nio/1.txt", "rw");
FileChannel fileChannel = aFile.getChannel();
// buffer
ByteBuffer buf = ByteBuffer.allocate(48);
while (fileChannel.read(buf) != -1) {
buf.flip();// 写模式切换成读模式
while (buf.hasRemaining()) {
System.out.println(buf.get());
}
buf.clear();
}
aFile.close();//从写切换到读
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
2、零复制
buffer默认是在JVM开辟空间、而NIO比BIO在数据处理方面有一个有点:不会将数据从逻辑主存复制到JVM主存
bytebuffer开辟空间的两个方法。第二个方法直接在主存开辟空间、不需要在JVM中操作
直接从内核态的数据区读取数据,不用在copy到jvm堆内存。
多个数据Buffer不用组合成一个,直接就程序处理了。
发送的时候,直接发送。
//直接在JVM开辟空间 public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity); }
//直接在内存开辟空间 public static ByteBuffer allocateDirect(int capacity) { return new DirectByteBuffer(capacity); }
3、Slector
普通的I/O调用都会阻塞等待,直到文件数据准备就行才能使用。而NIO则是通过一个单独的线程不对的去询问系统I/O数据是否准备好了。准备好后,就可以通过存放在Selector线程中的key(处理线程的引用)来处理。通过这种主动启动线程的方式,避免了掉多线程同时启动,通过CPU切换切换询问状态的方式,节约了CPU的开销。
a、开启Selector
Selector selector = Selector.open();
b、注册channel到selector
channel.configureBlocking(false);//设置非阻塞。也就是说不能和FileChannel一起使用了
SelectionKey key=channel.register(selector,SelecotionKey.OP_READ);
事件类型
注册类型
类型判断
监听:accept(服务器)
SelectionKey.OP_ACCEPT
SelctionKey.isAcceptable()
连接:connect(客服端、服务器)
SelectionKey.OP_CONNECT
SelctionKey.isConnectable()
读:read(客服端、服务器)
SelectionKey.OP_READ
SelctionKey.isReadable()
写:write(客服端、服务器)
SelectionKey.OP_WRITE
SelctionKey.isWritable()
**SelectionKey:**是channel在selector上的注册标签。当I/O事件准备好的时候,就会返回需要事件类型:
selectionKey可以获取channel、selector,以及添加和获取附加对象
//这个就是获取channel、处理数据的方式。
Channel channel = selectionKey.channel();
//这个就是获取selector,用来处理完事件后重新注册
Selector selector = selectionKey.selector();
//添加、获取附加对象。
selectionKey.attch(theObject);
Object attachObj = selectionKey.attachment();
c、从selector中获取事件 (slectionKey可以看作是一个存放channel、附加对象的容,和我们每次注册到selector中需要处理的事件方式。形成了一个映射关系。只要事件达成我们就可以继续处理)
while(true){
//第一步:获取事件,只有当有事件处理的时候,selecotr会返回一个大于0的值
int readyEvents = selector.select();
if(readyEvents==0) continue;
//第二步:获取事件标签
Set<SelectionKey> keys = slector.slectionKeys();
//第三步:处理事件
Iterator keyIteraotrs = keys.interator();
while(keyIterators.hasNext()){
SelectionKey selectionKey = keyIterators.next();
//当获取事件的时候,需要从selector删掉。
keys.remove(selectionKey);
if(selectionKey.isAcceptable()){
//do something 。。。
//注册
}else if(selectionKey.isConnectable()){
//do something 。。。
//注册
}else if(selectionKey.isReadable()){
//do something 。。。
//注册
}else if(selectionKey.isWritable()){
//do something 。。。
//注册
}
}
}
a、文件4、使用
try {
RandomAccessFile fromFile = new RandomAccessFile("D:/project/test/nio/1.txt", "rw");
FileChannel fromFileChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("D:/project/test/nio/2.txt", "rw");
FileChannel toFileChannel = toFile.getChannel();
//不同channel的数据传送
toFileChannel.transferFrom(fromFileChannel, 0, fromFileChannel.size());
// buffer
// ByteBuffer buf = ByteBuffer.allocate(48);
// while (fromFileChannel.read(buf) != -1) {
// // buf.flip();// 写模式切换成读模式
// while (buf.hasRemaining()) {
// System.out.println(buf.getChar());
// }
// // buf.clear();// 从写切换到读
// }
fromFile.close();
toFile.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
b、serverSocket
// 1.开启Selector
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2、设置channel的模式(阻塞-false、非阻塞-true)
serverSocketChannel.socket().bind(new InetSocketAddress(80));
serverSocketChannel.configureBlocking(false);
// 2、注册channel到Selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int readyChannel = selector.select();
if (readyChannel == 0)
continue;
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterators = keys.iterator();
while (keyIterators.hasNext()) {
SelectionKey selectionKey = keyIterators.next();
keys.remove(selectionKey);
SocketChannel socketChannel = null;
if (selectionKey.isAcceptable()) {
// 访问事件(这里需要获取的serverSocketChannel)
serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,
SelectionKey.OP_WRITE | SelectionKey.OP_READ | SelectionKey.OP_CONNECT);
} else if (selectionKey.isConnectable()) {
socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.configureBlocking(false);
ByteBuffer buff = ByteBuffer.allocateDirect(1024);
while (socketChannel.read(buff) != -1) {
buff.flip();
while (buff.hasRemaining()) {
System.out.println(buff.getChar());
}
buff.clear();
}
buff=null;
socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_WRITE);
} else if (selectionKey.isWritable()) {
socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.configureBlocking(false);
ByteBuffer buff = ByteBuffer.allocateDirect(1024);
buff.put(new String("hello , i am Nio !").getBytes());
buff.flip();
socketChannel.write(buff);
buff=null;
socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ);
}
}
}
c、socket
// 1.开启Selector
Selector selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
// 2、设置channel的模式(阻塞-false、非阻塞-true)
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://localhost/", 80));
// 2、注册channel到Selector
socketChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
// 返回事件
int readyChannels = selector.select();
if (readyChannels == 0)
continue;
// 有事件发生
// 返回的key集合(返回的是一个channel集合)
Set<SelectionKey> keys = selector.selectedKeys();
// 对每一个channel进行处理
Iterator<SelectionKey> keyIterators = keys.iterator();
while (keyIterators.hasNext()) {
SelectionKey selectionKey = keyIterators.next();
keys.remove(selectionKey);
// 连接发生了
if (selectionKey.isConnectable()) {
// 需要将key(channel)移除、因为selector是条件触发,如果不删除。下次事件来了会发生问题
// 从key中获取channel
socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.configureBlocking(false);
// 重新注册(前面删除了注册)
socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 读取数据
socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.configureBlocking(false);
ByteBuffer buff = ByteBuffer.allocateDirect(1024);
while (socketChannel.read(buff) != -1) {
buff.flip();
while (buff.hasRemaining()) {
System.out.println(buff.getChar());
}
buff.clear();
}
buff=null;
socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_WRITE);
} else if (selectionKey.isWritable()) {
// 写入数据
socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.configureBlocking(false);
ByteBuffer buff = ByteBuffer.allocateDirect(1024);
buff.put(new String("hello , i am Nio !").getBytes());
buff.blip();
socketChannel.write(buff);
buff=null;
socketChannel.register(selector, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ);
}
}
}
d、Pipe:两个线程之间的数据传送。传送用sink通道、接受用source通道
public void writeToPipeChannel() throws IOException {
Pipe pipe = Pipe.open();
Pipe.SinkChannel sinkChannel = pipe.sink();
String newData = "New String to write to file ... " + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while (buf.hasRemaining()) {
sinkChannel.write(buf);
}
}
public void readFromPipeChannel() throws IOException {
Pipe pipe = Pipe.open();
Pipe.SourceChannel sourceChannel = pipe.source();
ByteBuffer buf = ByteBuffer.allocateDirect(48);
buf.clear();
while (sourceChannel.read(buf) != -1) {
buf.flip();
while (buf.hasRemaining()) {
System.out.println(buf.getChar());
}
buf.clear();
}
}