NIO(二):Channel通道
一.Channel概述
channel(通道):进行IO的连接通道,为NIO的几个核心(Buffer,selector,channel)之一,相比于IO的stream具有较高的性能。
IO |
单向传输 |
NIO |
异步双向传输 |
使用时需要和buffer(缓冲区一切使用),将数据暂存入Buffer中,通过channel通道连接传输buffer以此传输数据。
二.channel继承结构
其中主要的几个实现类如下:
-
FileChannel: 本地文件传输通道
-
SocketChannel: 通过TCP传输通道
-
ServerSocketChannel: 监听新的TCP连接,并创建一个新的SocketChannel,通过.open()方法进行创建。
-
DatagramChannel: 发送和接收UDP数据包的channel
通过Buffer的两种传输数据方式:
-
非直接缓冲区传输数据 - - > ByteBuffer.allocate()方法;
-
直接缓冲区传输数据 - - > ByteBuffer.allocateDriect()方法;
几种获取channel的方式:
-
本地通过FileInPutStream/FileOutPutStream 使用getChannel()方法获取;
-
本地FileChannel 的open()方法获取;
-
本地RandomAccessFile的getChannel()方法获取;
-
网络socket、ServerSocket、DatagramSocket
三.FileInputStream获取通道方式
非直接缓冲区方式,再程序与本地磁盘中,并不是直接进行数据的交流。而是在用户和内核之间通过复制的形式。每次read 和write 都在中间进行复制。程序可以控制数据。
流程:FileInputStream写入文件 --> FileOutputStream的getChannel()获取通道 --> 开辟一个具有一定容量的buffer缓冲区 --> 将缓冲区的内容通过channel传输 --> 关闭流
1 //使用非直接缓冲区 传输数据 2 //通过流的方式 3 @Test 4 public void NoDirect(){ 5 6 FileInputStream fileInputStream = null; 7 FileOutputStream fileOutputStream =null; 8 FileChannel fileInputStreamChannel = null; 9 FileChannel fileOutputStreamChannel = null; 10 long start = 0; 11 12 try { 13 //查看耗时 14 start = System.currentTimeMillis(); 15 16 //写入 17 fileInputStream = new FileInputStream("D:/DataTestFile/1.zip"); 18 //读取到 19 fileOutputStream = new FileOutputStream("D:/DataTestFile/2.zip"); 20 21 //使用通道进行文件传输 22 //在传输前先获取传输通道channel 23 fileInputStreamChannel = fileInputStream.getChannel(); 24 fileOutputStreamChannel = fileOutputStream.getChannel(); 25 26 //将数据写入缓冲区 --> 开辟缓冲区容量 --> 后使用通道传输 27 ByteBuffer buf = ByteBuffer.allocate(1024); 28 29 //将通道中的数据送入缓冲区 数据为空时跳出循环 30 while(fileInputStreamChannel.read(buf) != -1){ 31 //切换读写模式 32 buf.flip(); 33 //读出缓冲区的数据写入通道中 34 fileOutputStreamChannel.write(buf); 35 //清空缓冲区 36 buf.clear(); 37 } 38 39 40 } catch (FileNotFoundException e) { 41 e.printStackTrace(); 42 } catch (IOException e) { 43 e.printStackTrace(); 44 } 45 //最终需要执行的步骤 需要关闭流 和通道 46 finally { 47 //写入流关闭 48 if (fileInputStream != null){ 49 try { 50 fileInputStream.close(); 51 } catch (IOException e) { 52 e.printStackTrace(); 53 } 54 } 55 //读出流关闭 56 if (fileOutputStream != null){ 57 try { 58 fileOutputStream.close(); 59 } catch (IOException e) { 60 e.printStackTrace(); 61 } 62 } 63 if (fileInputStreamChannel != null){ 64 try { 65 fileInputStreamChannel.close(); 66 } catch (IOException e) { 67 e.printStackTrace(); 68 } 69 } 70 //读出通道关闭 71 if (fileOutputStreamChannel != null){ 72 try { 73 fileOutputStreamChannel.close(); 74 } catch (IOException e) { 75 e.printStackTrace(); 76 } 77 } 78 //查看耗时 79 long end = System.currentTimeMillis(); 80 System.out.println("总共耗时:"+(end-start)); 81 } 82 83 84 }
测试数据大小
使用IO流的方式(FileInPutStream/FileOutPutStream)写入文件或者读取文件,FileInPutStream实现方式:(FileOutPutStream 与之类似)
1 public FileInputStream(String name) throws FileNotFoundException { 2 this(name != null ? new File(name) : null); 3 } 4 5 6 7 8
//获取本地文件 9 public File(String pathname) { 10 if (pathname == null) { 11 throw new NullPointerException(); 12 } 13 this.path = fs.normalize(pathname); 14 this.prefixLength = fs.prefixLength(this.path); 15 }
三.FileChannel方式+mapped*ByteBuffer(内存映射文件)传输数据
直接传输方式,在本地形成一个本地映射文件,程序通过本地映射直接传输给物理磁盘,没有复制的操作。在传输速度上优于非直接的传输
但是程序的数据一旦交给映射文件,程序将无法控制数据。
流程: FileChannel.open 获取通道 (写入和读取文件,并规定Read Or Write ,create等方式) - - > .map(将通道的一个区域映射到内存中) - - > 将映射文件中的数据存入数组,写入或读出到MappedByteBuffer
1 /* 2 * 通过直接缓冲区的方式进行传输数据 3 * 通过使用映射文件的方式MappedByteBuffer传输 4 * */ 5 @Test 6 public void Driect(){ 7 8 FileChannel fileInChannel = null; 9 FileChannel fileOutChannel = null; 10 MappedByteBuffer mappedInByteBuffer = null; 11 MappedByteBuffer mappedOutByteBuffer = null; 12 long start = 0; 13 14 try { 15 //耗时查询 16 start = System.currentTimeMillis(); 17 18 fileInChannel = FileChannel.open(Paths.get("D:/DataTestFile/1.zip"), StandardOpenOption.READ,StandardOpenOption.WRITE); 19 fileOutChannel = FileChannel.open(Paths.get("D:/DataTestFile/2.zip"), StandardOpenOption.READ, StandardOpenOption.WRITE,StandardOpenOption.CREATE); 20 21 //使用内存映射文件 ,杜绝非直接缓存区中的通过用户态 和核心态的相互复制影响性能的问题 22 //直接通过本地映射文件。但是一旦传输后,不归程序所管理 23 mappedInByteBuffer = fileInChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileInChannel.size()); 24 mappedOutByteBuffer = fileOutChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileInChannel.size()); 25 26 //直接对缓冲区进行读写操作 27 //定义一个数组,将映射文件的数据存入其中 28 byte[] bt = new byte[mappedInByteBuffer.limit()]; 29 //先从本地映射文件中读取 后写入传输 30 mappedInByteBuffer.get(bt); 31 mappedOutByteBuffer.put(bt); 32 33 34 } catch (IOException e) { 35 e.printStackTrace(); 36 } 37 finally { 38 if (fileInChannel != null){ 39 try { 40 fileInChannel.close(); 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 } 45 if (fileOutChannel != null){ 46 try { 47 fileOutChannel.close(); 48 } catch (IOException e) { 49 e.printStackTrace(); 50 } 51 } 52 53 //耗时结束查询 54 long end = System.currentTimeMillis(); 55 System.out.println("总共耗时:"+(end-start)); 56 57 }
相比较于非直接的方式,在相同资源下的第一次传输,性能更高。传输速度更快。
五.分散传输
将一个整体文件,分散成多个部分,使用多缓冲区分别进行分散。
流程:使用RandomAccessFile读取写入文件 - - > 通过RandomAccessFile.getChannel 获取通道 - - > 将两个开辟的缓冲区,存入数组中,并循环读入通道 - - > 转换成可以直观查看的String类型
1 package com.cllover.nio; 2 3 import org.junit.Test; 4 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.io.RandomAccessFile; 8 import java.nio.ByteBuffer; 9 import java.nio.channels.FileChannel; 10 11 /* 12 * @Author chengpunan 13 * @Description //TODO 18609 14 * @Date 7:29 PM 7/25/2020 15 * @Param 16 * @return 17 * 18 * 分散读写和聚集读写测试类 19 * Disperse:分散读写 20 * gather: 聚集读写 21 **/ 22 public class DisperseAndGather { 23 24 // 分散读写 将通道中的数据分散到缓冲区中, 25 @Test 26 public void Disperse(){ 27 RandomAccessFile randomAccessFile = null; 28 FileChannel randomAccessFileChannel = null; 29 30 try { 31 //随机访问文件流以RW形式访问某文件 32 randomAccessFile = new RandomAccessFile("D:/DataTestFile/1.txt","rw"); 33 34 //获取通道 35 randomAccessFileChannel = randomAccessFile.getChannel(); 36 37 //开辟两个缓冲区 38 ByteBuffer byteBuffer100 = ByteBuffer.allocate(100); 39 ByteBuffer byteBuffer200 = ByteBuffer.allocate(200); 40
//数值初始化:存入两个缓冲区的数据
41 //分散读取 42 ByteBuffer[] byteBuffer = {byteBuffer100,byteBuffer200}; 43 44 //存入读取通道 45 randomAccessFileChannel.read(byteBuffer); 46 47 for (ByteBuffer bf: byteBuffer) { 48 bf.flip(); //切换读写方式 49 } 50 //转换成字符串 51 //public String(byte bytes[], int offset, int length) 52 System.out.println(new String(byteBuffer[0].array(),0,byteBuffer[0].limit())); 53 System.out.println("----------------------------------"); 54 System.out.println(new String(byteBuffer[1].array(),0,byteBuffer[1].limit())); 55 56 } catch (FileNotFoundException e) { 57 e.printStackTrace(); 58 } catch (IOException e) { 59 e.printStackTrace(); 60 } 61 finally { 62 if (randomAccessFile != null){ 63 try { 64 randomAccessFile.close(); 65 } catch (IOException e) { 66 e.printStackTrace(); 67 } 68 } 69 if (randomAccessFileChannel != null){ 70 try { 71 randomAccessFileChannel.close(); 72 } catch (IOException e) { 73 e.printStackTrace(); 74 } 75 } 76 if (randomAccessFile != null){ 77 try { 78 randomAccessFile.close(); 79 } catch (IOException e) { 80 e.printStackTrace(); 81 } 82 } 83 } 84 85 } 86 87 }
将单独两个开辟的缓冲区,存入数组之中。存入通道后,用foreach循环遍历index[0]和index[1]缓冲区上的内容,并进行写入。
1 //分散读取:数组初始化 2 ByteBuffer[] byteBuffer = {byteBuffer100,byteBuffer200}; 3 4 //存入读取通道 5 randomAccessFileChannel.read(byteBuffer); 6 7 for (ByteBuffer bf: byteBuffer) { 8 bf.flip(); //切换读写方式 9 }
资源内容:
运行结果:
聚集写入:
1 //聚集写入 2 RandomAccessFile randomAccessFile1 = new RandomAccessFile("D:/DataTestFile/2.txt", "rw"); 3 FileChannel channel = randomAccessFile1.getChannel(); 4 //将全部内容。将上段代码红色部分,存入数组中的数据直接通过通道写入另一个文件中 5 channel.write(byteBuffer);
写入文件后,原本文件中的内容会被覆盖或者丢失。
写入后:
六.总结
channel作为一个连接通道,支持异步传输。
总是基于Buffer缓冲区配合使用。
在传输时,需要在buffer中暂存入数据后开启channel通道,进行读写操作。
下一篇将会写关于网络传输 和 selector的用法。