IO流BIO、NIO、AIO
一.BIO、NIO、AIO
1.BIO、NIO与AIO概述
1).BIO:Block(阻塞的) IO 【同步、阻塞】
2).NIO:Non-Block(非阻塞的(同步)IO——JDK1.4开始的。 【同步、非阻塞】
3).AIO:Asynchronous(异步-非阻塞)IO——JDK1.7开始 【异步、非阻塞】
2.阻塞和非阻塞,同步和异步的概念
举个例子,比如我们去照相馆拍照,拍完照片之后,商家说需要30分钟左右才能洗出来照片
-
同步+阻塞
这个时候如果我们一直在店里面啥都不干,一直等待商家把它洗完照片,这个过程就叫同步阻塞。
-
同步+非阻塞
当然大部分人很少这么干,更多的是大家拿起手机开始看电视,看一会就会问老板洗完没,老板说没洗完,然后我们接着看,再过一会接着问(轮询),直到照片洗完,这个过程就叫同步非阻塞。
-
异步+阻塞
因为店里生意太好了,越来越多的人过来拍,店里面快没地方坐了,老板说你把你手机号留下,我一会洗好了就打电话告诉你过来取,然后你去外面找了一个长凳开始躺着睡觉等待老板打电话,啥不都干,这个过程就叫异步阻塞。
-
异步+非阻塞
当然实际情况是,大家可能会直接先去逛街或者吃饭做其他的活动,同时等待老板打电话,这样以来两不耽误,这个过程就叫异步非阻塞。
总结
从上面的描述中我们其实能够看到阻塞和非阻塞通常是指客户端在发出请求后,在服务端处理这个请求的过程中,客户端本身是否直接挂起等待结果(阻塞),还是继续做其他的任务(非阻塞)。
而异步和同步,则是对于请求结果的获取是客户端主动等待获取(同步),还是由服务端来通知消息结果(异步)。
从这一点来看同步和阻塞其实描述的两个不同角度的事情,阻塞和非阻塞指的一个是客户端等待消息处理时的本身的状态,是挂起还是继续干别的。同步和异步指的对于消息结果的获取是客户端主动获取,还是由服务端间接推送。
阻塞:等待结果,什么事都不能做
非阻塞:可以做别的事情
同步:主动获取结果
异步:等待通知结果
NIO之所以是同步,是因为它的accept/read/write方法的内核I/O操作都会阻塞当前线程
首先,我们要先了解一下NIO的三个主要组成部分:Buffer(缓冲区)、Channel(通道)、Selector(选择器)
二.Buffer类(缓冲区)
1.Buffer概述
-
java.nio.Buffer(抽象类):用于特定原始类型(基本类型)的数据的容器。后期在会用Channel进行通信时,底层全部使用Buffer。
-
它的几个子类:
1.ByteBuffer:里面可以封装一个byte[]数组。【重点掌握】
2.ShortBuffer:里面可以封装一个short[]数组。
3.CharBuffer:里面可以封装一个char[]数组
4.IntBuffer:里面可以封装一个int[]数组。
5.LongBuffer:里面可以封装一个long[]数组。
6.FloatBuffer:里面可以封装一个float[]数组。
7.DoubleBuffer:里面可以封装一个double[]数组。没有boolean类型对应的Buffer
2.创建ByteBuffer
- 没有构造方法可以创建ByteBuffer,可以通过它的一些“静态方法”获取ByteBuffer对象。
- 常用三个静态方法: new byte[10]; 默认值 0,0,0...0
- public static ByteBuffer allocate(int capacity):使用一个“容量”来创建一个“间接字节缓存区”——程序的“堆”空间中创建。
- public static ByteBuffer allocateDirect(int capacity):使用一个“容量”来创建一个“直接字节缓存区”——系统内存。
- public static ByteBuffer wrap(byte[] byteArray):使用一个“byte[]数组”创建一个“间接字节缓存区”。
- 代码演示
点击查看代码
import java.nio.ByteBuffer;
import java.util.Arrays;
/*
创建ByteBuffer的方式:使用静态方法(抽象类)
- public static ByteBuffer allocate(int capacity):使用一个“容量”来创建一个“间接字节缓存区”——程序的“堆”空间中创建。
- public static ByteBuffer allocateDirect(int capacity):使用一个“容量”来创建一个“直接字节缓存区”——系统内存。 {1,2,3,4,5}
- public static ByteBuffer wrap(byte[] byteArray):使用一个“byte[]数组”创建一个“间接字节缓存区”。
*/
public class Demo01ByteBuffer {
public static void main(String[] args) {
ByteBuffer buffer1 = ByteBuffer.allocate(10);//创建一个指定长度的ByteBuffer,包含默认值[0,0,0,...0]==>间接字节缓冲区(堆内存中)
System.out.println(buffer1);//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);////创建一个指定长度的ByteBuffer,包含默认值[0,0,0,...0]==>直接字节缓冲区(系统内存中)
System.out.println(buffer2);//java.nio.DirectByteBuffer[pos=0 lim=10 cap=10]
byte[] bytes = "你好".getBytes();
System.out.println(Arrays.toString(bytes));//[-28, -67, -96, -27, -91, -67]
ByteBuffer buffer3 = ByteBuffer.wrap(bytes);//创建一个包含指定元素的ByteBuffer==>间接字节缓冲区(堆内存中)
System.out.println(buffer3);//java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]
}
}
点击查看代码
import java.nio.ByteBuffer;
import java.util.Arrays;
/*
向ByteBuffer添加数据
- public ByteBuffer put(byte b):向当前可用位置添加数据。
- public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
- public ByteBuffer put(byte[] byteArray,int offset,int len):添加一个byte[]数组的一部分
- byte[] array()获取此缓冲区的 byte 数组
*/
public class Demo02put {
public static void main(String[] args) {
//创建一个长度为10ByteBuffer对象
ByteBuffer buffer = ByteBuffer.allocate(10);
//使用array方法,取出ByteBuffer中取出的byte数组
byte[] bytes = buffer.array();
System.out.println(Arrays.toString(bytes));//[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
//public ByteBuffer put(byte b):向当前可用位置添加数据。
buffer.put((byte)10);
buffer.put((byte)20);
buffer.put((byte)30);
System.out.println(Arrays.toString(buffer.array()));//[10, 20, 30, 0, 0, 0, 0, 0, 0, 0]
//public ByteBuffer put(byte[] byteArray):向当前可用位置添加一个byte[]数组
byte[] bytes1 = {11,12,13,14};
buffer.put(bytes1);
System.out.println(Arrays.toString(buffer.array()));//[10, 20, 30, 11, 12, 13, 14, 0, 0, 0]
/*
public ByteBuffer put(byte[] byteArray,int offset,int len):添加一个byte[]数组的一部分
int offset:数组的开始索引
int len:添加个数
*/
buffer.put(bytes1,0,2);
System.out.println(Arrays.toString(buffer.array()));//[10, 20, 30, 11, 12, 13, 14, 11, 12, 0]
}
}
点击查看代码
import java.nio.ByteBuffer;
import java.util.Arrays;
/*
获取-get
byte get(int index) 获取ByteBuffer中指定索引处的元素
索引的范围:[0,长度-1]
*/
public class Demo03get {
public static void main(String[] args) {
//创建一个长度为10的ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte)10);
buffer.put((byte)11);
buffer.put((byte)12);
buffer.put((byte)13);
buffer.put((byte)14);
buffer.put((byte)15);
System.out.println(Arrays.toString(buffer.array()));//[10, 11, 12, 13, 14, 15, 0, 0, 0, 0]
System.out.println(buffer.get(0));//10
System.out.println(buffer.get(1));//11
System.out.println(buffer.get(9));//0
//System.out.println(buffer.get(10));//IndexOutOfBoundsException
}
}
点击查看代码
import java.nio.ByteBuffer;
/*
容量-capacity
- Buffer的容量(capacity)是指:Buffer所能够包含的元素的最大数量。定义了Buffer后,容量是不可变的。
int capacity() 返回此缓冲区的容量。
*/
public class Demo04capacity {
public static void main(String[] args) {
//创建一个长度为10的ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("容量:"+buffer.capacity());//容量:10
//创建一个包含指定元素的ByteBuffer
ByteBuffer buffer2 = ByteBuffer.wrap("你好吗".getBytes());
System.out.println("容量:"+buffer2.capacity());//容量:9
}
}
点击查看代码
import java.nio.ByteBuffer;
import java.util.Arrays;
/*
限制-limit
- 限制:limit:表示如果设置“限制为某一个位置,那么此位置后的位置将不可用”。
- 有两个相关方法:
- public int limit():获取此缓冲区的限制。
- public Buffer limit(int newLimit):设置此缓冲区的限制。
*/
public class Demo05limit {
public static void main(String[] args) {
//创建一个长度为10ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("容量:"+buffer.capacity()+",限制:"+buffer.limit());//容量:10,限制:10
//设置ByteBuffer的限制为3(3和3以后的索引不能在使用了)
buffer.limit(3);
System.out.println("容量:"+buffer.capacity()+",限制:"+buffer.limit());//容量:10,限制:3
buffer.put((byte)10);
buffer.put((byte)20);
buffer.put((byte)30);
System.out.println(Arrays.toString(buffer.array()));//[10, 20, 30, 0(limit), 0, 0, 0, 0, 0, 0]
//buffer.put((byte)40);//BufferOverflowException 超出缓冲区范围异常
}
}
点击查看代码
import java.nio.ByteBuffer;
import java.util.Arrays;
/*
位置-position
- 位置position是指:当前可写入的索引。位置不能小于0,并且不能大于"限制"。
- 有两个相关方法:
- public int position():获取当前可写入位置索引。
- public Buffer position(int p):更改当前可写入位置索引。
*/
public class Demo06position {
public static void main(String[] args) {
//创建一个长度为10ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(Arrays.toString(buffer.array()));//[0(p), 0, 0, 0, 0, 0, 0, 0, 0, 0]
System.out.println("索引位置:"+ buffer.position());//索引位置:0
buffer.put((byte)10);//往0索引处添加元素
buffer.put((byte)20);//往1索引处添加元素
buffer.put((byte)30);//往2索引处添加元素
System.out.println(Arrays.toString(buffer.array()));//[10, 20, 30, 0(p), 0, 0, 0, 0, 0, 0]
System.out.println("索引位置:"+ buffer.position());//索引位置:3
//修改position的位置
buffer.position(1);
System.out.println(Arrays.toString(buffer.array()));//[10, 20(p), 30, 0, 0, 0, 0, 0, 0, 0]
System.out.println("索引位置:"+ buffer.position());//索引位置:1
buffer.put((byte)66);
buffer.put((byte)77);
buffer.put((byte)88);
System.out.println(Arrays.toString(buffer.array()));//[10, 66, 77, 88, 0(p), 0, 0, 0, 0, 0]
System.out.println("索引位置:"+ buffer.position());//索引位置:4
}
}
点击查看代码
import java.nio.ByteBuffer;
/*
还原-clear 把ByteBuffer还原为初始状态
- public Buffer clear():还原缓冲区的状态。
- 将position设置为:0
- 将限制limit设置为容量capacity;
*/
public class Demo07clear {
public static void main(String[] args) {
//创建一个长度为10ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("索引:"+buffer.position()+",限制:"+buffer.limit());//索引:0,限制:10
buffer.put((byte)10);//往0索引处添加元素
buffer.put((byte)20);//往1索引处添加元素
buffer.put((byte)30);//往2索引处添加元素
//设置limit为5
buffer.limit(5);
System.out.println("clear前索引:"+buffer.position()+",限制:"+buffer.limit());//索引:3,限制:5
buffer.clear();
System.out.println("clear前索引:"+buffer.position()+",限制:"+buffer.limit());//索引:0,限制:10
}
}
- 将limit设置为当前position位置; [10, 11, 12, 0, 0, 0, 0, 0, 0, 0] position=3 limit=10
- 将当前position位置设置为0; position=0 limit=3 new String(bytes,0,len)
点击查看代码
import java.nio.ByteBuffer;
import java.util.Arrays;
/*
创建ByteBuffer的方式:使用静态方法(抽象类)
- public static ByteBuffer allocate(int capacity):使用一个“容量”来创建一个“间接字节缓存区”——程序的“堆”空间中创建。
- public static ByteBuffer allocateDirect(int capacity):使用一个“容量”来创建一个“直接字节缓存区”——系统内存。 {1,2,3,4,5}
- public static ByteBuffer wrap(byte[] byteArray):使用一个“byte[]数组”创建一个“间接字节缓存区”。
*/
public class Demo01ByteBuffer {
public static void main(String[] args) {
ByteBuffer buffer1 = ByteBuffer.allocate(10);//创建一个指定长度的ByteBuffer,包含默认值[0,0,0,...0]==>间接字节缓冲区(堆内存中)
System.out.println(buffer1);//java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);////创建一个指定长度的ByteBuffer,包含默认值[0,0,0,...0]==>直接字节缓冲区(系统内存中)
System.out.println(buffer2);//java.nio.DirectByteBuffer[pos=0 lim=10 cap=10]
byte[] bytes = "你好".getBytes();
System.out.println(Arrays.toString(bytes));//[-28, -67, -96, -27, -91, -67]
ByteBuffer buffer3 = ByteBuffer.wrap(bytes);//创建一个包含指定元素的ByteBuffer==>间接字节缓冲区(堆内存中)
System.out.println(buffer3);//java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]
}
}
三.Channel(通道)
1.Channel概述
1).java.nio.channels.Channel(接口):用于 I/O 操作的连接。
- Channel表示:通道。
- 可以是“文件通道-FileChannel”、“网络通道-SocketChannel和ServerSockecChannel”。
- 它类似于IO流,但比IO流更强大。read(byte[]) write(byte[])
- IO流是“单向”的,Channel是“双向的”。
2).Channel全部使用Buffer实现读、写。read(ByteBuffer) write(ByteBuffer)
2.ServerSocketChannel和SocketChannel创建连接
同步阻塞实现
点击查看代码
1.实现同步阻塞的服务器
相关的类:
java.nio.channels.ServerSocketChannel:用于面向流的侦听套接字的可选通道。
获取对象的方式:使用静态方法open
static ServerSocketChannel open() 打开服务器插槽通道。
成员方法:
ServerSocketChannel bind(SocketAddress local) 给服务器绑定指定的端口号,直接new
SocketChannel accept() 监听客户端的请求
SelectableChannel configureBlocking(boolean block) 设置服务器的阻塞模式 true:阻塞(不写默认是阻塞的) false:非阻塞
2.实现同步阻塞的客户端
相关的类:
java.nio.channels.SocketChannel:用于面向流的连接插座的可选通道。
获取对象的方法:使用静态方法open
static SocketChannel open() 打开套接字通道。
成员方法:
boolean connect(SocketAddress remote) 根据服务器的ip地址和端口号连接服务器
参数:
SocketAddress remote:封装服务器的ip地址和端口号,用的时候直接new
返回值:boolean
连接服务器成功:true
连接服务器失败:false,或者抛出连接异常
SelectableChannel configureBlocking(boolean block) 设置客户端的阻塞模式
true:阻塞(不写默认) false:非阻塞
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
实现同步阻塞的服务器
实现步骤:
1.使用ServerSocketChannel类中的方法open,获取服务器ServerSocketChannel对象
2.使用ServerSocketChannel对象中的方法bind,绑定指定的端口号
3.使用ServerSocketChannel对象中的方法accpet,监听并获取客户端SocketChannel对象
4.释放资源
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.使用ServerSocketChannel类中的方法open,获取服务器ServerSocketChannel对象
ServerSocketChannel server = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind,绑定指定的端口号
server.bind(new InetSocketAddress(8888));
//3.使用ServerSocketChannel对象中的方法accpet,监听并获取客户端SocketChannel对象
System.out.println("服务器已经启动,等待客户端连接...");
SocketChannel socket = server.accept();//accpet方法默认是阻塞的
System.out.println(socket);
System.out.println("有客户端连接服务器...");
//4.释放资源
socket.close();
server.close();
}
}
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
实现同步阻塞的客户端
实现步骤:
1.使用SocketChannel类中的方法open,获取客户端SocketChannel对象
2.使用SocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器(3次握手)
3.释放资源
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
//1.使用SocketChannel类中的方法open,获取客户端SocketChannel对象
SocketChannel socket = SocketChannel.open();
//2.使用SocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器(3次握手)
boolean b = socket.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(b);
//3.释放资源
socket.close();
}
}
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
服务器同步非阻塞实现
configure:设置,配置
Block:阻塞
SelectableChannel configureBlocking(boolean block) 设置服务器的阻塞模式
true:阻塞(不写默认是阻塞的) false:非阻塞
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用ServerSocketChannel类中的方法open,获取服务器ServerSocketChannel对象
ServerSocketChannel server = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind,绑定指定的端口号
server.bind(new InetSocketAddress(8888));
//设置服务器为非阻塞模式
server.configureBlocking(false);
//创建一个死循环,轮询accpet方法,一直监听客户端请求
while (true){
//3.使用ServerSocketChannel对象中的方法accpet,监听并获取客户端SocketChannel对象
System.out.println("服务器已经启动,等待客户端连接...");
SocketChannel socket = server.accept();
//增加一个非空判断
if(socket!=null){
System.out.println(socket);//null
System.out.println("有客户端连接服务器,结束轮询...");
socket.close();
break;
}
//增加一个睡眠
System.out.println("增加一个睡眠,睡眠2秒钟(干点其他事情),再次轮询获取客户端的请求!");
Thread.sleep(2000);
}
//4.释放资源
server.close();
}
}
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
实现同步非阻塞的客户端
SelectableChannel configureBlocking(boolean block) 设置客户端的阻塞模式
true:阻塞(不写默认) false:非阻塞
设置客户端为阻塞的:
connect方法连接服务器成功,返回true
connect方法连接服务器失败,抛出连接异常ConnectException
设置客户端为非阻塞的:
connect方法连接服务器成功,返回false
connect方法连接服务器失败,返回false
*/
public class TCPClient {
public static void main(String[] args) throws InterruptedException {
//添加一个死循环,让客户端轮询连接服务器
while (true){
try {
//1.使用SocketChannel类中的方法open,获取客户端SocketChannel对象
SocketChannel socket = SocketChannel.open();
//设置客户端为非阻塞模式:没有意义,区分不出来,到底是否连接服务器成功还是失败
//socket.configureBlocking(false);
//2.使用SocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器(3次握手)
boolean b = socket.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(b);
//3.释放资源
socket.close();
System.out.println("客户端连接服务器成功,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端连接服务器失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...");
Thread.sleep(2000);
}
}
}
}
点击查看代码
客户端连接服务器失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...
客户端连接服务器失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...
客户端连接服务器失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...
客户端连接服务器失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...
true
客户端连接服务器成功,结束轮询...
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/*
客户端给服务器发送数据,读取服务器回写的数据
使用SocketChannel类中的方法
int write(ByteBuffer src) 给服务器发送数据
int read(ByteBuffer dst) 读取服务器发送的数据
*/
public class TCPClient {
public static void main(String[] args) throws InterruptedException {
//添加一个死循环,让客户端轮询连接服务器
while (true){
try {
//1.使用SocketChannel类中的方法open,获取客户端SocketChannel对象
SocketChannel socket = SocketChannel.open();
//2.使用SocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器(3次握手)
boolean b = socket.connect(new InetSocketAddress("127.0.0.1", 8888));
System.out.println(b);
System.out.println("客户端连接服务器成功,给服务器发送数据");
System.out.println("①客户端给服务器发送:你好服务器");
socket.write(ByteBuffer.wrap("你好服务器".getBytes()));
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socket.read(buffer);
System.out.println("④客户端读取服务器发送的数据:"+new String(buffer.array(),0,len));
//3.释放资源
socket.close();
System.out.println("客户端写入数据和读取数据完毕,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端连接服务器失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...");
Thread.sleep(2000);
}
}
}
}
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/*
服务器读取客户端发送的数据,给客户端回写数据
使用SocketChannel类中的方法
int read(ByteBuffer dst) 读取客户端发送的数据
int write(ByteBuffer src) 给客户端发送数据
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用ServerSocketChannel类中的方法open,获取服务器ServerSocketChannel对象
ServerSocketChannel server = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind,绑定指定的端口号
server.bind(new InetSocketAddress(8888));
//设置服务器为非阻塞模式
server.configureBlocking(false);
//创建一个死循环,轮询accpet方法,一直监听客户端请求
while (true){
//3.使用ServerSocketChannel对象中的方法accpet,监听并获取客户端SocketChannel对象
System.out.println("服务器已经启动,等待客户端连接...");
SocketChannel socket = server.accept();
//增加一个非空判断
if(socket!=null){
System.out.println("有客户端连接服务器,读取客户端发送的数据...");
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socket.read(buffer);
System.out.println("②服务器读取客户端发送的数据为:"+new String(buffer.array(),0,len));
System.out.println("③服务器给客户端发送:收到谢谢");
socket.write(ByteBuffer.wrap("收到谢谢".getBytes()));
socket.close();
System.out.println("服务器读取数据,写入数据完毕,结束轮询...");
break;
}
//增加一个睡眠
System.out.println("增加一个睡眠,睡眠2秒钟(干点其他事情),再次轮询获取客户端的请求!");
Thread.sleep(2000);
}
//4.释放资源
server.close();
}
}

四.Selector(选择器)
1.多路复用的概念
选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。使用它可以节省CPU资源,提高程序的运行效率。
"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。
服务器端的非多路复用效果

如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能下降。
服务器端的多路复用效果

使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、高频段业务环境下有非常重要的优势
2.选择器Selector_服务器端实现多路注册
相关的类:
java.nio.channels.Selector:SelectableChannel 对象的多路复用器。
获取对象的方式:
static Selector open() 打开一个选择器。
把服务器注册到Selector选择器上:可以使用服务器ServerSocketChannel中的方法
SelectionKey register(Selector sel, int ops) 向给定的选择器注册此通道(服务器),返回一个选择键。
参数:
Selector sel:传递选择器Selector对象
int ops:要注册到选择器上的事件,传递SelectionKey.OP_ACCEPT(固定写法,用来监听客户端的请求事件)
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
/*
选择器Selector_服务器端实现多路注册
实现步骤:
1.使用ServerSocketChannel类中的方法open,获取3个服务器对象
2.使用ServerSocketChannel对象中的方法bind,给3个服务器绑定不同的端口号
3.使用ServerSocketChannel对象中的方法configureBlocking,设置3个服务器为非阻塞模式
4.使用Selector类中的方法open,获取选择器Selector对象
5.使用ServerSocketChannel对象中的方法register,把3个服务器注册到同一个选择器上
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
//1.使用ServerSocketChannel类中的方法open,获取3个服务器对象
ServerSocketChannel channel01 = ServerSocketChannel.open();
ServerSocketChannel channel02 = ServerSocketChannel.open();
ServerSocketChannel channel03 = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind,给3个服务器绑定不同的端口号
channel01.bind(new InetSocketAddress(7777));
channel02.bind(new InetSocketAddress(8888));
channel03.bind(new InetSocketAddress(9999));
//3.使用ServerSocketChannel对象中的方法configureBlocking,设置3个服务器为非阻塞模式
channel01.configureBlocking(false);
channel02.configureBlocking(false);
channel03.configureBlocking(false);
//4.使用Selector类中的方法open,获取选择器Selector对象
Selector selector = Selector.open();
//5.使用ServerSocketChannel对象中的方法register,把3个服务器注册到同一个选择器上
channel01.register(selector, SelectionKey.OP_ACCEPT);
channel02.register(selector, SelectionKey.OP_ACCEPT);
channel03.register(selector, SelectionKey.OP_ACCEPT);
}
}

点击查看代码
java.nio.channels.ServerSocketChannel;
import java.util.Set;
/*
选择器Selector_服务器端实现多路注册
实现步骤:
1.使用ServerSocketChannel类中的方法open,获取3个服务器对象
2.使用ServerSocketChannel对象中的方法bind,给3个服务器绑定不同的端口号
3.使用ServerSocketChannel对象中的方法configureBlocking,设置3个服务器为非阻塞模式
4.使用Selector类中的方法open,获取选择器Selector对象
5.使用ServerSocketChannel对象中的方法register,把3个服务器注册到同一个选择器上
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用ServerSocketChannel类中的方法open,获取3个服务器对象
ServerSocketChannel channel01 = ServerSocketChannel.open();
ServerSocketChannel channel02 = ServerSocketChannel.open();
ServerSocketChannel channel03 = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind,给3个服务器绑定不同的端口号
channel01.bind(new InetSocketAddress(7777));
channel02.bind(new InetSocketAddress(8888));
channel03.bind(new InetSocketAddress(9999));
//3.使用ServerSocketChannel对象中的方法configureBlocking,设置3个服务器为非阻塞模式
channel01.configureBlocking(false);
channel02.configureBlocking(false);
channel03.configureBlocking(false);
//4.使用Selector类中的方法open,获取选择器Selector对象
Selector selector = Selector.open();
//5.使用ServerSocketChannel对象中的方法register,把3个服务器注册到同一个选择器上
channel01.register(selector, SelectionKey.OP_ACCEPT);
channel02.register(selector, SelectionKey.OP_ACCEPT);
channel03.register(selector, SelectionKey.OP_ACCEPT);
//Selector的keys()方法 表示:已注册通道的集合
Set<SelectionKey> keys = selector.keys();
System.out.println("已注册通道的集合的长度:"+keys.size());
//轮询服务器,一直获取客户端的连接
while (true){
//Selector的select()方法 表示本次有几个客户端连接了服务器,没有客户端连接服务器,此方法将阻塞
int select = selector.select();
System.out.println("连接服务器的客户端数量为:"+select);
//Selector的selectedKeys()方法 表示:当前已连接的通道的集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("当前已连接的通道的集合长度:"+selectionKeys.size());
System.out.println("睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...");
Thread.sleep(2000);
}
}
}
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
/*
开启3个线程,每个线程创建一个客户端,访问服务器
*/
public class TCPClient {
public static void main(String[] args) {
new Thread(()->{
//定义一个死循环,轮询连接服务器
while (true){
try {
SocketChannel channel = SocketChannel.open();
boolean b = channel.connect(new InetSocketAddress("localhost", 7777));
System.out.println(b);
channel.close();
System.out.println("客户端连接服务器7777端口号成功,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端连接服务器7777端口失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...");
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}).start();
new Thread(()->{
//定义一个死循环,轮询连接服务器
while (true){
try {
SocketChannel channel = SocketChannel.open();
boolean b = channel.connect(new InetSocketAddress("localhost", 8888));
System.out.println(b);
channel.close();
System.out.println("客户端连接服务器8888端口号成功,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端连接服务器8888端口失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...");
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}).start();
new Thread(()->{
//定义一个死循环,轮询连接服务器
while (true){
try {
SocketChannel channel = SocketChannel.open();
boolean b = channel.connect(new InetSocketAddress("localhost", 9999));
System.out.println(b);
channel.close();
System.out.println("客户端连接服务器9999端口号成功,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端连接服务器9999端口失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...");
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}).start();
}
}
点击查看代码
已注册通道的集合的长度:3
连接服务器的客户端数量为:1
当前已连接的通道的集合长度:1
睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...
连接服务器的客户端数量为:2
当前已连接的通道的集合长度:3
睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...
连接服务器的客户端数量为:0
当前已连接的通道的集合长度:3
睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...
连接服务器的客户端数量为:0
当前已连接的通道的集合长度:3
睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...
连接服务器的客户端数量为:0
当前已连接的通道的集合长度:3
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/*
开启3个线程,每个线程创建一个客户端,访问服务器
*/
public class TCPClient {
public static void main(String[] args) {
new Thread(()->{
//定义一个死循环,轮询连接服务器
while (true){
try {
SocketChannel channel = SocketChannel.open();
boolean b = channel.connect(new InetSocketAddress("localhost", 7777));
System.out.println(b);
System.out.println("客户端连接服务器7777端口号成功,给服务器发送数据...");
channel.write(ByteBuffer.wrap("你好服务器,我是连接7777端口号的客户端!".getBytes()));
channel.close();
System.out.println("客户端给7777端口号发送数据成功,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端连接服务器7777端口失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...");
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}).start();
new Thread(()->{
//定义一个死循环,轮询连接服务器
while (true){
try {
SocketChannel channel = SocketChannel.open();
boolean b = channel.connect(new InetSocketAddress("localhost", 8888));
System.out.println(b);
System.out.println("客户端连接服务器8888端口号成功,给服务器发送数据...");
channel.write(ByteBuffer.wrap("你好服务器,我是连接8888端口号的客户端!".getBytes()));
channel.close();
System.out.println("客户端给8888端口号发送数据成功,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端连接服务器8888端口失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...");
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}).start();
new Thread(()->{
//定义一个死循环,轮询连接服务器
while (true){
try {
SocketChannel channel = SocketChannel.open();
boolean b = channel.connect(new InetSocketAddress("localhost", 9999));
System.out.println(b);
System.out.println("客户端连接服务器9999端口号成功,给服务器发送数据...");
channel.write(ByteBuffer.wrap("你好服务器,我是连接9999端口号的客户端!".getBytes()));
channel.close();
System.out.println("客户端给9999端口号发送数据成功,结束轮询...");
break;
} catch (IOException e) {
System.out.println("客户端连接服务器9999端口失败,睡眠2秒钟(干点其他事情),再次轮询连接服务器...");
try {
Thread.sleep(2000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}).start();
}
}
点击查看代码
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;
/*
选择器Selector_服务器端实现多路注册
实现步骤:
1.使用ServerSocketChannel类中的方法open,获取3个服务器对象
2.使用ServerSocketChannel对象中的方法bind,给3个服务器绑定不同的端口号
3.使用ServerSocketChannel对象中的方法configureBlocking,设置3个服务器为非阻塞模式
4.使用Selector类中的方法open,获取选择器Selector对象
5.使用ServerSocketChannel对象中的方法register,把3个服务器注册到同一个选择器上
*/
public class TCPServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用ServerSocketChannel类中的方法open,获取3个服务器对象
ServerSocketChannel channel01 = ServerSocketChannel.open();
ServerSocketChannel channel02 = ServerSocketChannel.open();
ServerSocketChannel channel03 = ServerSocketChannel.open();
//2.使用ServerSocketChannel对象中的方法bind,给3个服务器绑定不同的端口号
channel01.bind(new InetSocketAddress(7777));
channel02.bind(new InetSocketAddress(8888));
channel03.bind(new InetSocketAddress(9999));
//3.使用ServerSocketChannel对象中的方法configureBlocking,设置3个服务器为非阻塞模式
channel01.configureBlocking(false);
channel02.configureBlocking(false);
channel03.configureBlocking(false);
//4.使用Selector类中的方法open,获取选择器Selector对象
Selector selector = Selector.open();
//5.使用ServerSocketChannel对象中的方法register,把3个服务器注册到同一个选择器上
channel01.register(selector, SelectionKey.OP_ACCEPT);
channel02.register(selector, SelectionKey.OP_ACCEPT);
channel03.register(selector, SelectionKey.OP_ACCEPT);
//Selector的keys()方法 表示:已注册通道的集合
Set<SelectionKey> keys = selector.keys();
System.out.println("已注册通道的集合的长度:"+keys.size());
//轮询服务器,一直获取客户端的连接
while (true){
//Selector的select()方法 表示本次有几个客户端连接了服务器,没有客户端连接服务器,此方法将阻塞
int select = selector.select();
System.out.println("连接服务器的客户端数量为:"+select);
//Selector的selectedKeys()方法 表示:当前已连接的通道的集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
System.out.println("当前已连接的通道的集合长度:"+selectionKeys.size());
//遍历selectionKeys集合,获取每一个SelectionKey对象
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()){
SelectionKey selectionKey = it.next();
//取出每一个SelectionKey中封装的服务器ServerSocketChannel对象
ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
//处理服务器对象监听到的ACCEPT事件(获取客户端SocketChannel对象)
SocketChannel socket = channel.accept();
//读取客户端发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = socket.read(buffer);
System.out.println("服务器读取到客户端发送的数据为:"+new String(buffer.array(),0,len));
//在Set集合中,把处理完的通道删除
it.remove();//使用迭代器删除集合中元素的方法,不会抛出迭代器的并发修改异常
}
System.out.println("睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...");
Thread.sleep(2000);
}
}
}
点击查看代码
已注册通道的集合的长度:3
连接服务器的客户端数量为:1
当前已连接的通道的集合长度:1
服务器读取到客户端发送的数据为:你好服务器,我是连接8888端口号的客户端!
睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...
连接服务器的客户端数量为:2
当前已连接的通道的集合长度:2
服务器读取到客户端发送的数据为:你好服务器,我是连接9999端口号的客户端!
服务器读取到客户端发送的数据为:你好服务器,我是连接7777端口号的客户端!
睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...
连接服务器的客户端数量为:1
当前已连接的通道的集合长度:1
服务器读取到客户端发送的数据为:你好服务器,我是连接9999端口号的客户端!
睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...
连接服务器的客户端数量为:2
当前已连接的通道的集合长度:2
服务器读取到客户端发送的数据为:你好服务器,我是连接8888端口号的客户端!
服务器读取到客户端发送的数据为:你好服务器,我是连接7777端口号的客户端!
睡眠2秒钟,干点其他事情,再次轮询获取客户端的连接...
五.NIO2-AIO(异步、非阻塞)
1.AIO概述
JDK7新增的:AsynchronousIO:异步、非阻塞IO
2.AIO异步非阻塞连接
点击查看代码
创建AIO的服务器端:
相关的类:
java.nio.channels.AsynchronousServerSocketChannel:用于面向流的侦听套接字的异步通道。
获取对的方法:
static AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。
成员方法:
AsynchronousServerSocketChannel bind(SocketAddress local) 给服务器绑定指定的端口号
void accept(A attachment, CompletionHandler<?> handler) 监听客户端的请求,默认就是非阻塞的
参数:
A attachment:附件,可以传递null
CompletionHandler<?> handler:事件处理的接口,用于处理accept方法监听到的事件
CompletionHandler:也叫回调函数,客户端请求服务器之后,会自动执行CompletionHandler接口中的方法
java.nio.channels.CompletionHandler<V,A>接口:用于消除异步I / O操作结果的处理程序。
接口中的方法:
void completed(V result, A attachment) 客户端连接服务器成功执行的方法
void failed(Throwable exc, A attachment) 客户端连接服务器失败执行的方法
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
/*
创建AIO的服务器端(异步非阻塞)
实现步骤:
1.使用AsynchronousServerSocketChannel类中的方法open,获取服务器对象
2.使用AsynchronousServerSocketChannel对象中的方法bind,给服务器绑定执定的端口号
3.使用AsynchronousServerSocketChannel对象中的方法accpet,监听并获取客户端对象
4.释放资源
*/
public class AIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用AsynchronousServerSocketChannel类中的方法open,获取服务器对象
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
//2.使用AsynchronousServerSocketChannel对象中的方法bind,给服务器绑定执定的端口号
server.bind(new InetSocketAddress(9999));
System.out.println("accept方法开始执行了...");
//3.使用AsynchronousServerSocketChannel对象中的方法accpet,监听并获取客户端对象
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("客户端连接服务器成功,自动执行completed方法...");
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接服务器失败,自动执行failed方法...");
}
});
System.out.println("accept方法执行结束了...");
//增加一个死循环,目的是不让服务器结束(以后在实际工作中可以干一些有意义的事情)
while (true){
System.out.println("服务器正在做其他的事情,睡眠2秒钟...");
Thread.sleep(2000);
}
//4.释放资源
//server.close();
}
}
点击查看代码
创建AIO的客户端:
相关的类:
java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
获取对象的方法:
static AsynchronousSocketChannel open() 打开异步套接字通道。
成员方法:
Future<Void> connect(SocketAddress remote) 连接服务器的方法,参数传递服务器的ip地址和端口号
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.Future;
/*
创建AIO的客户端
实现步骤:
1.使用AsynchronousSocketChannel中的方法open,获取客户端对象
2.使用AsynchronousSocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器
3.释放资源
*/
public class AIOClient {
public static void main(String[] args) throws IOException {
//1.使用AsynchronousSocketChannel中的方法open,获取客户端对象
AsynchronousSocketChannel socket = AsynchronousSocketChannel.open();
//2.使用AsynchronousSocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器
Future<Void> future = socket.connect(new InetSocketAddress("localhost", 9999));
//3.释放资源
socket.close();
}
}
点击查看代码
accept方法开始执行了...
accept方法执行结束了...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
客户端连接服务器成功,自动执行completed方法...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
点击查看代码
AIO异步连接:异步阻塞读写
AsynchronousSocketChannel成员方法:
Future<Integer> write(ByteBuffer src) 给服务器发送数据
Future<Integer> read(ByteBuffer dst) 读取服务器发送的数据
Future<Void> connect(SocketAddress remote) 连接服务器的方法,参数传递服务器的ip地址和端口号
java.util.concurrent.Future<V>接口
接口中的方法:
boolean isDone() 如果此任务完成,则返回 true 。
返回true:连接服务器成功
返回false:还没有连接上服务器(客户端连接服务器是需要时间的)
注意:
connect是一个非阻塞的方法,不会等待方法运行完毕,连接服务器成功在执行下边的代码
客户端连接服务器需要时间的,如果没有连接成功,就给服务器使用write方法发送数据,会抛出异常
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.Future;
/*
创建AIO的客户端
实现步骤:
1.使用AsynchronousSocketChannel中的方法open,获取客户端对象
2.使用AsynchronousSocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器
3.释放资源
*/
public class AIOClient {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用AsynchronousSocketChannel中的方法open,获取客户端对象
AsynchronousSocketChannel socket = AsynchronousSocketChannel.open();
//2.使用AsynchronousSocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器
Future<Void> future = socket.connect(new InetSocketAddress("localhost", 9999));
System.out.println(future.isDone());
//让程序睡眠5秒钟
Thread.sleep(10000);
if(future.isDone()){
//isDone返回的是true,给服务器发送数据
socket.write(ByteBuffer.wrap("你好服务器!".getBytes()));
}
//3.释放资源
socket.close();
}
}
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/*
创建AIO的服务器端(异步非阻塞)
实现步骤:
1.使用AsynchronousServerSocketChannel类中的方法open,获取服务器对象
2.使用AsynchronousServerSocketChannel对象中的方法bind,给服务器绑定执定的端口号
3.使用AsynchronousServerSocketChannel对象中的方法accpet,监听并获取客户端对象
4.释放资源
*/
public class AIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用AsynchronousServerSocketChannel类中的方法open,获取服务器对象
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
//2.使用AsynchronousServerSocketChannel对象中的方法bind,给服务器绑定执定的端口号
server.bind(new InetSocketAddress(9999));
System.out.println("accept方法开始执行了...");
//3.使用AsynchronousServerSocketChannel对象中的方法accpet,监听并获取客户端对象
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel result, Object attachment) {
System.out.println("客户端连接服务器成功,自动执行completed方法,读取客户端发送的数据...");
/*
Future<Integer> read(ByteBuffer dst) 读取客户端发送的数据
read方法:是一个阻塞的方法,客户端连接成功服务器,没有发送数据,此方法一直阻塞等待
*/
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> future = result.read(buffer);
/*
使用Future接口中的方法get,取出读取到的数据
V get() 等待计算完成,然后检索其结果。
*/
try {
Integer len = future.get();
System.out.println("服务器读取客户端发送的数据为:"+new String(buffer.array(),0,len));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接服务器失败,自动执行failed方法...");
}
});
System.out.println("accept方法执行结束了...");
//增加一个死循环,目的是不让服务器结束(以后在实际工作中可以干一些有意义的事情)
while (true){
System.out.println("服务器正在做其他的事情,睡眠2秒钟...");
Thread.sleep(2000);
}
//4.释放资源
//server.close();
}
}
点击查看代码
accept方法开始执行了...
accept方法执行结束了...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
客户端连接服务器成功,自动执行completed方法,读取客户端发送的数据...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器读取客户端发送的数据为:你好服务器!
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/*
创建AIO的客户端
实现步骤:
1.使用AsynchronousSocketChannel中的方法open,获取客户端对象
2.使用AsynchronousSocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器
3.释放资源
*/
public class AIOClient {
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
//1.使用AsynchronousSocketChannel中的方法open,获取客户端对象
AsynchronousSocketChannel socket = AsynchronousSocketChannel.open();
//2.使用AsynchronousSocketChannel对象中的方法connect,根据服务器的ip地址和端口号连接服务器
Future<Void> future = socket.connect(new InetSocketAddress("localhost", 9999));
System.out.println(future.isDone());
//让程序睡眠5秒钟
Thread.sleep(4000);
if(future.isDone()){
//isDone返回的是true,给服务器发送数据
socket.write(ByteBuffer.wrap("你好服务器!".getBytes()));
}
//客户端读取服务器发送的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> f = socket.read(buffer);
System.out.println("客户端读取服务器发送的数据:"+new String(buffer.array(),0,f.get()));
//3.释放资源
socket.close();
}
}
点击查看代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/*
创建AIO的服务器端(异步非阻塞)
实现步骤:
1.使用AsynchronousServerSocketChannel类中的方法open,获取服务器对象
2.使用AsynchronousServerSocketChannel对象中的方法bind,给服务器绑定执定的端口号
3.使用AsynchronousServerSocketChannel对象中的方法accpet,监听并获取客户端对象
4.释放资源
*/
public class AIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
//1.使用AsynchronousServerSocketChannel类中的方法open,获取服务器对象
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
//2.使用AsynchronousServerSocketChannel对象中的方法bind,给服务器绑定执定的端口号
server.bind(new InetSocketAddress(9999));
System.out.println("accept方法开始执行了...");
//3.使用AsynchronousServerSocketChannel对象中的方法accpet,监听并获取客户端对象
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel socket, Object attachment) {
System.out.println("客户端连接服务器成功,自动执行completed方法,读取客户端发送的数据...");
/*
read:是一个非阻塞的方法,只在设置的时间内等待客户端发送数据
void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer,? super A> handler)读取客户端发送的数据
参数
ByteBuffer dst - 要传输字节的缓冲区
long timeout - 完成I / O操作的最长时间 10
TimeUnit unit - timeout参数的时间单位 TimeUnit.SECONDS:秒 TimeUnit.DAYS:天
A attachment - 要附加到I / O操作的对象; 可以是null
CompletionHandler handler - 消费结果的处理程序
completed:客户端连接服务器之后,在设置的时间范围内发送数据,执行completed成功方法
failed:客户端连接服务器之后,在设置的时间范围内没有发送数据,执行failed失败方法
*/
ByteBuffer buffer = ByteBuffer.allocate(1024);
socket.read(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer len, Object attachment) {
System.out.println("客户端连接服务器之后,在设置的时间范围内发送数据,执行completed成功方法");
System.out.println("服务器读取到客户端发送的数据为:"+new String(buffer.array(),0,len));
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接服务器之后,在设置的时间范围内没有发送数据,执行failed失败方法");
}
});
//服务器给客户端发送数据
socket.write(ByteBuffer.wrap("收到,谢谢!".getBytes()));
}
@Override
public void failed(Throwable exc, Object attachment) {
System.out.println("客户端连接服务器失败,自动执行failed方法...");
}
});
System.out.println("accept方法执行结束了...");
//增加一个死循环,目的是不让服务器结束(以后在实际工作中可以干一些有意义的事情)
while (true){
System.out.println("服务器正在做其他的事情,睡眠2秒钟...");
Thread.sleep(2000);
}
//4.释放资源
//server.close();
}
}
点击查看代码
accept方法开始执行了...
accept方法执行结束了...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
客户端连接服务器成功,自动执行completed方法,读取客户端发送的数据...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
客户端连接服务器之后,在设置的时间范围内没有发送数据,执行failed失败方法
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
点击查看代码
accept方法开始执行了...
accept方法执行结束了...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
客户端连接服务器成功,自动执行completed方法,读取客户端发送的数据...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
客户端连接服务器之后,在设置的时间范围内发送数据,执行completed成功方法
服务器读取到客户端发送的数据为:你好服务器!
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
服务器正在做其他的事情,睡眠2秒钟...
六.FileChannel与结合MappedByteBuffer实现高效读写
1.FileChannel类的基本使用
- java.nio.channels.FileChannel (抽象类):用于读、写文件的通道。
- FileChannel是抽象类,我们可以通过FileInputStream和FileOutputStream的getChannel()方法方便的获取一个它的子类对象。
点击查看代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/*
java.nio.channels.FileChannel类:用于读取、写入、映射和操作文件的通道。
可以使用FileChannel读取文件,写入文件和复制文件
FileChannel是一个抽象类,我们不能直接创建对象使用
创建对象的方式:可以使用FileInputStream和FileOutputStream流中的方法获取
java.io.FileInputStream:字节输入流
FileChannel getChannel() 获取读取文件的FileChannel对象
java.io.FileOutputStream:字节输出流
FileChannel getChannel() 获取写入文件的FileChannel对象
FileChannel的成员方法:
int read(ByteBuffer dst) 读取数据的方法
int write(ByteBuffer src) 写入数据的方法
FileChannel的使用步骤(重点):
1.创建FileInputStream对象,构造方法中绑定要读取的数据源
2.创建FileOutputStream对象,构造方法中绑定要写入的目的地
3.使用FileInputStream对象中的方法getChannel,获取读取文件的FileChannel对象
4.使用FileOutputStream对象中的方法getChannel,获取写入文件的FileChannel对象
5.使用读取文件的FileChannel对象中的方法read读取文件中的数据
6.使用写入文件的FileChannel对象中的方法write,把读取到的数据写入到文件
7.释放资源
*/
public class Demo01FileChannel {
public static void main(String[] args) throws IOException {
//1.创建FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("c:\\1.jpg");
//2.创建FileOutputStream对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
//3.使用FileInputStream对象中的方法getChannel,获取读取文件的FileChannel对象
FileChannel fisChannel = fis.getChannel();
//4.使用FileOutputStream对象中的方法getChannel,获取写入文件的FileChannel对象
FileChannel fosChannel = fos.getChannel();
//一读一写复制文件
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
//5.使用读取文件的FileChannel对象中的方法read读取文件中的数据
while ((len = fisChannel.read(buffer))!=-1){
buffer.flip();//缩小limit范围
//6.使用写入文件的FileChannel对象中的方法write,把读取到的数据写入到文件
fosChannel.write(buffer);
buffer.clear();//还原ByteBuffer
}
//7.释放资源
fosChannel.close();
fisChannel.close();
fos.close();
fis.close();
}
}
点击查看代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/*
flip方法和clear方法的作用
*/
public class Demo02FileChannel {
public static void main(String[] args) throws IOException {
//1.创建FileInputStream对象,构造方法中绑定要读取的数据源
FileInputStream fis = new FileInputStream("c:\\1.jpg");
//2.创建FileOutputStream对象,构造方法中绑定要写入的目的地
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
//3.使用FileInputStream对象中的方法getChannel,获取读取文件的FileChannel对象
FileChannel fisChannel = fis.getChannel();
//4.使用FileOutputStream对象中的方法getChannel,获取写入文件的FileChannel对象
FileChannel fosChannel = fos.getChannel();
//一读一写复制文件
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
//5.使用读取文件的FileChannel对象中的方法read读取文件中的数据
while ((len = fisChannel.read(buffer))!=-1){
System.out.println("flip前==>position:"+buffer.position()+",limit:"+buffer.limit());
buffer.flip();//缩小limit范围
System.out.println("flip后==>position:"+buffer.position()+",limit:"+buffer.limit());
//6.使用写入文件的FileChannel对象中的方法write,把读取到的数据写入到文件
System.out.println("write方法写position:"+buffer.position()+"到limit:"+buffer.limit()+"之间的数据!");
fosChannel.write(buffer);
System.out.println("clear前==>position:"+buffer.position()+",limit:"+buffer.limit());
buffer.clear();//还原ByteBuffer
System.out.println("clear后==>position:"+buffer.position()+",limit:"+buffer.limit());
System.out.println("----------------------------------------------------------------");
}
//7.释放资源
fosChannel.close();
fisChannel.close();
fos.close();
fis.close();
}
}
点击查看代码
...
flip前==>position:1024,limit:1024
flip后==>position:0,limit:1024
write方法写position:0到limit:1024之间的数据!
clear前==>position:1024,limit:1024
clear后==>position:0,limit:1024
----------------------------------------------------------------
flip前==>position:1024,limit:1024
flip后==>position:0,limit:1024
write方法写position:0到limit:1024之间的数据!
clear前==>position:1024,limit:1024
clear后==>position:0,limit:1024
----------------------------------------------------------------
flip前==>position:780,limit:1024
flip后==>position:0,limit:780
write方法写position:0到limit:780之间的数据!
clear前==>position:780,limit:780
clear后==>position:0,limit:1024
----------------------------------------------------------------

