问题
- 什么是nio
- nio Buffer
- nio Channel(FileChannel,SocketChannel,ServerSocketChannel )
- nio 多路复用
- DirectBuffer直接内存
解答
- 什么是nio
NIO是为了弥补传统I/O工作模式的不足而研发的,NIO的工具包提出了基于Selector(选择器)、Buffer(缓冲区)、Channel(通道)的新模式;Selector(选择器)、可选择的Channel(通道)和SelectionKey(选择键)配合起来使用,可以实现并发的非阻塞型I/O能力 - nio 缓存器
Buffer相当于一个内存数组,对其的写入和读取都是基于内存的,所以效率更高。
Buffer是一个接口,对应有7种Buffer的实现,对应7种数据类型,除了Boolean类型。
其实核心是最后的 ByteBuffer,前面的一大串类只是包装了一下它而已,我们使用最多的通常也是 ByteBuffer。
我们应该将 Buffer 理解为一个数组,IntBuffer、CharBuffer、DoubleBuffer 等分别对应 int[]、char[]、double[] 等。
MappedByteBuffer 用于实现内存映射文件,也不是本文关注的重点。
我觉得操作 Buffer 和操作数组、类集差不多,只不过大部分时候我们都把它放到了 NIO 的场景里面来使用而已。下面介绍 Buffer 中的几个重要属性和几个重要方法 - Channel
Chanel是一个通道,和Buffer配合使用。例如:
public class BufferExampleTest {
@Test
public void testBuffer(){
/*
capacity: 内存数组的最大容量,一旦初始化后不能修改
postsition: 当在写入模式的时候,表示当前写入的位置。使用flip切换到读取模式的时候,position重置为0
limit:写入模式时表示写入的最大量,等于capacity。读取模式表示读取的最大量,等于写入模式的position
*/
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
//ByteBuffer数组中每位是一个byte,写入int意味着最大能存128,超过该值会溢出
byteBuffer.put((byte)2);
byteBuffer.put((byte)3);
byteBuffer.put((byte)4);
for(int i =0 ; i<byteBuffer.array().length;i++){
// System.out.println(byteBuffer.array()[i]);
}
//将写模式切换到读模式
byteBuffer.flip();
for(int i =0; i<byteBuffer.limit();i++){
System.out.println(byteBuffer.get());
}
}
@Test
public void testFloatBuffer(){
FloatBuffer floatBuffer = FloatBuffer.allocate(10);
floatBuffer.put(2.0f);
floatBuffer.put(200.f);
floatBuffer.flip();
while(floatBuffer.hasRemaining()){
System.out.println(floatBuffer.get());
}
}
@Test
public void testChannel() throws IOException {
/*
Channel:
*/
RandomAccessFile randomAccessFile = new RandomAccessFile("copy.txt","rw");
FileChannel fileChannel = randomAccessFile.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
int hasRead = fileChannel.read(byteBuffer);
while(hasRead != -1){
byteBuffer.flip();
while(byteBuffer.hasRemaining()){
System.out.println((char) byteBuffer.get());
}
byteBuffer.clear();
hasRead = fileChannel.read(byteBuffer);
}
randomAccessFile.close();
}
@Test
public void testChannelFile2() throws IOException {
FileOutputStream fout = new FileOutputStream("name.txt");
FileChannel channel = fout.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
String name = "My name is Tom";
for(int i =0; i<name.length();i++){
byteBuffer.put(name.getBytes()[i]);
}
byteBuffer.flip();
channel.write(byteBuffer);
channel.close();
fout.close();
}
@Test
public void testSocketChannel() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",3333));
SocketChannel socketChannel = serverSocketChannel.accept();
ByteBuffer byteBuffer = ByteBuffer.allocate(1000);
byteBuffer.put("hello this is server".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
// socketChannel.close();
}
@Test
public void testSocketClientChannel() throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",3333));
ByteBuffer byteBuffer = ByteBuffer.allocate(1000);
socketChannel.read(byteBuffer);
byteBuffer.flip();
StringBuffer sb = new StringBuffer();
while(byteBuffer.hasRemaining()){
sb.append((char)byteBuffer.get());
}
System.out.println("从服务端收到的数据:"+sb.toString());
}
}
- DirectBuffer
DirectBuffer称之为对外内存或者直接内存。区别于堆内存,受系统内存的限制。是在jdk1.4 nio包中加入的
创建方式:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
构造方法源码:
DirectByteBuffer(int cap) { // package-private
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 {
//本地方法,调用c语言中的malloc方法申请一块内存
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;
}
为什么需要堆外内存?
在以前java io的流的处理上,是有一个copy的过程,将HeapBuffer 在内存中的数据拷贝到一块直接内存。因为如果直接使用HeapBuffer进行io读取和写入,当gc的时候,字节数组的地址会改变,无法保证正常的写入或读取。而直接内存的好处就是零拷贝,而且不受gc的影响。很少被回收。