打算输出一系列Netty源码分析与实践的文章,也作为后端开发学习过程中的沉淀。写作风格会遵循目标导向,关注核心,抽离出知识的Pattern,无价值细节决不花时间。
此文章为第三篇,和大家一块了解下NIO中的核心组件Channel
和Buffer
。
Buffer
顾名思义,本质上就是一个内存缓冲区,作为存储数据的一块内存而已。对于每个非布尔原始数据类型都有一个缓冲区类,不过底层数据存储本质上都是字节数组,只不过给我们提供了方便的特定类型的put()
, get()
。可以类比JDK中字符流和字节流联系。Channel
是对“连接”的一种抽象,比如和硬件设备的连接,网络socket连接,同时它具备了IO操作的能力,比如从网络中读取数据,写出数据到对端。通过channel读取数据的过程是先要从网络中读取数据到Buffer,写数据依然是会通过channrl先写入到Buffer中。下图是对Buffer概念模型:
Buffer的实现主要依赖下面的四个属性:
get()
, put()
操作时,会启动加1mark()
方法会记录下mark的buffer下标位置,当后续调用reset()
, 就会回到当时曾mark的位置,回到此位置继续操作buffer。allocate(int capacity)
: 在堆内存中分配指定容量大小的BufferallocateDirect(int capacity)
: 在堆外内存分配指定大小的Buffer,JVM会通过native I/O方式操作这块分配在堆外的内存,这样少了堆内和堆外内存的拷贝,未来我们会看到很多地方都会出现这玩意。wrap(byte[] array)
: 包装一个字节数据作为buffer下面是一个创建capacity == 256 大小的buffer示例:
ByteBuffer buffer = ByteBuffer.allocate(256);
写入数据到buffer可通过以下方式:
put()
方法read()
方法直接基于buffer操作时,put()
方法有多个重载版本: put(byte b)
, put(byte[] src)
, put(byte[] src, int offset, int length)
, 以及 put(ByteBuffer src)
。
myBuffer.put("QUOTE".getBytes());
下面这个例子,我们使用Channel的read()
方法,它会从某连接中读取字节序列写入到buffer,概念模型如下:
int bytesRead = inChannel.read(myBuffer);
在这里我们可以形象的看到Channel所体现的对文件系统建立连接的抽象。
下面给出一个完整的通过NIO Channel 和
Buffer将字符串数据写入到文本文件的程序:
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.apache.log4j.Logger;
public class MyBufferWriteExample {
private static final Logger logger
= Logger.getLogger(MyBufferWriteExample.class);
private static final int BUFFER_SIZE = 1024;
private static final String FILE_NAME = "c:\\tmp\\output.txt";
private static final String QUOTE
= "If your actions inspire others to dream more, learn "
+ "more, do more and become more, you are a leader.";
public static void main(String[] args) throws IOException {
logger.info("Starting MyBufferWriteExample...");
FileOutputStream fileOS = new FileOutputStream(FILE_NAME);
FileChannel channel = fileOS.getChannel();
try {
ByteBuffer myBuffer = ByteBuffer.allocate(BUFFER_SIZE);
myBuffer.put(QUOTE.getBytes());
myBuffer.flip();
int bytesWritten = channel.write(myBuffer);
logger.info(
String.format("%d bytes have been written to disk...",
bytesWritten));
logger.info(
String.format("Current buffer position is %d",
myBuffer.position()));
} finally {
channel.close();
fileOS.close();
}
}
上图是基于Buffer本身的API get()
操作。
上图展示了应用程序会先将数据写到Buffer,Channel基于write()
操作的调用,将数据从buffer读出刷到文件系统的过程
int bytesWritten = channel.write(myBuffer);
下面给出一个完整的从文本文件读取数据的NIO程序:
package com.avaldes.tutorial;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.apache.log4j.Logger;
public class MyBufferReadExample {
private static final Logger logger
= Logger.getLogger(MyBufferReadExample.class);
private static final int BUFFER_SIZE = 1024;
private static final String FILE_NAME = "c:\\tmp\\output.txt";
public static void main(String[] args) throws IOException {
logger.info("Starting MyBufferReadExample...");
FileInputStream fileIS = new FileInputStream(FILE_NAME);
FileChannel inChannel = fileIS.getChannel();
ByteBuffer myBuffer = ByteBuffer.allocate(BUFFER_SIZE);
while (inChannel.read(myBuffer) > 0) {
myBuffer.flip();
while (myBuffer.hasRemaining()) {
System.out.print((char) myBuffer.get());
}
myBuffer.clear();
}
inChannel.close();
fileIS.close();
}
}
这一部分介绍一些Buffer中内置的其它一些操作,所有的子类都会继承这些方法
会将Buffer由写模式切换到读模式,flip操作时会将limit设置为当前position,mark重置为-1, position指针置为0,表示从0可以开始读,最大可读到limit位置:
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
mark()
方法会记录当前buffer的position位置,在未来的某个时间点,使得我们能够通过调用reset()
, 来将当前position重新设定为之前mark的position。
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
Warning: If the mark has not set, invoking reset() will cause an exception called InvalidMarkException.
clear()
方法会设置position为0,设置limit等于buffer的capacity,mark重置为-1。这是将buffer置为写模式的准备
rewind()
会设置position为0,并丢弃mark(将mark=-1),一般用于从0position开始重新读。
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
本篇文章我们分析了NIO中的两大组件Channel和Buffer,围绕它是什么,存在的意义。通过概念模型展示了所扮演的角色,讲述了基本的使用操作。一旦我们对知识建立了顶层抽象认知,对细节的探索就更有效率和针对性。所以至于JDK是如何实现的这些东西,打开IDEA,对着代码傻子都能看懂吧!
下一篇我们会分析Netty的ByteBuf
,嗯? 又一个buffer?
Java NIO Buffers