引言:为什么会出现NIO?
我们知道JDK很早就提供对IO操作-----BIO(同步阻塞IO),那为什么还要提供NIO呢?翻阅了一些资料以后,我觉得NIO出现的原因主要是为了解决BIO中不必要的线程上下文切换。
下面我们以聊天室来解释:
我们知道BIO是阻塞式IO,当服务端接受到一个Socket连接的时候,我们需要开一个线程来处理这个Socket连接中的网络请求(单线程是无法同时处理多个Socket请求的)。如果说这个聊天室的在线活跃人数非常多的话(比如说,有100万个用户),我们难道要开100万个线程(能开的了吗?),还有聊天室中的每个人不可能一直聊天吧?如果有很多用户只是进入了聊天室,但是什么都不做,那我们给他开一个线程,还要经常切换到该线程,岂不是很浪费资源(线程上下文切换也是消耗资源的!),所以使用BIO对服务器资源要求比较高。为了解决了这个问题,JDK的大佬们在1.4版本就推出了NIO。。。。。
开始瞎掰:
Java NIO 由以下几个核心部分组成:
1 、Buffer
2、Channel
3、Selector
传统的IO操作面向数据流,意味着每次从流中读一个或多个字节,直至完成,数据没有被缓存在任何地方。
NIO操作面向缓冲区,数据从Channel读取到Buffer缓冲区,随后在Buffer中处理数据。
Buffer
根据类型的不同,提供了相应类型的缓冲区
不过一般常用的是ByteBuffer,适合所有。
一块缓存区,内部使用字节数组存储数据,并维护几个特殊变量,实现数据的反复利用。
缓冲区的四大核心属性
mark():把当前的position赋值给mark
public final Buffer mark() {
mark = position;
return this;
}
``
reset():把mark值还原给position
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
clear():一旦读完Buffer中的数据,需要让Buffer准备好再次被写入,clear会恢复状态值,但不会擦除数据。
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
flip():Buffer有两种模式,写模式和读模式,flip后Buffer从写模式变成读模式。
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
rewind():重置position为0,从头读写数据。
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
缓存区存取数据的两个核心方法
- put():存入数据到缓冲区
- get():获取缓冲区中的数据
NIO基本操作
直接缓冲区与间接缓冲区
非直接缓冲区
调用allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中
ByteBuffer的实现类"HeapByteBuffer"的allocate代码:
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
HeapByteBuffer(int cap, int lim) {
super(-1, 0, lim, cap, new byte[cap], 0);
}
HeapByteBuffer通过初始化字节数组hd,在虚拟机堆上申请内存空间。
直接缓冲区
通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中
ByteBuffer的实现类"DirectByteBuffer"的allocateDirect代码:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
DirectByteBuffer通过unsafe.allocateMemory申请堆外内存,并在ByteBuffer的address变量中维护指向该内存的地址。
unsafe.setMemory(base, size, (byte) 0)方法把新申请的内存数据清零。