简介

本文主要是用来记录Channel接口相关实现类的功能和特性

FileChannel

FileChannel主要是从文件中中读写数据的Channel,其实现的接口和继承的对象如下:

public abstract class FileChannel extends AbstractInterruptibleChannel

implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel{

....

}

FileChannel是连接到一个文件的通道,对于它所连接的文件,会维护一个position用来指向文件内容的绝对位置,该绝对位置可以通过position()查询和position(long)进行修改,如果该position修改后,那么输出文件内容到指定ByteBuffer时,将从该position处开始;另外read(ByteBuffer,position)会从指定position开始读取文件内容到ByteBuffer,但并不会修改通道本身position的位置;

URL path = FileChannelTest.class.getClassLoader().getResource("text.txt");

RandomAccessFile accessfile = new RandomAccessFile(new java.io.File(path.getFile()), "r");

FileChannel fileChannel=accessfile.getChannel();

fileChannel.position(5);

ByteBuffer byteBuffer=ByteBuffer.allocate(10);

fileChannel.read(byteBuffer);

byteBuffer.flip();

System.out.println(new String(byteBuffer.array()));

FileChannel具有以下特性:

可以利用read(ByteBuffer,position)或者write(ByteBuffer,position)来在文件的绝对位置上读取或者写入,但是不会改变通道本身的position;

可以利用map(MapMode,position,size)方法将文件映射到内存中,其中position指的是通道的绝对位置,size映射大小,映射方式有三种:

MapMode.READ_ONLY:只读的方式映射到内存,修改文件将抛出ReadOnlyBufferException;

MapMode.READ_WRITE:读写的方式映射到内存,修改后的内存可以通过force()方法写入内存,但是对其他关联到该文件进程可见性是不确定的,可能会出现并发性问题,同时在该模式下,通道必须以rw的方式打开;

MapMode.PRIVATE:私有方式,可以修改映射到内存的文件,但是该修改不会写入内存,同时对其他进程也是不可见的

另外该map中的数据只能等到gc的时候才能清理,同时map一旦创建,将和FileChannel无关,FileChannel关闭也不会对其有影响;

map方法因为将文件直接映射到内存中,因此其读写性能相比FileInputStream和FileOutputStream来说要好一些,但是资源消耗代价也会大些,因此比较适合大文件的读写;

RandomAccessFile accessfile = new RandomAccessFile(

new java.io.File("C:\\Users\\Administrator\\git\\javabase\\JavaBase\\resources\\text.txt"), "rw");

FileChannel fileChannel = accessfile.getChannel();

MappedByteBuffer map = fileChannel.map(MapMode.READ_WRITE, 0, fileChannel.size());

Charset charset=Charset.forName("utf-8");

CharBuffer decode = charset.decode(map.asReadOnlyBuffer());

System.out.println(decode.toString());//读取测试

byte[] chars = "hao hi yo".getBytes();

map.put(chars,0,chars.length);//写入测试,写入位置和position有关

map.force();

fileChannel.close();

可以利用transferTo()/transferFrom()来将bytes数组在两个通道之间来回传递,该性能相对来较快,可以快速实现文件复制,因为FileChannel是将通过JNI(本地方法接口)将文件读取到native堆即堆外内存中,通过DirectrByteBuffer来引用这些数据,这样在实现文件复制或传输时,无需将文件从堆外内存拷贝到java堆中,本质上这就是减少了内核内存和用户内存之间的数据拷贝,从而提升性能;

可以利用lock(position,size,isShared)方法实现对指定文件区域进行加锁,加锁的方式分为共享或互斥,有些操作系统不支持共享锁,因此可通过isShared()方式来判断是否能进行互斥操作;

FileChannel是线程安全的,对于多线程操作,只有一个线程能对该通道所在文件进行修改,

可以通过open()方法开启一个通道,同时也可以通过FileInputStream或者FileOutputStream,RandomAccessFile调用方法getChannel()来获取;

Linux零拷贝

普通传输文件

