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]緩衝區上的內容,並進行寫入。java

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的用法。

相關文章
相關標籤/搜索