您的位置:首页 > 编程语言 > Java开发

java NIO 的通道Channel的理解

2017-10-25 21:49 483 查看
一、什么是通道?

Channel,中文意思“通道”,表示IO源与目标打开的连接,类似于传统的“流”。但是Channel不能直接访问数据,

需要和缓冲区buffer进行交互。打个比喻:山西有煤,我们想要,于是乎建了一条铁路连通到山西,

这条铁路就是这里的"Channel",那么煤通过什么运过来呢?铁路建好了,就差火车了,

因此这里的火车就像是缓冲区,火车把山西的煤运到这边来,把我们这里的钱运过去,

这样双向传输就愉快的完成了。Channel类似于传统的“流”,只不过Channel不能直接访问数据,

Channel只能与buffer交互。而且流是单向的,分为输入流和输出流, 但是通道是双向的,

通过缓冲区buffer两边都可达。Channel表示IO源与目标打开的连接。工作原理如下,通过Channel来独立处理IO请求:

                                          


二、主要实现类:

   FileChannel:用于读取、写入、映射和操作文件的通道。

DatagramChannel:通过UDP读写网络中的数据通道。

SocketChannel:通过tcp读写网络中的数据。

ServerSocketChannel:可以监听新进来的tcp连接,对每一个连接都创建一个SocketChannel。

三、获取通道的方式

1、通过getChannel()方法获取

前提是该类支持该方法。支持该类的方法有:

FileInputStream/FileOutputStream,RandomAccessFile,Socket,ServerSocket ,DatagramSocket

2、通过静态方法open()

3、通过jdk1.7中Files的newByteChannel()方法

四、通道数据传输

通过一段复制图片的代码解释:

1、利用通道和非直接缓冲区完成

FileInputStream fis=null;           //引用
FileOutputStream fout=null;
FileChannel channel=null;		//通道引用
FileChannel outchannel=null;
try {
fis = new FileInputStream("sb.jpg");     //源文件
fout = new FileOutputStream("bb.jpg");	 //目标文件
channel = fis.getChannel();             //获取连接源文件的通道
outchannel = fout.getChannel();	        //获取连接目标文件的通道
//指定缓冲区 非直接缓冲区
ByteBuffer buffer=ByteBuffer.allocate(1024);  //创建缓冲区 用来传输数据
while(channel.read(buffer)!=-1)          //从连接源文件的管道读取数据到缓冲区
{
//将缓冲区反转
buffer.flip();
outchannel.write(buffer);        //将缓冲区中的数据写入连接到目标文件的管道
buffer.clear();                  //"清空"缓冲区
}
2、利用通道和内存映射文件完成

FileChannel inChannel=FileChannel.open(Paths.get("C:\\Users\\Administrator\\Pictures\\picture\\1.jpg"), StandardOpenOption.READ);
//参数1表示目标文件或者源文件的路径,参数2是可以是多个参数,表示通道支持的操作类型
FileChannel outChannel=FileChannel.open(Paths.get("C:\\Users\\Administrator\\Pictures\\picture\\1bar.jpg"),



StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//内存映射文件 相当于之前的缓冲区
MappedByteBuffer inBuffer=inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outBuffer=outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区读写操作
byte[] data=new byte[inBuffer.limit()];
//将数据读入字节数组
inBuffer.get(data);
outBuffer.put(data);   //写入管道
inChannel.close();
outChannel.close();
对于上面这种方式,可以很大程度上提高程序的效率。那么怎么提高的呢?

回到最上面的那张通道工作的原理图,用户地址和内存地址都有一个缓冲区,它们之间有一个copy过程,也就是说程序的数据要先到jvm用户地址缓冲区,再到计算机内存中。也就是不能直接和计算机内存进行数据操作。那么通过内存映射文件呢,也就是上诉方式,内部以直接缓冲区和内存映射文件实现了直接访问内存。这样大大提高了效率。但是也有缺点,如果写过去的数据很大,然后jvm没有及时回收内存,那么结果可想而知。因此,这个适用于那种长久存放在内存不应该被短时间内就需要回收的数据。

3、通道之间的直接传输

FileChannel inChannel=FileChannel.open(Paths.get("C:\\Users\\Administrator\\Pictures\\picture\\1.jpg"), StandardOpenOption.READ);
FileChannel outChannel=FileChannel.open(Paths.get("C:\\Users\\Administrator\\Pictures\\picture\\1bar.jpg"),
StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//transfer方法   内部是直接缓冲区的方式实现
//inChannel.transferTo(0,inChannel.size(), outChannel);
4000
outChannel.transferFrom(inChannel, 0, inChannel.size());
inChannel.close();
outChannel.close();

咦?为什么上面的代码没有缓冲区呢?不是说通道不能直接访问数据吗?
没错,只不过在transferTo和transferFrom内部实现了缓冲区,所以数据还是通过缓冲区来完成的。

关于transferTo和transferFrom,两者达到的效果一样,只是使用的方式不同,前者是源文件到目标文件,

后者是目标文件的资源来自于源文件。

4、分散于聚集

分散读取:是指读取一个Channel的数据到多个buffer中,注意是依次填满每个缓冲区,直到没有数据了;

聚集写入:是指将多个buffer的内容依次写入到通道中。

RandomAccessFile randomAccessFile=new RandomAccessFile("1.txt", "rw");
FileChannel channel=randomAccessFile.getChannel();//获取通道
//分配指定大小缓冲区 这里指定两个大小不一样的缓冲区
ByteBuffer buf1=ByteBuffer.allocate(100);
ByteBuffer buf2=ByteBuffer.allocate(4096);
ByteBuffer[] buf={buf1,buf2};
channel.read(buf);
for (ByteBuffer byteBuffer : buf) {
byteBuffer.flip(); //翻转 开启读模式
}
//打印buf1的内容
System.out.println(new String(buf[0].array(),0,buf[0].limit()));
//打印buf2的内容
System.out.println(new String(buf[1].array(),0,buf[1].limit()));
//聚集写入
RandomAccessFile randFile=new RandomAccessFile("2.txt", "rw");
FileChannel fileChannel=randFile.getChannel();
fileChannel.write(buf);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java nio channel 通道