共计 5271 个字符,预计需要花费 14 分钟才能阅读完成。
程序读取数据模型
传统 IO 和 NIO 的不同
传统 IO 特点
- 面向流。
- 单向(只能读或只能写)。
- 程序读写间接和操作系统交互。
- 读写线程阻塞。
NIO 特点
- 面向缓冲区。
- 双向(既能读,又能写)。
- 程序读写通过 Channel,程序和 Channel 之间的交互通过缓冲区。
- 读写线程不阻塞。(然而 selector 的 select() 会阻塞)。
NIO 读取数据模型
NIO 在读取数据时,是数据通过 Channel 写入缓冲区,程序从缓冲区读取数据。写数据也是同理。
NIO 四大外围组件
NIO 有 4 个外围组件,它们是:
- Buffer:为了读或写,存储数据的容器。
- Channel:与某些组件建设连贯,相似 Java IO 中的流,进行读或写操作,然而和 IO 流不同的是,Channel 是双向的。
- Charsets:蕴含字符集(charsets)、解码器(decoders)、编码器(encoders),能够用来进行 byte 与 unicode 的转换。
- Selector:通过 Selector,能够与多个 Channel 一起工作。
Buffer
Buffer 是一个存储固定数据大小、存储 Java 根本数据类型数据的容器。一个 Buffer 由上面这些组成:
• Capacity:容器容量 | |
• Limit: 不能读或写的索引 | |
• Position: 下一个读或写的元素索引 | |
• Flip: 切换 IO 操作(读写操作)• Rewind: 设置 position = 0,limit 放弃不变 | |
• Mark: 在 Buffer 标记一个地位 | |
• Reset: 将 position 重置为 mark 的地位 |
间接字节缓冲区
Java 能够应用 ByteBuffer 的 allocateDirect 办法创立间接缓冲区:
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
相比拟于非间接缓冲区。IO 效率会更高。因为咱们在应用非间接缓冲区时,操作系统会创立一个长期间接缓冲区,将非间接缓冲区中的内容读入创立的长期间接缓冲区再进行操作。如果单从这点来看,应用间接缓冲区是 IO 最好的抉择。
然而在 Java 中创立间接缓冲区,是间接调用操作系统的 API 办法来创立的,会绕过 JVM 的内存治理,而且 JVM 针对缓冲区也有优化,所以可能应用间接缓冲区不是最好的形式。
往 ByteBuffer 写入数据
public class ByteBufferReadDemo { | |
/** | |
* 读取文件门路 | |
*/ | |
private static final String INPUT_FILE = "./resources/input.txt"; | |
/** | |
* 缓冲区大小 | |
*/ | |
private static final int BYTE_BUFFER_LENGTH = 1024; | |
public static void main(String[] args) { | |
// 通过 IO 流获取 Channel | |
try (FileChannel fileChannel = new FileInputStream(INPUT_FILE).getChannel()) { | |
// 创立 ByteBuffer | |
ByteBuffer byteBuffer = ByteBuffer.allocate(BYTE_BUFFER_LENGTH); | |
StringBuilder content = new StringBuilder(); | |
int read = 0; | |
// 数据从通道读出(写入到 ByteBuffer)while ((read = fileChannel.read(byteBuffer)) != -1) {content.append(new String(byteBuffer.array(), 0, read)); | |
// 让 ByteBuffer 复原如初:position = 0, limit = capacity, 丢掉 mark | |
byteBuffer.clear();} | |
System.out.println(content); | |
} catch (IOException e) {throw new RuntimeException("Unable to read file", e); | |
} | |
} | |
} |
Buffer 读写原理
ByteBuffer 继承 Buffer 抽象类,很重要的三个属性是:
# 读或者写的开始地位 | |
private int position = 0; | |
# 限度读或写的地位 | |
private int limit; | |
# 容量 | |
private int capacity; |
当新创建一个容量为 10 的 ByteBuffer 对象时,它们三个的地位别离为:
此时 ByteBuffer 为写模式,能够从 position 开始写,limit 是最多能写到的地位,此时 limit = capacity。
随后咱们往 buffer 写入 1234,buffer 内部结构变为:
position 挪动到最小的一个可写地位 5(position = 5)。
此时咱们想要将 buffer 数据读出,就要转成读模式(调用 flip() 办法):
position 挪动到 buffer 中第一个可读的地位,limit 挪动到 buffer 中最初一个可读的地位。
咱们将数据全副读出,此时 buffer 外部变为:
此时 position = limit,证实曾经没有可读。
此时咱们应用 clear(),将 ByteBuffer 复原,此时 buffer 回到写模式(默认是写模式):
compact()和 clear()的区别就是,clear() 会间接让 buffer 回到刚创立的状态,而 compact() 会压缩还没被读的数据。比方咱们只读了 1 和 2:
此时调用 compact(),buffer 内部结构会变为:
Channel
能够通过 Channel 对 Buffer 读写。
创立一个 FileChannel:
// 创立一个 File 文件对象 | |
final File file = new File(FileChannelReadExample.class.getClassLoader().getResource(path).getFile()); | |
// 依据须要读或者须要写创立不同的 FileChannel | |
return fileOperation == FileOperation.READ ? new FileInputStream(file).getChannel() : new FileOutputStream(file).getChannel(); |
Charsets
蕴含 charsets、decoders、encoders。通过 Charsets 能够进行 unicode 与 byte 的转换。
编码与解码
编码(Encoding):一序列字符 -> 字节。
解码(Decoding):字节 -> 字符。
Charset 应用
// 拿到 UTF-8 字符集 | |
Charset utf8Charset = Charset.forName("UTF-8"); | |
// 编码 | |
ByteBuffer byteBuffer = utf8Charset.encode("zhangsan"); | |
// 解码 | |
System.out.println(utf8Charset.decode(byteBuffer)); |
Selector
Selector
能够多路复用 SelectableChannel
,当 SelectableChannel
有 IO 事件产生时,Selector
就会告诉咱们的程序。
这里的 IO 事件(SelectableChannel
连贯 Selectors
的事件)包含:
- Connect
- Accept
- Read
- Write
SelectableChannel
:能够多路复用的连贯 Selecotr 的通道。
Server/Client Demo
应用 Java NIO 实现一个简略的 Server、Client 示例:
Server
public class Server { | |
protected static final String HOST = "127.0.0.1"; | |
protected static final int PORT = 8899; | |
public static void main(String[] args) { | |
try {ServerSocketChannel serverSocket = ServerSocketChannel.open(); | |
InetSocketAddress hostAddress = new InetSocketAddress(HOST, PORT); | |
// 绑定端口 | |
serverSocket.bind(hostAddress); | |
// 设置为非阻塞 | |
serverSocket.configureBlocking(false); | |
// 创立 selector | |
Selector selector = Selector.open(); | |
// Channel 注册到 selector(ACCEPT 事件)serverSocket.register(selector, serverSocket.validOps(), null); | |
while (true) { | |
// select 是阻塞的 | |
int selectKeyNums = selector.select(); | |
if (selectKeyNums > 0) {Set<SelectionKey> selectionKeys = selector.selectedKeys(); | |
Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator(); | |
while (selectionKeyIterator.hasNext()) {SelectionKey selectionKey = selectionKeyIterator.next(); | |
if (selectionKey.isAcceptable()) { | |
// 注册读事件 | |
registerRead(serverSocket, selector); | |
} else if (selectionKey.isReadable()) { | |
// 解决写事件 | |
dealRead(selectionKey); | |
} else {System.out.println("Invalid selection key"); | |
} | |
// 每次事件操作做完要删除,否则会持续读,报错 | |
selectionKeyIterator.remove();} | |
} | |
} | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
public static void registerRead(ServerSocketChannel channel, Selector selector) throws IOException {SocketChannel client = channel.accept(); | |
client.configureBlocking(false); | |
client.register(selector, SelectionKey.OP_READ, null); | |
System.out.println(client.getRemoteAddress() + "connected!"); | |
} | |
public static void dealRead(SelectionKey selectionKey) { | |
try {SocketChannel channel = (SocketChannel) selectionKey.channel(); | |
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); | |
int read = channel.read(byteBuffer); | |
if (read == -1) {selectionKey.channel(); | |
channel.close();} else {byteBuffer.flip(); | |
System.out.println("from" + channel.getRemoteAddress() + "reve a message:" + StandardCharsets.UTF_8.decode(byteBuffer)); | |
} | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
} |
Client
public class Client {public static void main(String[] args) { | |
try {InetSocketAddress hostAddress = new InetSocketAddress(Server.HOST, Server.PORT); | |
SocketChannel client = SocketChannel.open(hostAddress); | |
Scanner sc = new Scanner(System.in); | |
while (sc.hasNextLine()) {client.write(StandardCharsets.UTF_8.encode(sc.next())); | |
} | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
} |
总结
IO 与 NIO 比照:
NIO 四大外围组件:
- ByteBuffer(存储数据的容器)
- Charsets(不便字符和字节的转化)
- Channels(真正 IO 的中央)
- Selectors(多路复用 SelectableChannel)
它们四个互相配合,形成 NIO 的流程。