byte[] buf=new byte[1024];
InputStream inputStream=new FileInputStream("in.txt");
OutputStream outputStream = new FileOutputStream("out.txt");
int read;
while ((read=inputStream.read(buf))!=-1){
outputStream.write(buf,0,read);
}
inputStream.close();
outputStream.close();
这一段代码是一个经典的将in.txt文件复制到out.txt的一段代码,看起来非常简单,然而其中包含着非常多的操作,下面的图表明了这段代码发生的事情
其中至少包含了4次数据的复制,并且包含了多次用户空间和内核空间的切换.其中**DMA(Direct Memory Access)**叫做直接内存访问
第一次不需要将DMA数据复制到fd关联的数据缓冲区的原因是,这个硬件本身就是通过fd查找到的.
write的时候需要复制到fd相关的内核缓冲区中,因为是另外一个fd
对于数据的复制,用户空间的数据其实是不需要的,因此可以针对这种问题进行优化,即不进行内核态到用户态的数据复制,即使用内存映射.
内存映射可以将内核空间中的内存块关联到用户空间,使得用户空间看起来在操作内核空间一样,底层是使用mmap函数
使用这个函数可以去除内核空间复制到用户空间的操作,但仍然需要从old fd复制到new fd空间
因此,数据的复制操作可以减少为3次.但用户态的切换次数仍然不变
在Java中使用MappedByteBuffer实现这个操作,实际上底层操作的是DirectByteBuffer
FileChannel channel=...
MappedByteBuffer map=channel.map(FileChannel.MapMode.READ_WRITE,0,5);
map.set(1,'b')
FileChannel可以来自于File和IO
除上述操作之外,还可以使用内核2.1之后提供的sendFile操作,进行直接复制的操作来避免用户态的切换
如下图
一共进行3次数据复制,2次上下文切换
这个方式还可以进行优化,其中数据复制到新文件描述符的操作不是必要的,可以通过内存地址的方式寻址到第一次复制的数据.即关联old fd和new fd即可
在Java中使用FileChannel的transferFrom函数和transferTo函数实现
上述使用sendFile的方式仅适用于不需要操作数据的简单复制,如果需要操作数据,最优的方式是mmap的方式
与操作系统的ZeroCopy,netty由于本身不仅仅是数据的复制,大部分情况下,netty都需要进行数据的操作,因此netty更加关注的是避免复制.
netty提供了许多种避免复制的方式,但本质上都是一样的,即通过视图的方式进行,如果熟悉数据库的话马上就能够理解意思了.视图不是真实存在的数据,而是对真实数据的一种访问方式.另外对于不需要操作数据的情况,jdk本身就已经提供了transferTo和transferFrom函数,netty进行了一点封装以便ByteBuf使用
CompositeByteBuf
JDK的ByteBuffer未找到类似的操作
netty使用compositeByteBuf.addComponents相关的函数进行操作
JDK通过ByteBuffer.wrap相关函数进行操作
netty使用Unpooled.wrappedBuffer相关的函数进行操作
JDK使用ByteBuffer.slice()函数进行操作
netty使用ByteBuf.slice相关的函数进行操作
netty使用FileRegion,实际上底层还是使用的JDK的transfer