最近在项目中存储一个内存对象,这个内存对象比较大,于是把它放在文件,通过内存映射的方式访问这个对象。
于是用到了这个nio,因为他可以开辟堆外内存
堆外内存(说非堆不太准确,毕竟非堆区域不止这一块),时分配在C Heap上的Buffer,由于不属于JVM HEAP,管理/监控起来会比较困难,但也会被GC回收。但他是通过PhantomReference来达到的,正常的young gc或者mark and compact的时候不会在内存里移动。DirectByteBuffer 自身是(Java)堆内的,它背后真正承载数据的buffer是在(Java)堆外——native memory中的。这是 malloc() 分配出来的内存,是用户态的。(来自于Java中的Heap Buffer与Direct Buffer - SegmentFault 思否)
我们使用普通的ByteBuffer,那么这个ByteBuffer就会在Java堆内,被JVM所管理当 buf 被JVM所管理时,它会在变成垃圾以后,自动被回收,这个不需要我们来操心。但它有个问题,后面会讲到,在执行GC的时候,JVM实际上会做一些整理内存的工作,也就说buf这个对象在内存中的实际地址是会发生变化的。有些时候,ByteBuffer里都是大量的字节,这些字节在JVM GC整理内存时就显得很笨重,把它们在内存中拷来拷去显然不是一个好主意。(来自于java directBuffer - 搜索结果 - 知乎)
明白这个堆外内存之后,我们就可以创建一个比较大的对象,因为它的地址不会因为GC整理而改变,那么我们就能通过Nio来做MemoryMap.例如把Tire树,写入文件
public class TrieNode {
private byte key;
private String word;
private int wordSize;
private int currentOffset;
private HashMap<Byte, TrieNode> children;
public void write(FileChannel channel, int[] offset) {
try {
long currentOffset = offset[0];
offset[0] = offset[0] + size();
ByteBuffer buffer = ByteBuffer.allocateDirect(size());
buffer.put(key);
buffer.putShort((short) (wordSize));
buffer.putShort((short) children.keySet().size());
for (byte k : children.keySet()) {
TrieNode node = children.get(k);
node.write(channel, offset);
buffer.putInt(offset[0]);
}
if (word != null && wordSize > 0) {
buffer.put(word.getBytes());
}
buffer.flip();
channel.write(buffer, currentOffset);
} catch (IOException e) {
LogUtil.d("___异常" + e.getCause());
e.printStackTrace();
}
}
private int size() {
int size = 1 + 2 + 2 + children.keySet().size() * 4 + wordSize;
return size;
}
}
之后我们想访问这个树,可以通过FileChanel 获取MappedByteBuffer来访问。这样就不用把整个树加载到内存里了。
传统io是插管道,单项流,要么输出流,要么输入流
到这里是第一次去使用Nio,但是Nio的其他用法
1.它是双向的,(轨道和火车的比喻很象形,channel是轨道,buffer是火车)
2.它强制使用buffer,还可以精细操作buffer
3.有非阻塞式支持,仅仅网络交互支持非阻塞式。
简单的使用:
public static void main(String[] args) {
// write your code here
try {
RandomAccessFile r = new RandomAccessFile("./iocompare/text.txt", "r");
FileChannel channel = r.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
channel.read(byteBuffer);
byteBuffer.flip();//
Charset.defaultCharset().decode(byteBuffer);
byteBuffer.clear()
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
这个channel.read,对一1024个字节到bytebuffer里,同样也可以写channel.write。
有了第一个特点既可以读也可以写
2.可以精确操作buffer,就是position, limit,mark,capcity
3.支持非阻塞流
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
// serverSocketChannel.configureBlocking(false);
SocketChannel socketChannel = serverSocketChannel.accept();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
if (socketChannel.read(byteBuffer) !=-1) {
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
这里的使用场景是什么呢?非阻塞,有点像Future,如果不阻塞去拿,拿到的结果肯定是空的,Future的非阻塞场景,我想应该是做兼容的时候用。
另一个也很重要的方面,它在拷贝数据方面很有优势,少一层从内核态到用户态的数据拷贝,所以可以在拷贝大数据时可以考虑使用