概念
- I/O是输入和输出的简写,指的是数据在计算机内部和外部设备之间的流动
- Java中将输入输出抽象为流,类似于水管将两个容器连接起来
- 将数据从外存读取到内存中的称为输入流,从内存写到外存中的称为输出流
电脑的三种数据存储方式
- 外存:硬盘,磁盘,U盘等。存储量大,读取最慢
- 内存:电脑上的内存条。存储量其次,读取速度其次慢
- 缓存:CPU。存储量小,读取最快。
分类
-
基于字节操作的 I/O 接口:InputStream 和 OutputStream
-
基于字符操作的 I/O 接口:Writer 和 Reader
-
基于磁盘操作的 I/O 接口:File
-
基于网络操作的 I/O 接口:Socket
字节流
数据流中最小的数据单元
Java系统自带标准的数据流是java.lang.System:
public final class System {
//标准输入流(键盘输入流)
public final static InputStream in = null;
//标准输出流(显示器输出流)
public final static PrintStream out = null;
//标准错误流(输出)
public final static PrintStream err = null;
}
//它用于打印异常(Exception)或错误(Error)的栈追踪信息到标准错误流(System.err)
public class Throwable implements Serializable {
//打印异常或错误
public void printStackTrace() {
//错误流
printStackTrace(System.err);
}
}
输入流与输出流
InputStream | 节点流 | ByteArrayInputStream(数组) |
FileInputStream(文件)=>SocketInpurStream(网络) | ||
PipedInputStream(管道) | ||
处理流 | InflaterInputStream(压缩操作)=>ZipInputStream(Zip格式操作) | |
BufferedInputStream(缓冲操作) | ||
DataInputStream(基本数据类型操作) | ||
ObjectInputStream(序列化操作) | ||
OutputStream | 节点流 | ByteArrayOutputStream(数组) |
FileOutputStream(文件)=>ScoketOutputStream(网络) | ||
PipedOutputStream(管道) | ||
处理流 | InflaterOutputStream(压缩操作)=>ZipOutputStream(Zip格式操作) | |
BufferedOutputStream(缓冲操作) | ||
DataOutputStream(基本数据类型操作) | ||
PrintStream(打印操作) | ||
ObjectInputStream(序列化操作) |
字符流
最小存储单元是字节而不是字符,Java中的字符是Unicode编码,一个字符占用两个字节
因为程序通常操作的数据都是以字符的形式,为了程序操作更加方便提供了直接写字符的IO接口
Reader | 节点流 | CharArrayReader(数组) |
FileReader(文件) | ||
PipedReader(管道) | ||
StringReader(字符串) | ||
处理流 | BufferReader(缓冲操作)=>LineNumnerReader(跟踪行号) | |
InputStreamReader(转化控制) | ||
PushbackReader(从回退的缓冲区读数据) | ||
Writer | 节点流 | CharArrayWriter(数组) |
FileWriter(文件) | ||
PipedWriter(管道) | ||
StringWriter(字符串) | ||
处理流 | BufferWriter(缓冲操作) | |
OutputWriter(转化控制) | ||
PrintWriter(打印操作) |
字符与字节的转换
InputStreamReader将字节输入流转为字符输入流
OutputStreamWriter将字符输出流转为字节输出流
编码与解码
编码:字符串与字符 ==> 字节
解码:字节 == >字符串与字符
字符编码和字符集
编码依赖于字符集,一个字符集可以有多个编码实现,就像代码中接口的实现依赖于接口一样
转换流原理:字符流=字节流+编码表
字符编码:enconding是charset encoding的简写,即字符集编码,简称编码
字符集:character set的简写
ASCII字符集 | ASCII编码 |
GBK字符集 | GBK编码 |
Unicode字符集 | UTF8编码 |
UTF16编码 | |
UTF32编码 |
输入流转化过程
- InputStreamReader 类是字节到字符的转化桥梁
- StreamDecoder指的是一个解码操作类,Charset值指的是字符集
- InputStream到Reader的过程需要制定编码字符集,否则采取默认字符集可能会出现乱码,StreamDecoder则是完成字节到字符的解码的实现类
- InputStreamReader(InputStream in):创建一个默认字符集字符输入流
- InputStreamReader(InputStream in, String charsetName):创建一个指定字符集的字符流
public class InputStreamReader extends Reader {
private final StreamDecoder sd;
public InputStreamReader(InputStream in) {
super(in);
try {
// ## check lock object
sd = StreamDecoder.forInputStreamReader(in, this, (String)null);
} catch (UnsupportedEncodingException e) {
// The default encoding should always be available
throw new Error(e);
}
}
public InputStreamReader(InputStream in, String charsetName)
throws UnsupportedEncodingException
{
super(in);
if (charsetName == null)
throw new NullPointerException("charsetName");
sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
}
}
public class IOTest {
public static void main(String[] args) {
try {
InputStreamReader isr1 = new InputStreamReader
(new FileInputStream("D:\\IO\\utf8.txt"));
InputStreamReader isr2 = new InputStreamReader
(new FileInputStream("D:\\IO\\utf8.txt"),"UTF-8");
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
}
}
输出流转化过程
- 通过 OutputStreamWriter 类完成字符到字节的编码过程,由StreamEncoder完成编码过程
- OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
- OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
public class OutputStreamWriter extends Writer {
private final StreamEncoder se;
public OutputStreamWriter(OutputStream out, String charsetName)
throws UnsupportedEncodingException
{
super(out);
if (charsetName == null)
throw new NullPointerException("charsetName");
se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
}
public OutputStreamWriter(OutputStream out) {
super(out);
try {
se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
}
public class IOTest {
public static void main(String[] args) {
try {
OutputStreamWriter isr1 = new OutputStreamWriter
(new FileOutputStream("D:\\IO\\gbk.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter
(new FileOutputStream("D:\\IO\\gbk1.txt") , "GBK");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
基于磁盘的操作接口
- 将数据持久化到物理磁盘
- 数据在磁盘的唯一最小描述就是文件
- 文件是系统与磁盘驱动器交互的最小单元
- File类是唯一代表磁盘文件本身的对象,方法有文件是否存在、创建、删除、重命名等
- File并不代表一个真实存在的文件对象,通过制定路径返回代表这个路径的虚拟对象,可能是一个真实的文件也可能是一个包含多级文件的目录
创建File对象
//通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
File file = new File("E://test//Test.txt");
//从父路径名字符串和子路径名字符串创建新的 File实例。
File file = new File("E:\\test","Test.txt");// 创建一个URI对象,指向新文件的位置
URI uri = URI.create("file:///path/to/the/newfile.txt");
// 使用URI创建File对象
File myFile = new File(uri);
public class File
implements Serializable, Comparable<File>
{
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
public File(String parent, String child) {
if (child == null) {
throw new NullPointerException();
}
if (parent != null) {
if (parent.equals("")) {
this.path = fs.resolve(fs.getDefaultParent(),
fs.normalize(child));
} else {
this.path = fs.resolve(fs.normalize(parent),
fs.normalize(child));
}
} else {
this.path = fs.normalize(child);
}
this.prefixLength = fs.prefixLength(this.path);
}
public File(URI uri) {
// Check our many preconditions
if (!uri.isAbsolute())
throw new IllegalArgumentException("URI is not absolute");
if (uri.isOpaque())
throw new IllegalArgumentException("URI is not hierarchical");
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equalsIgnoreCase("file"))
throw new IllegalArgumentException("URI scheme is not \"file\"");
if (uri.getAuthority() != null)
throw new IllegalArgumentException("URI has an authority component");
if (uri.getFragment() != null)
throw new IllegalArgumentException("URI has a fragment component");
if (uri.getQuery() != null)
throw new IllegalArgumentException("URI has a query component");
String p = uri.getPath();
if (p.equals(""))
throw new IllegalArgumentException("URI path component is empty");
// Okay, now initialize
p = fs.fromURIPath(p);
if (File.separatorChar != '/')
p = p.replace('/', File.separatorChar);
this.path = fs.normalize(p);
this.prefixLength = fs.prefixLength(this.path);
}
}
File常用方法
方法 | 返回值类型 | 作用 |
getName() | String | 获取文件名称 |
getParent() | String | 获取文件的父路径(字符串),若无指定父目录,则返回null |
getParentFile() | File | 获取文件的父路径(抽象路径名),若无指定父目录,则返回null |
canRead() | boolean | 判断文件是否可读 |
canWrite() | boolean | 判断文件是否可写 |
canExecute() | boolean | 判断文件是否执行 |
exists() | boolean | 判断文件是否存在 |
length() | long | 文件的长度(字节) |
getAbsolutePath() | String | 获取文件的绝对路径(字符串) |
getAbsoluteFile() | File | 获取文件的绝对路径(抽象路径名) |
isFile() | boolean | 判断文件是否为标准文件或是否存在 |
isDirectory() | boolean | 判断文件是否是目录 |
isHidden() | boolean | 判断文件是否是隐藏文件 |
latModified() | long | 返回文件最后修改时间 |
renameTo() | boolean | 文件重命名 |
基于网络操作的接口
- 数据写入互联网中以供其他电脑访问
- Socket是猫叔计算机之间完成互相通信的一种抽象定义
- 多数基于TCP/IP的流套接字,它是一种稳定通信协议
TCP协议的三次握手
- 发送端=>发送带有SYN标志的数据包=>接收端 (第一次握手)
- 接收端=>发送带有SYN+ACK标志的数据包=>发送端(第二次握手)
- 发送端=>发送带有=ACK标志的数据包=>接收端(第三次握手)
SYN:Synchronize Sequence Numners,表示同步序列序号,是TCP/IP建立连接使用的握手信号
ACK:AcKnowledge character,确认字符,表示发送的数据已确认接收无误
完成三次握手后,客户端与服务器应用程序可以开始传送数据
scoket客户端
public class ClientTest {
public static void main(String[] args) {
try {
//通过IP和端口与服务端连接
Socket socket=new Socket("127.0.0.1",8080);
//将字符流转换为字节流,并输出
BufferedWriter bufferedWriter=new BufferedWriter
(new OutputStreamWriter(socket.getOutputStream()));
String str="Hello,我是客户端!";
bufferedWriter.write(str);
bufferedWriter.flush();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
socket服务端
public class ServerTest {
public static void main(String[] args) {
try {
//初始化服务端socket并绑定8080端口
ServerSocket serverSocket = new ServerSocket(8080);
//循环监听客户端请求
while (true) {
//等待客户端连接
Socket socket = serverSocket.accept();
//将字节流转换成字符流,读取客户端输入的内容
BufferedReader bufferedReader = new BufferedReader
(new InputStreamReader(socket.getInputStream()));
//读取一行数据
String str = bufferedReader.readLine();
//输出打印
System.out.println("服务端接收到客户端信息:" + str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
- 注意,客户端只有与服务端建立三次握手成功之后,才会发送数据,而 TCP/IP 握手过程,底层操作系统已经帮我们实现了!
- 当连接已经建立成功,服务端和客户端都会拥有一个 Socket 实例,每个 Socket 实例都有一个 InputStream 和 OutputStream
- 当 Socket 对象创建时,操作系统将会为 InputStream 和 OutputStream 分别分配一定大小的缓冲区,数据的写入和读取都是通过这个缓存区完成的
IO的工作方式
BIO:同步阻塞 IO
- 一个客户端一个线程
- 由独立的Acceptor线程负责监听客户端的连接
- 服务端wilhe(ture)中调用accept方法等待客户端连接
- 接收一个请求,只能等待当前连接客户端操作完成才能接收下一个请求
- 可以通过多线程的方式连接
- 对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发效率和更好的维护性
多线程方式
public class MoreClientTest {
public static void main(String[] args) {
//创建5个线程
for (int i = 0; i < 5; i++) {
final int m = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
//通过IP和端口与服务端连接
Socket socket = new Socket("127.0.0.1", 8080);
//将字符流转换为字节流,并输出
BufferedWriter bufferedWriter = new BufferedWriter
(new OutputStreamWriter(socket.getOutputStream()));
String str = "Hello,我是客户端" + m + "号!";
bufferedWriter.write(str);
bufferedWriter.flush();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
public class MoreServerTest {
public static void main(String[] args) {
try {
//初始化服务端socket并绑定8080端口
ServerSocket serverSocket = new ServerSocket(8080);
//创建5个线程来监听客户端
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//循环监听客户端请求
while (true) {
//等待客户端连接
Socket socket = serverSocket.accept();
//将字节流转换成字符流,读取客户端输入的内容
BufferedReader bufferedReader = new BufferedReader
(new InputStreamReader(socket.getInputStream()));
//读取一行数据
String str = bufferedReader.readLine();
//输出打印
System.out.println("服务端接收到客户端信息:" + str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
线程池和任务队列(伪异步BIO)
- 采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架
- Java 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理
socket客户端
public class BIOClient {
public static void main(String[] args) {
//创建15个线程,模拟15个客户端向服务端发送请求
for (int i = 0; i < 15; i++) {
final int m = i;
new Thread(new Runnable() {
@Override
public void run() {
Socket socket = null;
PrintWriter printWriter = null;
BufferedReader bufferedReader = null;
try {
//通过IP和端口与服务端连接
socket = new Socket("127.0.0.1", 8080);
//将字符流转换为字节流,并输出
printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
String str = "Hello,我是客户端" + m + "号!";
printWriter.println(str);
//从输入流程中读取服务端返回的消息,将字节流转化成字符流
bufferedReader = new BufferedReader(new InputStreamReader((socket.getInputStream())));
//读取内容
String result = bufferedReader.readLine();
//打印服务端返回的信息
System.out.println("客户端发送:" + str + " => 收到服务端返回内容:" + result);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(bufferedReader!=null){
bufferedReader.close();
}
if(printWriter!=null){
printWriter.close();
}
if(socket!=null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
}
socket服务端
public class BIOServer {
public static void main(String[] args) {
try {
//初始化服务端socket并绑定8080端口
ServerSocket serverSocket = new ServerSocket(8080);
//在线程池中创建5个固定大小线程,来处理客户端请求
ExecutorService executorService = Executors.newFixedThreadPool(5);
while (true) {
//等待客户端连接
Socket socket = serverSocket.accept();
executorService.execute(new Runnable() {
@Override
public void run() {
BufferedReader bufferedReader = null;
PrintWriter printWriter = null;
try {
//将字节流转换成字符流,读取客户端输入的内容
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//读取一行数据
String str = bufferedReader.readLine();
//输出打印
System.out.println("服务端接收到客户端信息:" + str);
//向服务端返回信息,字符流转换为字节流,并输出
printWriter=new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
printWriter.println("服务端返回消息结果:"+str);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(bufferedReader!=null){
bufferedReader.close();
}
if(printWriter!=null){
printWriter.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
- 客户端数量是 15,服务端使用 java 线程池来处理任务,线程数量为 5 个
- 服务端不用为每个客户端都创建一个线程,
- 由于线程池可以设置消息队列的大小和最大线程数资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机
- 底层仍然是同步阻塞的 BIO 模型,当面对十万甚至百万级连接的时候,采用NIO模型
- 适用于连接数目比较小且固定的架构,这对服务器资源要求比较高
NIO:同步非阻塞 IO
- 新增了 Channel、Selector、Buffer 等抽象概念,支持面向缓冲、基于通道的 I/O 操作方法。
- NIO 提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现
- 对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
- NIO 引入了 Channel、Buffer 和 Selector 就是想把 IO 传输过程中涉及到的信息具体化
- 适用于连接数目多且连接比较短(轻操作)的架构,例如聊天服务器
NIO 中的核心概念
Channel:可以理解为通道
Selector:
- 可以理解为选择器,可以翻译为多路复用器
- 用于检查一个或多个 Channel(通道)的状态是否处于连接就绪、接受就绪、可读就绪、可写就绪
- 可以实现单线程管理多个 channels,也就是可以管理多个网络连接
- 相比传统方式使用多个线程来管理 IO,Selector 使用了更少的线程就可以处理通道了,并且实现网络高效传输
Buffer:可以理解为数据缓冲流
socket客户端
public class NIOClient {
public static void main(String[] args) {
//创建15个线程,模拟15个客户端向服务端发送请求
for (int i = 0; i < 15; i++) {
final int m = i;
new Thread(new Runnable() {
@Override
public void run() {
Socket socket = null;
PrintWriter printWriter = null;
BufferedReader bufferedReader = null;
try {
//通过IP和端口与服务端连接
socket = new Socket("127.0.0.1", 8080);
//将字符流转换为字节流,并输出
printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()),true);
String str = "Hello,我是客户端" + m + "号!";
printWriter.println(str);
//从输入流程中读取服务端返回的消息,将字节流转化成字符流
bufferedReader = new BufferedReader(new InputStreamReader((socket.getInputStream())));
//读取内容
String result = bufferedReader.readLine();
//打印服务端返回的信息
System.out.println("客户端发送:" + str + " => 收到服务端返回内容:" + result);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(bufferedReader!=null){
bufferedReader.close();
}
if(printWriter!=null){
printWriter.close();
}
if(socket!=null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
}
socket服务端
public class NIOServer {
public static void main(String[] args) {
try {
//打开服务器套接字通道
ServerSocketChannel ssc=ServerSocketChannel.open();
//服务器配置为非阻塞
ssc.configureBlocking(false);
//进行服务绑定,监听8080端口
ssc.bind(new InetSocketAddress(8080));
//通过open()方法找到Selector选择器
Selector selector=Selector.open();
//将ssc注册到selector,并让selector监听同道中人的接受事件
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true){
//查询指定事件已经就绪的通道数量,如果为0就跳过
int readyChannels=selector.select();
if (readyChannels==0){
continue;
}
//通过选择器获取所有的key集合
Set<SelectionKey> selectedKeys=selector.selectedKeys();
Iterator<SelectionKey> keyIterator=selectedKeys.iterator();
while (keyIterator.hasNext()){
SelectionKey key=keyIterator.next();
//判断状态是否有效
if(!key.isValid()){
continue;
}
if(key.isAcceptable()){
//通道接受就绪,接受事件下通道只能是服务器套接字通道
ServerSocketChannel serverSocketChannel= (ServerSocketChannel) key.channel();
SocketChannel clientChannel=serverSocketChannel.accept();
clientChannel.configureBlocking(false);
//将通道注册到选择器并监听通道中的可读事件
clientChannel.register(selector,SelectionKey.OP_READ);
System.out.println("接收到NIO客户端连接,地址:"+clientChannel.getRemoteAddress());
}
else if(key.isReadable()){
//通道可读就绪
//创建一个容量为1024的字节数组
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
//获取通道
SocketChannel clientChannel= (SocketChannel) key.channel();
//从通道中读取数据到缓冲中
int numberRead=clientChannel.read(byteBuffer);
byteBuffer.flip();
//获取缓冲数据
String result=new String(byteBuffer.array(),0,numberRead);
System.out.print("服务端接收到客户端发送的消息:"+result);
//将通道注册到选择器,并监听通道中的可写事件
clientChannel.register(selector,SelectionKey.OP_WRITE);
}else if(key.isWritable()){
//通道可写就绪
//创建一个容量为1024的字节数组
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
SocketChannel clientChannel= (SocketChannel) key.channel();
byteBuffer.put("服务端发送server send".getBytes());
byteBuffer.flip();
clientChannel.write(byteBuffer);
//将通道注册到选择器并监听通道中的可读事件
clientChannel.register(selector,SelectionKey.OP_READ);
//写完关闭通道
clientChannel.close();
}
//该事件已经处理,可以丢弃
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
SocketChannel
客户端
public class NIOClient2 {
public static void main(String[] args) {
try {
//写入缓冲buffer
ByteBuffer writeBuffer= ByteBuffer.allocate(1024);
//读取缓冲buffer
ByteBuffer readBuffer=ByteBuffer.allocate(1024);
//打开socket通道
SocketChannel sc=SocketChannel.open();
//设置为非阻塞
sc.configureBlocking(false);
//连接服务器地址和端口
sc.connect(new InetSocketAddress("127.0.0.1",8080));
//打开选择器
Selector selector=Selector.open();
//注册连接服务器socket的冯作,并且让selector监听通道中的连接事件
sc.register(selector, SelectionKey.OP_CONNECT);
//查询指定的事件已经就绪的通道数量,如果为0跳过
while (selector.select()>0){
//通过选择器获取所有key的集合
Set<SelectionKey> keys= selector.selectedKeys();
Iterator<SelectionKey> keyIterator=keys.iterator();
while (keyIterator.hasNext()){
SelectionKey key=keyIterator.next();
if(key.isConnectable()){
//判断此通道上是否正在进行连接操作
if(sc.finishConnect()){
//完成连接
sc.register(selector,SelectionKey.OP_WRITE);
System.out.println("连接目标服务器...");
}
}
else if(key.isWritable()){
//写入数据
//将缓冲区清空以备下次使用
writeBuffer.clear();
writeBuffer.put("Hello,我是客户端".getBytes());
writeBuffer.flip();
sc.write(writeBuffer);
//注册到选择器中,并监听读取事件操作
sc.register(selector,SelectionKey.OP_READ);
}
else if(key.isReadable()){
//读取数据
SocketChannel client= (SocketChannel) key.channel();
//将缓冲区清空以备下次使用
readBuffer.clear();
//从通道中读取数据至缓冲中
client.read(readBuffer);
readBuffer.flip();
String result= Charset.defaultCharset().newDecoder().decode(readBuffer).toString();
System.out.println("客户端接收到服务端:"+client.socket().getRemoteSocketAddress()+",返回的信息:"+result);
//注册到选择器中,并监听写入事件操作
sc.register(selector,SelectionKey.OP_WRITE);
//读取完关闭客户端通道
client.close();
}
}
keyIterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
AIO:异步非阻塞 IO
- 线程发起IO请求,不需要阻塞,立即返回值
- 也不需要定时轮询查询结果,异步IO操作之后会回调通知调用方
- 适用于连接数目多气人连接比较长(重操作)的架构,例如相册服务器