2.FileChannel结合MappedByteBuffer实现高效读写
java.io.RandomAccessFile类
点击查看代码
获取FileChannel需要使用RandomAccessFile类,可以创建流对象的同时设置读写模式
java.io.RandomAccessFile类,可以设置读、写模式的IO流类
构造方法:
RandomAccessFile(String name, String mode)
参数:
String name:要读取的数据源,或者写入的目的地
String mode:设置流的读写模式
"r":只读,必须是小写
"rw":读写,必须是小写
成员方法:
FileChannel getChannel() 返回与此文件关联的唯一 FileChannel 对象。
点击查看代码
- MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) 将此通道的文件区域直接映射到内存中。
参数:
FileChannel.MapMode mode:设置读写的模式
READ_ONLY:只读映射模式。
READ_WRITE:读取/写入映射模式。
long position:文件中的位置,映射区域从此位置开始,一般都是从0开始
size - 要映射的区域大小,就是要复制文件的大小,单位字节
点击查看代码
java.nio.MappedByteBuffer:它可以创建“直接缓存区”,将文件的磁盘数据映射到内存。
注意:它最大可以映射:Integer.MAX_VALUE个字节(2G)左右。
eg:磁盘和内存实时映射 硬盘(abc) 内存(abc) 内存修改为(ab) 磁盘也跟着修改(ab)
MappedByteBuffer中的方法:
byte get(int index) 获取缓冲区中指定索引处的字节
ByteBuffer put(int index, byte b) 把字节写入到指定的索引处
点击查看代码
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/*
FileChannel结合MappedByteBuffer实现高效读写(重点)
java.nio.MappedByteBuffer:它可以创建“直接缓存区”,将文件的磁盘数据映射到内存。
注意:它最大可以映射:Integer.MAX_VALUE个字节(2G)左右。
复制2g以下的文件:
实现步骤:
1.创建读取文件的RandomAccessFile对象,构造方法中绑定要读取的数据源和设置只读默认
2.创建写入文件的RandomAccessFile对象,构造方法中绑定要写入的目的地和设置读写默认
3.使用读取文件的RandomAccessFile对象中的方法getChannel,获取读取文件的FileChannel对象
4.使用写入文件的RandomAccessFile对象中的方法getChannel,获取写入文件的FileChannel对象
5.使用读取文件的FileChannel对象中的方法size,获取要读取文件大小(单位:字节)
6.使用读取文件的FileChannel对象中的方法map.获取读取文件的MappedByteBuffer直接字节缓冲区
7.使用写入文件的FileChannel对象中的方法map.获取写入文件的MappedByteBuffer直接字节缓冲区
8.创建for循环,循环size次(文件大小:文件有多少字节,循环多少次)
9.使用读取文件的MappedByteBuffer中的方法get,获取读取文件指定索引处的字节
10.使用写入文件的MappedByteBuffer中的方法put,把读取到的字节,写入到目的地
11.释放资源
*/
public class Demo03FileChannel {
public static void main(String[] args) throws IOException {
long s = System.currentTimeMillis();
//1.创建读取文件的RandomAccessFile对象,构造方法中绑定要读取的数据源和设置只读默认
RandomAccessFile inRAF = new RandomAccessFile("c:\\748m.rar","r");
//2.创建写入文件的RandomAccessFile对象,构造方法中绑定要写入的目的地和设置读写默认
RandomAccessFile outRAF = new RandomAccessFile("d:\\748m.rar","rw");
//3.使用读取文件的RandomAccessFile对象中的方法getChannel,获取读取文件的FileChannel对象
FileChannel inRAFChannel = inRAF.getChannel();
//4.使用写入文件的RandomAccessFile对象中的方法getChannel,获取写入文件的FileChannel对象
FileChannel outRAFChannel = outRAF.getChannel();
//5.使用读取文件的FileChannel对象中的方法size,获取要读取文件大小(单位:字节)
long size = inRAFChannel.size();
System.out.println("要复制的文件大小为:"+size+"字节");//要复制的文件大小为:785042177字节
//6.使用读取文件的FileChannel对象中的方法map.获取读取文件的MappedByteBuffer直接字节缓冲区
MappedByteBuffer inMap = inRAFChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
//7.使用写入文件的FileChannel对象中的方法map.获取写入文件的MappedByteBuffer直接字节缓冲区
MappedByteBuffer outMap = outRAFChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
//8.创建for循环,循环size次(文件大小:文件有多少字节,循环多少次)
for (int i = 0; i < size; i++) {
//9.使用读取文件的MappedByteBuffer中的方法get,获取读取文件指定索引处的字节
byte b = inMap.get();
//10.使用写入文件的MappedByteBuffer中的方法put,把读取到的字节,写入到目的地
outMap.put(i,b);
}
//11.释放资源
outRAFChannel.close();
inRAFChannel.close();
outRAF.close();
inRAF.close();
long e = System.currentTimeMillis();
System.out.println("复制文件共耗时:"+(e-s)+"毫秒!");//复制文件共耗时:3578毫秒!
}
}