在linux系统中,用户程序要访问某个文件,传输到网络,可以通过如下代码进行访问

while((n = read(diskfd, buf, BUF_SIZE)) > 0)

write(sockfd, buf , n);

代码虽然简单,但是IO实际上的操作,会经过如下过程:

操作系统会根据read系统调用中的diskfd文件描述父中的位置和长度判断该文件是否存在于内核缓存中,如果是,则直接通过cpu copy直接拷贝到用户程序空间;如果不是,那么操作系统会通过DMA(Direct Memory Access)将文件拷贝到内核页缓存中,然后再通过cpu copy拷贝到用户程序空间;

输出文件时,用户程序会将该文件缓存拷贝的网络堆栈的内核缓存中,然后操作系统再将该缓存通过DMA发送给网络端口;

具体如下图:

文件网络传输过程.png

在这种场景下,一次文件传输一般需要两次cpu copy,两次DMA copy,同时也发生了多次用户态和内核态之间的上下文切换,这无疑加大了cpu的负担;

零拷贝技术

什么是零拷贝技术?在上面场景中,磁盘文件传输到网络端口,需要经过多次cpu copy,加大了cpu的负担,而零拷贝就是指为了避免CPU做大量的拷贝和减少不必要的拷贝而采用的一些技术,这些技术包括采用其他组件来进行简单的文件网络传输;

内核缓存区主要是缓存本地读写文件并与用户程序交换数据的缓存区,而socket缓存区则是用来发送到网络或者从网络读取的文件数据;

mmap

mmap函数可以将用户空间的一块内存地址和内核中的一块内存地址同时映射到真正的物理内存上,从而这块物理内存对于内核和用户空间都是可见的,需要注意的是映射的文件大小最好是内核缓存页大小(PAGE_SIZE)的整数倍,如果不是则会进行强制内存对齐,最后一页没被使用的空间会被填充零;

mmap系统调用代码如下:

buf = mmap(diskfd, len);

write(sockfd, buf, len);

其主要作用如下:

mmap会将diskfd描述的文件通过DMA的方式拷贝到内核页缓存区,同时这个相关缓存区对用户程序是共享的,这样可以减少一次cpu copy;

write会之间将页内核缓存区中文件通过cpu copy到内核中网络堆栈相关的缓存去,然后进行传输;

如下图所示:

mmap文件传输.png

mmap隐藏问题

使用mmap了一个文件,那么当write这个文件过程中,如果存在另一个进程对该文件进行truncate操作(truncate操作可以改变文件大小),那么write系统调用会因为访问非法地址而被SIGBUS终止,这样SIGBUS会杀掉你的进程,同时留下一个coredump文件(coredump文件用来存储进程崩溃时的内存快照,可以用来定位问题);

针对于这类问题的处理方法,就是避免write在文件被truncate后继续访问,具体方法如下:

1.SIGBUS信号处理程序:

当遇到SIGBUS信号时,信号处理程序简单放回,停止write系统调用,返回已经写入的字节数,同时将errno设置为success,虽然这是一种解决方式,但是该方式比较糟糕,并没有解决核心问题;

2.为文件描述符使用租借锁

在mmap文件的文件描述符上使用租借锁,可以向内核为文件申请一个租借锁,这样在其他进程通过内核修改文件时,内核会通过实时信号RT_SIGNAL_LEASE通知我们文件正在被破坏,这种情况下为了避免访问非法地址,程序可以中断write系统调用,返回已经写入的字节数,同时把errno设置为success;

文件租借锁使用代码,使用前加锁,使用后解锁如下:

if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {

perror("kernel lease set signal");

return -1;

}

/* l_type can be F_RDLCK F_WRLCK 加锁*/

/* l_type can be F_UNLCK 解锁*/

if(fcntl(diskfd, F_SETLEASE, l_type)){

perror("kernel lease set type");

return -1;

}

sendFile

关于sendFile,可以先看下sendFile方法:

#include

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

