目录
一 Scatter/Gatter
Java NIO开始支持scatter/gather,scatter/gather用于描述从Channel(译者注:Channel在中文经常翻译为通道)中读取或者写入到Channel的操作。
- 分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
- 聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。
scatter / gather经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体
scatter
//这是分散(scatter)
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
注意buffer首先被插入到数组,然后再将数组作为channel.read() 的输入参数。read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。
Scattering Reads在移动下一个buffer前,必须填满
当前的buffer,这也意味着它不适用于动态消息(译者注:消息大小不固定)。换句话说,如果存在消息头和消息体,消息头必须完成填充(例如 128byte),Scattering Reads才能正常工作。
gatter
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
buffers数组是write()方法的入参,write()方法会按照buffer在数组中的顺序,将数据写入到channel,注意只有position和limit
之间的数据才会被写入。因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据,那么这58byte的数据将被写入到channel中。因此与Scattering Reads相反,Gathering Writes能较好的处理动态消息。
二 transferFrom & transferTo
在 Java NIO 中,如果两个通道中有一个是 FileChannel(该类实现了这两个方法),那你可以直接将数据从一个 channel传输到另外一个 channel。
transferFrom
FileChannel 的 transferFrom() 方法可以将数据从源通道传输到 FileChannel (所以接收方必须是FileChannel)中(译者注:这个方法在 JDK 文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个简单的例子:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(position, count, fromChannel);
方法的输入参数 position 表示从 position 处开始向目标文件写入数据,count 表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。
此外要注意,在 SoketChannel 的实现中,SocketChannel 只会传输此刻准备好的数据(可能不足 count 字节)。因此,SocketChannel 可能不会将请求的所有数据 (count 个字节) 全部传输到 FileChannel 中。
transferTo
transferTo() 方法将数据从 FileChannel 传输到其他的 channel 中。下面是一个简单的例子:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
fromChannel.transferTo(position, count, toChannel);
是不是发现这个例子和前面那个例子特别相似?除了调用方法的 FileChannel 对象不一样外,其他的都一样。
上面所说的关于 SocketChannel 的问题在 transferTo() 方法中同样存在。SocketChannel 会一直传输数据直到目标 buffer 被填满。
三 Pipe
Java NIO 管道是 2 个线程之间的单向数据连接。Pipe有一个 source 通道和一个 sink 通道。数据会被写到 sink 通道,从 source 通道读取。
这里是 Pipe 原理的图示:
创建管道
通过Pipe.open()方法打开管道。例如:
Pipe pipe = Pipe.open();
向管道写数据
要向管道写数据,需要访问 sink 通道。像这样:
Pipe.SinkChannel sinkChannel = pipe.sink();
通过调用 SinkChannel 的write()方法,将数据写入SinkChannel, 像这样:
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
sinkChannel.write(buf);
}
从管道读取数据
从读取管道的数据,需要访问 source 通道,像这样:
Pipe.SourceChannel sourceChannel = pipe.source();
//调用 source 通道的read()方法来读取数据,像这样:
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);
read()方法返回的 int 值会告诉我们多少字节被读进了缓冲区。
四 内存映射文件
内存映射文件非常特别,它允许Java程序直接从内存中读取文件内容,通过将整个或部分文件映射到内存,由操作系统来处理加载请求和写入文件,应用只需要和内存打交道,这使得IO操作非常快。加载内存映射文件所使用的内存在Java堆区之外.(这样就可以避免读取大容量文件时占用过多jvm堆内存)
内存映射文件,是由一个文件到一块内存的映射。使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。
内存映射IO
在传统的文件IO操作中,我们都是调用操作系统提供的底层标准IO系统调用函数 read()、write() ,此时调用此函数的进程(在JAVA中即java进程)由当前的用户态切换到内核态,然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区,然后再把数据从内核IO缓冲区拷贝到进程的私有地址空间中去,这样便完成了一次IO操作。这么做是为了减少磁盘的IO操作,为了提高性能而考虑的,因为我们的程序访问一般都带有局部性,也就是所谓的局部性原理,在这里主要是指的空间局部性,即我们访问了文件的某一段数据,那么接下去很可能还会访问接下去的一段数据,由于磁盘IO操作的速度比直接访问内存慢了好几个数量级,所以OS根据局部性原理会在一次 read()系统调用过程中预读更多的文件数据缓存在内核IO缓冲区中,当继续访问的文件数据在缓冲区中时便直接拷贝数据到进程私有空间,避免了再次的低效率磁盘IO操作(这里只有一次和磁盘的操作:将数据从磁盘搬到内核IO缓冲区,有两次拷贝数据的过程)。其过程如下
Java的内存映射IO的要点
如下为一些你需要了解的java内存映射要点:
java通过java.nio包来支持内存映射IO
。
内存映射文件主要用于性能敏感的应用,例如高频电子交易平台。
通过使用内存映射IO,你可以将大文件加载到内存。
内存映射文件可能导致页面请求错误,如果请求页面不在内存中的话。
映射文件区域的能力取决于于内存寻址的大小。在32位机器中,你不能访问超过4GB或2 ^ 32(以上的文件)。
内存映射IO比起Java中的IO流要快的多。
加载文件所使用的内存是Java堆区之外,并驻留共享内存,允许两个不同进程共享文件。
内存映射文件读写由操作系统完成,所以即使在将内容写入内存后java程序崩溃了,它将仍然会将它写入文件直到操作系统恢复。
出于性能考虑,推荐使用直接字节缓冲而不是非直接缓冲。
不要频繁调用MappedByteBuffer.force()
方法,这个方法意味着强制操作系统将内存中的内容写入磁盘,所以如果你每次写入内存映射文件都调用force()方法,你将不会体会到使用映射字节缓冲的好处,相反,它(的性能)将类似于磁盘IO的性能。
万一发生了电源故障或主机故障,将会有很小的机率发生内存映射文件没有写入到磁盘,这意味着你可能会丢失关键数据。
使用
FileChannel提供了map方法来
把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size)
; 可以把文件的从position开始的size大小的区域映射为内存映像文件,mode指出了 可访问该内存映像文件的方式:
- READ_ONLY,(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)
- READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)
- PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)
MappedByteBuffer是ByteBuffer的子类,其扩充了三个方法:
- force():缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件;
- load():将缓冲区的内容载入内存,并返回该缓冲区的引用;
- isLoaded():如果缓冲区的内容在物理内存中,则返回真,否则返回假;
注:MappedByteBuffer有资源释放的问题:被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭,而这个点是不确定的。在Javadoc中这里描述:A mapped byte buffer and the file mapping that it represents remian valid until the buffer itself is garbage-collected
这里可以看https://www.jianshu.com/p/f90866dcbffc
五 直接缓冲
DirectByteBuffer
是Java用于实现堆外内存的一个重要类
而DirectByteBuffer中的unsafe.allocateMemory(size);
是个一个native
方法,这个方法分配的是堆外内存
,通过C的malloc来进行分配的。分配的内存是系统本地的内存
,并不在Java的内存中,也不属于JVM
管控范围,所以在DirectByteBuffer一定会存在某种方式来操纵堆外内存。
这种方式是直接在堆外分配
一个内存(即,native memory)来存储数据,程序通过JNI
直接将数据读/写
到堆外内存中。因为数据直接写入到了堆外内存中,所以这种方式就不会再在JVM管控的堆内再分配内存来存储数据了,也就不存在堆内内存和堆外内存数据拷贝
的操作了。这样在进行I/O操作时,只需要将这个堆外内存地址传给JNI的I/O
的函数就好了。
六 总结
内存映射就是使用了直接缓冲区
参考资料
1.攻破JAVA NIO技术壁垒
2.http://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-channel.html
3.http://ifeve.com/java-nio-scattergather/
4.https://blog.csdn.net/Evankaka/article/details/48464013
5.堆外内存 之 DirectByteBuffer 详解
来源:https://blog.csdn.net/m0_38060977/article/details/102754540