4.代码实现:复制2g以上的文件

点击查看代码
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/*
FileChannel结合MappedByteBuffer实现高效读写(重点)
注意:
MappedByteBuffer:直接缓冲区(系统内存中,和系统直接交互数据,速度快,效率高)
直接缓冲区设置最大为2G,复制的文件不能超过2g,超过了就需要分块复制
需求:
使用FileChannel结合MappedByteBuffer实现高效读写复制2g以上的文件
*/
public class Demo03FileChannel {
public static void main(String[] args) throws IOException {
long s = System.currentTimeMillis();
//1.创建读取文件的RandomAccessFile对象,构造方法中封装要读取的数据源和设置只读模式("r")
RandomAccessFile inRAF = new RandomAccessFile("c:\\2g.rar","r");
//2.创建写入文件的RandomAccessFile对象,构造方法中封装要写入的目的地和设置读写模式("rw")
RandomAccessFile outRAF = new RandomAccessFile("d:\\2g.rar","rw");
//3.使用读取文件的RandomAccessFile对象中的方法getChannel,获取读取文件的FileChannel对象
FileChannel inRAFChannel = inRAF.getChannel();
//4.使用写入文件的RandomAccessFile对象中的方法getChannel,获取读取写入的FileChannel对象
FileChannel outRAFChannel = outRAF.getChannel();
//5.使用读取文件的FileChannel对象中的方法size,获取读取文件的大小(单位字节)
long size = inRAFChannel.size();
System.out.println(size);//2355126731 字节
//定义复制文件需要使用的变量
long count = 1;//复制文件的块数,默认值是1
long startIndex = 0;//每次复制每块文件的开始索引
long everySize = 512*1024*1024;//分块,每块的大小 512M
long copySize = size;//每次复制文件的大小,默认等于文件的总大小
//判断要复制的文件大小是否大于每块文件的大小
if(size>everySize){
//复制的文件大于512M,进行分块
//计算复制文件可以分成几块 2242.56M/512M
count = size%everySize==0 ? size/everySize : size/everySize+1;
System.out.println("文件的大小:"+size+"字节,可以分成:"+count+"块");
//第一次复制文件的大小等于每块的大小
copySize = everySize;
}
//定义一个for循环,分几块就循环复制几次
for (int i = 0; i < count; i++) {
//6.使用读取文件的FileChannel对象中的方法map,创建读取文件的直接缓冲区MappedByteBuffer对象
MappedByteBuffer inMap = inRAFChannel.map(FileChannel.MapMode.READ_ONLY, startIndex, copySize);
//7.使用写入文件的FileChannel对象中的方法map,创建写入文件的直接缓冲区MappedByteBuffer对象
MappedByteBuffer outMap = outRAFChannel.map(FileChannel.MapMode.READ_WRITE, startIndex, copySize);
System.out.println("每块文件的开始复制的索引:"+startIndex);
System.out.println("每块文件的大小:"+copySize+"字节");
System.out.println("--------------------------------------------");
//8.创建for循环,循环size次
for (int j = 0; j < copySize; j++) {
//9.使用取文件的直接缓冲区MappedByteBuffer对象中的方法get,读取数据源指定索引处的文件
byte b = inMap.get(j);
//10.使用写入文件的直接缓冲区MappedByteBuffer对象中的方法put,把读取到的字节写入到目的地指定的索引处
outMap.put(j,b);
}
//复制完每块文件,重新计算startIndex和copySize的大小
startIndex += copySize;
copySize = size-startIndex>everySize ? everySize : size-startIndex;
}
//11.释放资源
outRAFChannel.close();
inRAFChannel.close();
outRAF.close();
inRAF.close();
long e = System.currentTimeMillis();
System.out.println("复制文件共耗时:"+(e-s)+"毫秒!");//复制文件共耗时:4912毫秒!
}
}
点击查看代码
文件的大小:2355126731字节,可以分成:5块
每块文件的开始复制的索引:0
每块文件的大小:536870912字节
--------------------------------------------
每块文件的开始复制的索引:536870912
每块文件的大小:536870912字节
--------------------------------------------
每块文件的开始复制的索引:1073741824
每块文件的大小:536870912字节
--------------------------------------------
每块文件的开始复制的索引:1610612736
每块文件的大小:536870912字节
--------------------------------------------
每块文件的开始复制的索引:2147483648
每块文件的大小:207643083字节
--------------------------------------------
复制文件共耗时:9833毫秒!

浙公网安备 33010602011771号