其中in_fd必须是mmap文件,out_fd必须是套接字,这样就可以通过sendFile直接将内核缓存区的文件拷贝到网络缓存区,减少了用户态和内核态的上下文切换,和减少了文件拷贝次数,同时数据拷贝只发生在内核层,如下图所示:

sendFile.png

另外sendfile即使不做任何信号程序处理,如果调用sendFile时其他进程truncate文件,sendFile会被中断调用,返回中断前读取的字节,将errno设置为success,但是不会因为读取非法地址而中断进程;如果给文件使用租借锁,情况没有变化,但是会返回一个RT_SIGNAL_LEASE信号;

sendFile改进

在上面的场景中,文件数据从内核缓存区到socket缓存区同样会经历一次拷贝,那么有没有办法减少这次拷贝呢?借助硬件是可以实现的,我们可以使用sendFile将页缓存区的关于文件缓存的描述符如位置,大小添加到socket端口,这一步不会复制文件缓存,这样DMA控制引擎可以根据文件描述符直接将内核页缓存区的文件拿到协议引擎中,避免最后一次拷贝,如下图:

sendFile2.png

splice

sendFile可以将数据拷贝到一个套接字上面,这就限制了它的一些适用范围;linux可以通过splice的方法将文件数据在两个文件描述符中进行移动,其方法如下:

#define _GNU_SOURCE /* See feature_test_macros(7) */

#include

ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

splice系统调用会在两个文件描述符中进行文件移动,但是其中一个一方必须是管道设备;

flags参数有以下取值:

SPLICE_F_MOVE:尝试去移动数据而不是拷贝数据。这仅仅是对内核的一个小提示:如果内核不能从pipe移动数据或者pipe的缓存不是一个整页面,仍然需要拷贝数据。Linux最初的实现有些问题,所以从2.6.21开始这个选项不起作用,后面的Linux版本应该会实现。

SPLICE_F_NONBLOCK:splice 操作不会被阻塞。然而,如果文件描述符没有被设置为不可被阻塞方式的 I/O ,那么调用 splice 有可能仍然被阻塞

SPLICE_F_MORE:后面的splice调用会有更多的数据;

扩展:linux写时复制

linux为了减少数据文件在内核和用户缓存区进行复制,采用的一种机制,其主要原理是:多个进程访问同一个文件时,那么该文件被拷贝到内核缓存区,对所有进程都是可见的,但是不是所有线程都需要去修改该文件,所以针对这一现象,linux采用了只用当进程需要修改该文件时,才将该文件复制到用户空间,这就是写时复制;

FileChannel与零拷贝

FileChannel中的map()方法其实就是利用mmap()系统调用,而transferTo()、transferFrom()的实现也是根据情况采用了相应的零拷贝技术;

