- 1. 前言
- 2. NIO源码初探
- 2.1 缓冲区补充
- 2.1.1 只读缓冲区(asReadOnlyBuffer)
- 2.1.2 直接缓冲区(allocateDirect)
- 2.1.3 内存映射
- 2.2 Selector
- 3. 其他
- 3.1 访问修饰符的作用范围
- 3.2 使用递归来反转字符串
- 3.3 使用递归来逆序一个栈
这里再次强调一个概念:
所谓“高并发”是指1ms内至少同时有上千个连接请求准备好。
在前面的几篇博客中,我们已经可以简单的使用NIO来实现一些简单的消息收发。在这篇博客中,将看看其实现源码。
2. NIO源码初探在NIO中有三个核心对象:缓冲区(Buffer)、选择器(Selector)和通道(Channel)。
对于缓冲区这里补充两个之前没有使用的缓冲区和内存映射。
2.1 缓冲区补充 2.1.1 只读缓冲区(asReadOnlyBuffer)可以通过调用缓冲区的asReadonlyBuffer()方法,将任何常规缓冲区转换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据。和其名称一样只读缓冲区可以读取它们,但是不能向它们写入数据。需要注意的是,如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化。比如下面的案例:
public class ReadOnlyBufferDemo {
public static void main(String[] args) {
String message = "Hello!";
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
byteBuffer.put(message.getBytes());
ByteBuffer readonlyBuffer = byteBuffer.asReadOnlyBuffer();
// 改变原缓冲区的内容
byteBuffer.clear();
byteBuffer.put("World_Hello!".getBytes());
byteBuffer.flip();
// 读取只读ByteBuffer
readOnlyBuffer.flip();
byte[] datas = new byte[readOnlyBuffer.limit()];
int index = 0;
while (readOnlyBuffer.remaining() > 0) {
datas[index++] = readOnlyBuffer.get();
}
System.out.println("消息内容:" + new String(datas, 0, datas.length));
}
}
结果:
从上面的案例中可以得出两个结论:
- 如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化。但是如果将只读的缓冲区传递给调用程序,在一定程度上对数据具有保护作用。
- 只读缓冲区的范围在调用asReadonlyBuffer()方法之后,其数据范围就固定了,对应的就是原数据的position和limit。
需要注意的是,只可以把常规缓冲区转换为只读缓冲区,而不能将只读缓冲区转换为可写的缓冲区。
2.1.2 直接缓冲区(allocateDirect)下面为了测试,以文件拷贝的时间来比较效率:
public class DirectBufferDemo {
private static final String PATH = "D://a.png";
public static void main(String[] args) {
long time_1 = System.currentTimeMillis();
copyFileByBuffer();
System.out.println("使用缓冲区文件拷贝时间:"+ (System.currentTimeMillis() - time_1));
long time_2 = System.currentTimeMillis();
copyFileByDirectBuffer();
System.out.println("使用直接缓冲区文件拷贝时间:" + (System.currentTimeMillis() - time_2));
}
public static void copyFileByDirectBuffer(){
FileOutputStream fileOutputStream = null;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(PATH);
fileOutputStream = new FileOutputStream("b.png");
FileChannel inputChannel = fileInputStream.getChannel();
FileChannel outputChannel = fileOutputStream.getChannel();
// 这里使用allocateDirect
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(512);
int len = -1;
while((len = inputChannel.read(byteBuffer)) != -1){
byteBuffer.flip();
outputChannel.write(byteBuffer);
byteBuffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fileOutputStream != null) {
fileOutputStream.close();
}
if(fileInputStream != null){
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void copyFileByBuffer(){
FileOutputStream fileOutputStream = null;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(PATH);
fileOutputStream = new FileOutputStream("a.png");
FileChannel inputChannel = fileInputStream.getChannel();
FileChannel outputChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
int len = -1;
while((len = inputChannel.read(byteBuffer)) != -1){
byteBuffer.flip();
outputChannel.write(byteBuffer);
byteBuffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fileOutputStream != null) {
fileOutputStream.close();
}
if(fileInputStream != null){
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
2.1.3 内存映射
public class MappedBufferDemo {
private static final String PATH = "D://a.png";
public static void main(String[] args) {
long time_1 = System.currentTimeMillis();
copyFileByBuffer();
System.out.println("使用缓冲区文件拷贝时间:"+ (System.currentTimeMillis() - time_1));
long time_2 = System.currentTimeMillis();
copyFileByMappedBuffer();
System.out.println("使用IO映射缓冲区文件拷贝时间:" + (System.currentTimeMillis() - time_2));
}
public static void copyFileByMappedBuffer(){
FileOutputStream fileOutputStream = null;
FileInputStream fileInputStream = null;
try {
File file = new File(PATH);
fileInputStream = new FileInputStream(file);
fileOutputStream = new FileOutputStream("b.png");
FileChannel inputChannel = fileInputStream.getChannel();
FileChannel outputChannel = fileOutputStream.getChannel();
MappedByteBuffer buffer = null;
int len = 0, length = (int) file.length();
while(len < length){
if(length - len > 1024){
buffer = inputChannel.map(FileChannel.MapMode.READ_ONLY, len, 1024);
len += 1024;
} else{
buffer = inputChannel.map(FileChannel.MapMode.READ_ONLY, len, length - len);
len = length;
}
// buffer.flip(); 因为本身是可读的,所以不需要flip()
outputChannel.write(buffer);
buffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fileOutputStream != null) {
fileOutputStream.close();
}
if(fileInputStream != null){
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void copyFileByBuffer(){
FileOutputStream fileOutputStream = null;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(PATH);
fileOutputStream = new FileOutputStream("a.png");
FileChannel inputChannel = fileInputStream.getChannel();
FileChannel outputChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
int len = -1;
while((len = inputChannel.read(byteBuffer)) != -1){
byteBuffer.flip();
outputChannel.write(byteBuffer);
byteBuffer.clear();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fileOutputStream != null) {
fileOutputStream.close();
}
if(fileInputStream != null){
fileInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.2 Selector
在使用NIO多路复用的时候,我们需要使用Selector.open()方法来得到一个Selector实例。方法的背后:
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
因为所用的系统为Windows,这里JDK中所使用的SelectorProvider为WindowsSelectorProvider。即通过provider()方法得到一个WindowsSelectorProvider对象,然后使用openSelector()方法来创建一个WindowsSelectorImpl的Selector对象。
3. 其他
一些遗忘的知识点:
3.1 访问修饰符的作用范围
public class SelectorDemo {
public static int one = 10;
protected static int value = 10;
static int other = 10;
private static int two = 10;
}
需要注意的是,protected和默认的访问修饰符在同包中可以访问。
3.2 使用递归来反转字符串不引入该方法外的变量。
public String reverse(String str){
int len = str.length();
if(len <= 1){
return str;
}
String left = str.substring(0, len / 2);
String right = str.substring(len / 2, len);
return reverse(right) + reverse(left);
}
3.3 使用递归来逆序一个栈
因为栈的操作只在一端,所有我们每次只能删除栈顶元素,然后添加新元素到栈顶。
public Stackreverse(Stack stack){ if(stack.isEmpty()){ return stack; } int last = getAndRemoveLastElement(stack); // 取得当前栈底元素 reverse(stack); stack.push(last); return stack; } public int getAndRemoveLastElement(Stack stack){ int result = stack.pop(); if(stack.isEmpty()){ return result; }else{ int last = getAndRemoveLastElement(stack); //递归到找到最后一个元素,命名为last stack.push(result); //其他名称仍叫result,要重新压入栈 return last; //只返回last一个元素 } }



