NIO学习二、NIO的基本使用
这是作为学习NIO的总结,如有不对,请大佬指出。
一、基本操作(这些操作不会的时候查文档就行)
从一个buffer中读写到另一个buffer
@Test
public void bufferTest1(){
try {
RandomAccessFile readAccessFile=new RandomAccessFile("C:\\Users\\e550c\\Desktop\\日结.txt","rw");
RandomAccessFile writeAccessFile=new RandomAccessFile("C:\\Users\\e550c\\Desktop\\temp.txt","rw");
FileChannel readChannel=readAccessFile.getChannel();
FileChannel writeChannel=writeAccessFile.getChannel();
//将一个channel数据送入另一个channel(这样做可以实现文件的写入)
//与之相对的是transferFrom
readChannel.transferTo(0,readAccessFile.length(), writeChannel);
readAccessFile.close();
writeAccessFile.close();;
} catch (FileNotFoundException e) {
} catch (IOException e) {
e.printStackTrace();
}
}
其他常见的:
Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。
mark()与reset()方法 通过调用Buffer.mark()方法,可以标记Buffer中的
一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。
二、Scatter与Gather
scatter:是将channel中的数据写入到多个buffer中,先写满第一个,然后写入下一个:
Gater:将多个buffer数据先聚集在一起,然后写入到指定的buffer中。
try {
ByteBuffer byteBuffer1=ByteBuffer.allocate(10);
ByteBuffer byteBuffer2=ByteBuffer.allocate(10);
RandomAccessFile randomAccessFile=new RandomAccessFile("C:\\Users\\e550c\\Desktop\\temp.txt","rw");
byteBuffer1.clear();
byteBuffer2.clear();
byteBuffer1.put("hello".getBytes());
byteBuffer2.put("OK,world".getBytes());
byteBuffer1.flip();
byteBuffer2.flip();
FileChannel fileChannel=randomAccessFile.getChannel();
//将多个byteBuffer聚集在一起写入
fileChannel.write(new ByteBuffer[]{byteBuffer1,byteBuffer2});
byteBuffer1.clear();
byteBuffer2.clear();
fileChannel.position(0L);
//先写满第一个,第一个满后再写入第二个
fileChannel.read(new ByteBuffer[]{byteBuffer1,byteBuffer2});
byteBuffer1.flip();
while(byteBuffer1.hasRemaining()){
System.out.print((char)byteBuffer1.get());
}
System.out.println();
byteBuffer2.flip();
while(byteBuffer2.hasRemaining()){
System.out.print((char)byteBuffer2.get());
}
fileChannel.close();
randomAccessFile.close();
}catch (Exception e){
e.printStackTrace();;
}
三、非阻塞IO操作
所有的非阻塞IO的channel都实现了SelectableChannel接口,而FileChannle没有实现这个接口,所以它不支持非阻塞IO
以一个ServerSocketChannel与SocketChannel为例子来演示:不懂的话可以先看看java网络编程。
服务端代码:
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 1234));//监听1234端口
serverSocketChannel.configureBlocking(false);//这样配置后就是同步非阻塞的
SocketChannel socketChannel = null;
while ((socketChannel = serverSocketChannel.accept()) == null) {//如果没有客户端连接的话立即返回
System.out.println("try to Linking:"); //不会阻塞
TimeUnit.SECONDS.sleep(1);
}
ByteBuffer byteBuffer1 = ByteBuffer.allocate(10);
ByteBuffer byteBuffer2 = ByteBuffer.allocate(10);
socketChannel.read(new ByteBuffer[]{byteBuffer1, byteBuffer2});
byteBuffer1.flip();//回到初始位置
byteBuffer2.flip();
while (byteBuffer1.hasRemaining()) {
System.out.println((char) byteBuffer1.get());
}
System.out.println("-----------------");
while (byteBuffer2.hasRemaining()) {
System.out.println((char) byteBuffer2.get());
}
socketChannel.close();//读取完之后手动关闭这个连接
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
客户端代码
SocketChannel socketChannel=null;
try {
socketChannel=SocketChannel.open();//打开socket
socketChannel.connect(new InetSocketAddress("127.0.0.1",1234));//连接上指定服务端口
ByteBuffer byteBuffer1=ByteBuffer.allocate(12);
ByteBuffer byteBuffer2=ByteBuffer.allocate(12);
byteBuffer1.put("hello,world".getBytes());
byteBuffer1.flip();
byteBuffer2.put("hello,next".getBytes());
byteBuffer2.flip();
socketChannel.write(byteBuffer1);
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
四、Piple管道操作:
Java NIO管道是两个线程之间单项的数据连接。Piple有一个source通道和一个sink通道。数据会被写到sink通道,从source通道中读取。
/*
* Piple进行管道
* */
@Test
public void test16(){
try {
//创建一个piple通道
Pipe pipe=Pipe.open();
//创建一个写通道
WritableByteChannel writableByteChannel=pipe.sink();
//c创建一个读通道,从读通道中获取数据
ReadableByteChannel readableByteChannel=pipe.source();
//创建一个线程从sink写入数据
WorkerThread workerThread=new WorkerThread(writableByteChannel);
workerThread.start();
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
while(readableByteChannel.read(byteBuffer)>=0){
byteBuffer.flip();
byte[] bytes=new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String str=new String(bytes);
System.out.println(str);
byteBuffer.clear();
}
readableByteChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static class WorkerThread extends Thread{
WritableByteChannel writableByteChannel;
public WorkerThread(WritableByteChannel writableByteChannel){
this.writableByteChannel=writableByteChannel;
}
@Override
public void run() {
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
for (int i=0;i<10;i++){
String str="piple sink data"+i;
byteBuffer.put(str.getBytes());
byteBuffer.flip();
try {
writableByteChannel.write(byteBuffer);
} catch (IOException e) {
e.printStackTrace();
}
byteBuffer.clear();
}
try{
writableByteChannel.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
五、Selector操作(最重要的,压轴的&&&)
channle与selector适配使用的,创建了非阻塞的channel之后,注册到selector中,返
回注册的SelectionKey,并设置感兴趣的事件,也就是说当这些事件发生的时候,对应的
Channel就准备就绪,可以使用。如果没有发生,那么就等待。
ps:有时候感觉channel出现很突兀,是因为selector 直接管理 Java Socket 很难实现
,所以使用channel做一次封装与之适配。这一切都是为了selector而存在的。
如下所示:
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
感兴趣的事件是通过SelectionKey来获得:有如下四种:
SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,
服务器可以接收这个连接了
SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功
SelectionKey.OP_READ--读就绪事件,表示可以读了(通道目前有数据,可以进行读操作了)
SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可
以用于写操作)
操作了(通道目前有数据,可以进行读操作了)
selector中两个重要的函数:
selector.select():返回当前就绪的channel数量,如果没有就绪channel,那么它就
阻塞,直到就绪channel将它唤醒。
其他的select():
select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
selectNow()不会阻塞,不管什么通道就绪都立刻返回(此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,
则此方法直接返回零)。
selector.SelectionKey():返回已经准备就绪的channel注册标记的set集合,单个
SelectionKey通过channel()为此对应的SelectionKey创建通道.其他操作查文档就好了。
呼呼~~~~接下来是一个例子:仍然以ServerSocketChannel与SocketChannel为例子:
//服务端:
try {
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",1234));
//设置非阻塞状态
serverSocketChannel.configureBlocking(false);
//创建一个Selector
Selector selector=Selector.open();
//将serverSocketChannel注册到selecor中,它的返回值为对应的SelectionKey
//它对ACCPET事件感兴趣,也就是说当客户端连接的时候,它准备就绪,可以被Selector调用
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
//分配空间
ByteBuffer byteBuffer=ByteBuffer.allocate(128);
while(true){
//如果没有就绪事件,它就阻塞
int n=selector.select();
//获取就绪的channel对应的注册标记集合
Set<SelectionKey> set=selector.selectedKeys();
Iterator<SelectionKey> iterator=set.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey=iterator.next();
//注意当得到一个SelectionKey之后必须移除它,不然会陷入死循环
iterator.remove();
if (selectionKey.isAcceptable()){
//触发accpet之后,它会将连接的SocketChannel注册到register中
//它感兴趣的事件是读事件,当通道中有数据之后,它准备就绪
//SocketChannel必须是同步非阻塞的
SocketChannel socketChannel=serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
}else if (selectionKey.isReadable()){
SocketChannel socketChannel= (SocketChannel) selectionKey.channel();
byteBuffer.clear();
socketChannel.read(byteBuffer);
byteBuffer.flip();
while(byteBuffer.hasRemaining()){
System.out.print((char)byteBuffer.get());
}
System.out.println();
socketChannel.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
//客户端
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 1234));
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("hello,world".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
另外:针对UDP的数据传输,文件锁等等等,不是全部能写完的,多看文档,多练~~