java filechannel 并发_java.nio中的Channel系列(2)-FileChannel与零拷贝原理相关推荐

  1. 从根上理解高性能、高并发(二):深入操作系统,理解I/O与零拷贝技术

    1.系列文章引言 1.1 文章目的 作为即时通讯技术的开发者来说,高性能.高并发相关的技术概念早就了然与胸,什么线程池.零拷贝.多路复用.事件驱动.epoll等等名词信手拈来,又或许你对具有这些技术特 ...

  2. java io类型_Java NIO之Java中的IO分类

    前言 前面两篇文章(Java NIO之理解I/O模型(一).Java NIO之理解I/O模型(二))介绍了,IO的机制,以及几种IO模型的内容,还有涉及到的设计模式.这次要写一些更贴近实际一些的内容了 ...

  3. nio java是什么_Java NIO 的前生今世 之一 简介

    简介 Java NIO 是由 Java 1.4 引进的异步 IO. Java NIO 由以下几个核心部分组成: Channel Buffer Selector NIO 和 IO 的对比 IO 和 NI ...

  4. NIO中的Channel详解

    NIO的通道类似于流,但有些区别如下: •通道可以同时进行读写,而流只能读或者只能写 •通道可以实现异步读写数据 •通道可以从缓冲区(Buffer)读数据,也可以写数据到缓冲区 BIO 中的 stre ...

  5. c语言 java高并发_Java高并发解决方式 2019.docx

    Java高并发解决方式 2019 目录 一.消息队列 (1)应用场景 1.1 解耦和 1.2 异步处理 1.3 流量削峰 (2)消息队列缺点 2.1 系统可用性降低 2.2 系统复杂度提高 2.3 一 ...

  6. Java 高并发_JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过!...

    JAVA并发编程与高并发解决方案 JAVA高并发项目实战课程 没有项目经验的朋友不要错过! 1.JPG (37.82 KB, 下载次数: 0) 2018-12-3 09:40 上传 2.JPG (28 ...

  7. java的成员方法_java编程中的成员方法是什么?

    DIEA 成员方法描述对象所具有的功能或操作,反映对象的行为,是具有某种相对独立功能的程序模块.它与过去所说的子程序.函数等概念相当.一个类或对象可以有多个成员方法,对象通过执行它的成员方法对传来的消 ...

  8. java runnable 异常_JAVA 线程中的异常捕获

    在java多线程程序中,所有线程都不允许抛出未捕获的checked exception(比如sleep时的InterruptedException),也就是说各个线程需要自己把自己的checked e ...

  9. java swing背景_java Swing中添加背景图片(总结)

    在java GUI图形化设计中,Swing组件添加背景图片主要有两中情况:1,在Jpanl面板中添加背景图片,2,在jLable中添加背景图片 首先介绍一种最巧妙的背景图片添加方法, 1.利用JLab ...

  10. java static 并发_Java static并发问题

    1. 只要你的静态方法不访问全局变量的话,就不会有并发问题 访问全局变量肯定会出现并发问题,这是毫无疑问的 静态方法内部的变量,都是局部变量,每次调用静态方法时都会重新分配内存空间,所以是安全的. 也 ...

最新文章

  1. 2022-2028年中国pu管行业市场深度分析及市场规模预测报告
  2. shell 命令执行结果判断语句
  3. 20个绝密电脑小技巧
  4. Qt修炼手册12_线程同步与线程等待条件
  5. 工厂三兄弟之工厂方法模式
  6. nginx虚拟目录支持PHP,nginx“虚拟目录”不支持php的解决方法
  7. [转]史上最全的后端技术大全,你都了解哪些技术呢?
  8. SpringApplication:SpringBoot程序启动的一站式解决方案
  9. iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  10. 前端关于html的面试题,关于java:前端面试HTML面试题汇总
  11. Android中关于Adapter的使用(下)BaseAdapter
  12. java lock unlock_【Java并发007】原理层面:ReentrantLock中lock()、unlock()全解析
  13. 不用登录就可以复制 CSDN 代码
  14. springboot办公OA考勤请假系统java
  15. 【Unity】入门级Unity安装教程
  16. 【叨、校长】一个基于Extjs、Pushlet美轮美奂的Web聊天室
  17. 卖西瓜问题(Java经典编程案例)
  18. 摩尔投票(包含题目讲解)
  19. UART数据发送和接收(Verilog)
  20. 卸载正在使用中的tcp_congestion_control模块

热门文章

  1. 【笔检测】基于matlab模板匹配+PCA笔检测【含Matlab源码 1093期】
  2. matlab算法knn算法,MATLAB KNN算法
  3. 人工智能和机器学习的前五门课程
  4. python 中 or 的特殊用法,很有用的一个用法
  5. 380v pcb 接线端子_220V线圈的接触器改成380V线圈方法
  6. c语言中x的n次方怎么表示_为什么一定要慎用C语言标准库中的pow函数,你知道吗?...
  7. 滁州2021年高考成绩查询时间,安徽2021中考时间表安排 滁州2021年中考时间考点公布...
  8. java put请求_计算机毕业设计中用java实现小程序推送(springboot实现)
  9. python 读png的值变了_深度学习数据预处理_python批量转换labelme标注的json格式标签为png格式...
  10. 不小心rm删除文件怎么办