day24【JUnit單元測試、NIO】

1.Junit單元測試框架

1.1 概述

​ junit是單元測試,你想測哪個方法就寫一個對應的測試方法,然後用junit運行。每個方法之間是獨立的,非常靈活。而且測試方法一般不會直接寫在原類中,而是單獨的測試類,這樣測試代碼就完全與邏輯代碼分開了。

​ Junit是Java語言編寫單元測試框架。Junit屬於第三方工具,一般情況下需要導入jar包,而多數Java開發環境都集成了Junit。

1.2 使用方式

問題:在之前,我們寫了一個方法,我們都需要使用通過main方法來調用。當我們需要測試的方法很多的時候,每次使用main方法來調用時是非常麻煩的。所以我們有了接下來的單元測試,在開發中使用調用方法就顯得特別的簡單。

創建java項目,並創建“com.itheima.sh.junit”包

在這裏插入圖片描述

  1. 編寫測試類

    說明:如果以前想讓一個方法運行必須在main方法中調用該方法。

在這裏插入圖片描述

2.在測試類JunitDemo01方法上添加註解 @Test(說明:關於什麼是註解,後面會詳細講解)

在這裏插入圖片描述
3.@Test修飾的方法要求:public void 方法名() {…} ,沒有參數。

說明:單元測試的方法必須是public修飾,void表示沒有返回值,沒有參數列表,否則就會不滿足單元測試要求,報異常。

在這裏插入圖片描述

4.添加Idea中集成的Junit庫,鼠標放在“@Test”處,使用快捷鍵“alt+Enter”,點擊“Add Junit …”

在這裏插入圖片描述

5.使用:選中方法右鍵,執行當前方法;選中類名右鍵,執行類中所有方法(方法必須標記@Test)

在這裏插入圖片描述

注意:如果按照上述方式無法導入junit包,說明你的idea中沒有集成junit,可以參考課後我給大家提供的幫助文檔。如果最後還是無法解決那隻能在當前項目根目錄下創建lib文件夾,將課後資料的jar包放到lib下就可以使用了

1.3 常用註解

  • Junit4.x版本註解

    @Test  :  測試方法,必須是public修飾的,沒有返回值,沒有參數
    @Before:用來修飾方法,該方法會在每一個測試方法執行之前執行一次。 
    @After:用來修飾方法,該方法會在每一個測試方法執行之後執行一次。
    @BeforeClass:用來修飾靜態方法,該方法會在所有測試方法之前執行一次,而且只執行一次。 
    @AfterClass:用來修飾靜態方法,該方法會在所有測試方法之後執行一次,而且只執行一次。
    
  • 代碼演示

    public class JunitDemo02 {
    	@Test
    	public void myTest(){
    		System.out.println("測試 test");
    	}
    	@Test
    	public void myTest1(){
    		System.out.println("測試 test1");
    	}
    	@Before
    	public void myBefore(){
    		System.out.println("方法前");
    	}
    	
    	@After
    	public void myAfter(){
    		System.out.println("方法後");
    	}
         @BeforeClass // 用來修飾靜態方法,該方法會在所有測試方法之前執行一次。
        public static void myBeforeClass(){
            System.out.println("初始化操作BeforeClass.....");
        }
    
        @AfterClass // 用來修飾靜態方法,該方法會在所有測試方法之後執行一次。
        public static void myAfterClass(){
            System.out.println("釋放資源myAfterClass.....");
        }
    }
    

注意:被測試的類名不能單獨是Test,因爲junit單元測試框架底層有叫Test,重名了使用會有問題。被測試的類命名規範:以Test開頭,以業務類類名結尾,使用駝峯命名法 。比如業務類類名:ProductDao,那麼測試類類名就應該叫:TestProductDao

  • Junit常用註解(Junit5.x版本)【自學,瞭解,開發中平時都使用junit4,所以有興趣可以看下,這裏不講解】

在這裏插入圖片描述

1.4 斷言

  • 作用

    斷言:預先判斷某個條件一定成立,如果條件不成立,則直接報錯。

    斷言代碼:

  //第一個參數args1表示期望值
  //第二個參數args2表示實際值
  //如果args1和args2相同,說明結果正確就測試通過,如果不相同,說明結果是錯誤的,就會報錯
  Assert.assertEquals(args1, args2);
  • 代碼示例

    public class Test01 {
        @Test
        public void addTest() {
            //測試
            int sum = add(3, 6);
    
            //斷言判斷結果
            //第一個參數表示期望值
            //第二個參數表示實際值
            //如果結果正確的就測試通過,如果結果錯誤的,就會報錯
            Assert.assertEquals(9, sum);
            System.out.println("sum = " + sum);
        }
    
        //加法
        //這個代碼的語法沒問題,也沒有異常。他是邏輯錯誤,系統不知道你要算的是加法
        public int add(int a, int b) {
    //        int sum = a + b;
            //這裏是*,不是+,而實際上我們希望是+,所以使用斷言技術可以進行測試判斷,滿足就通過,不滿足預期值就報異常
            int sum = a * b;
            return sum;
        }
    }
    

2. NIO

概述

​ Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java IO API(就是之前學習的IO,也稱爲BIO(Blocking IO))。NIO與原來的IO有同樣的作用和目的,都是實現數據的傳輸。但是使用的方式完全不同,NIO支持面向緩衝區的、基於通道的IO操作。**NIO將以更加高效的方式進行文件的讀寫操作。同時NIO是一種同步非阻塞的I/O模型。**對應 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解爲Non-blocking,不單純是New。NIO提供了與傳統BIO模型中的 SocketServerSocket 相對應的 SocketChannelServerSocketChannel 兩種不同的套接字通道實現。

Java NIO 傳統IO(BIO)的主要區別

在這裏插入圖片描述

BIO:面向流

在這裏插入圖片描述

NIO:面向緩衝區

在這裏插入圖片描述

小結:

​ 1.對於傳統IO屬於阻塞式IO.NIO屬於非阻塞IO,並且NIO具有Selector選擇器,BIO沒有選擇器都是和我們明天講解的NIO實現網絡編程內容有關,我們到時候在介紹。

​ 2. NIO 包含下面幾個核心的組件:

  • Channel(通道):通道表示打開到 IO 設備(例如:文件、套接字)的連接。

  • Buffer(緩衝區):若需要使用 NIO ,需要獲取用於連接 IO 設備的通道以及用於容納數據的緩衝區。然後操作緩衝區,對數據進行處理。

  • Selector(選擇器):Selector 是非阻塞 IO 的核心(明天講解)

    3.NIO一般是在高併發情況下使用的,效率特別高。比如流行的軟件或流行的遊戲中會有高併發和大量連接。

3.Buffer緩衝區

Buffer緩衝區概述

1.一個用於特定基本數據類型的容器。由 java.nio 包定義的,所有緩衝區都是 Buffer 抽象類的子類。

2.Java NIO 中的 Buffer 主要用於與 NIO 通道(Channel)進行交互,數據是從通道讀入緩衝區,從緩衝區寫入通道中的。

3.在NIO中,所有的數據都是用Buffer處理的,它是NIO讀寫數據的中轉池

4.Buffer是一個抽象類,它對某種基本類型的數組進行了封裝.可以保存多個相同類型的數據。根據數據類型不同(boolean 除外) ,有以下 Buffer 常用子類:

ByteBuffer  (最重要的一個,我們只學習它)
CharBuffer 
DoubleBuffer 
FloatBuffer
IntBuffer 
LongBuffer 
ShortBuffer

ByteBuffer的創建方式

​ 上述 Buffer 類 他們都採用相似的方法進行管理數據,只是各自管理的數據類型不同而已。我們只需要掌握ByteBuffer緩衝區即可。

​ ByteBuffer內部封裝了一個byte[]數組,ByteBuffer裏面有一些方法可以對數組進行操作。以下是ByteBuffer的創建方式,一共有三種方式。

1.ByteBuffer的創建方式

  • 在堆中創建緩衝區**(間接緩衝區)**:allocate(int capacity)

    • capacity表示的是數組的容量,也就是數組的大小
    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
    System.out.println(byteBuffer.capacity());
    
  • 在系統內存創建緩衝區**(直接緩衝區)**:allocatDirect(int capacity)

    ByteBuffer byteBuffer1 = ByteBuffer.allocateDirect(10);
    System.out.println(byteBuffer1.capacity());
    
  • 通過數組創建緩衝區**(間接緩衝區)**:static ByteBuffer wrap(byte[] array) 將 byte 數組包裝到緩衝區中。

    byte[] buf = new byte[10];
    ByteBuffer byteBuffer2 = ByteBuffer.wrap(buf);
    System.out.println(byteBuffer2.capacity());
    

2.間接緩衝區和直接緩衝區區別

  - 在堆中創建緩衝區稱爲:間接緩衝區(非直接緩衝區)
  - 在系統內存創建緩衝區稱爲:直接緩衝區

間接緩衝區(非直接緩衝區):

在這裏插入圖片描述

說明:

​ 傳統的IO流和allocate()方式都是間接緩衝區。應用程序到物理磁盤的過程中都要經歷一個os操作系統和jvm內存之間的copy複製過程,性能比較低。

直接緩衝區:

在這裏插入圖片描述

說明:直接緩衝區是佔用物理內存的,在物理內存中創建映射文件,從而取消了內核地址空間和用戶地址空間之間的數據copy過程,提高了效率。直接緩衝區讀寫效率雖然高了,但是不受我們的應用程序控制了,由系統控制。我們將數據交給物理內存以後具體什麼時候給物理磁盤,程序無法控制。同時會消耗系統的內存,具體何時釋放資源得需要垃圾回收機制釋放垃圾,然後系統纔會釋放資源。一般適合大量的數據長時間存儲到內存中的數據.

小結:

  • 間接緩衝區的創建和銷燬效率要高於直接緩衝區,但是間接緩衝區的工作效率要低於直接緩衝區

常用方法

  • 方法介紹

    • put(byte b) :給數組添加元素
    • byte[] array() : 返回實現此緩衝區的 byte 數組
    public class Test01 {
        @Test
        public void test1(){
            //創建對象 :在堆中創建緩衝區(間接緩衝區):allocate(int capacity)
            ByteBuffer buffer = ByteBuffer.allocate(10);
            //put() 添加方法
            buffer.put((byte)11);
            buffer.put((byte)22);
            //把ByteBuffer類型轉成普通數組類型
            //byte[] array() : 返回實現此緩衝區的 byte 數組
            byte[] bytes = buffer.array();
            //打印
            System.out.println(Arrays.toString(bytes));
            //打印結果:  [11, 22, 0, 0, 0, 0, 0, 0, 0, 0]
        }
    }
    
    • int capacity() :獲取容量,容量是一個定值
      • Buffer的容量(capacity)是指:Buffer所能夠包含的元素的最大數量。定義了Buffer後,容量是不可變的。因爲底層封裝的是一個數組。
    public class Demo03 {
           @Test
        public void test1(){
            //創建對象
    //        ByteBuffer buffer = ByteBuffer.allocate(3);//報錯
            ByteBuffer buffer = ByteBuffer.allocate(10);
    
            //添加元素
            buffer.put((byte)11);
    
            byte[] bytes = {22,33,44};
            buffer.put(bytes);
    
            //打印
            System.out.println(Arrays.toString(buffer.array()));
            //[11, 22, 33, 44, 0, 0, 0, 0, 0, 0]
    
            //buffer的容量
            int capacity = buffer.capacity();
            System.out.println("容量" + capacity);  //10
        }
    }
    
    • int limit() :返回此緩衝區的限制。

    • Buffer limit(int newLimit) :設置此緩衝區的限制.參數:newLimit表示指定的限制的索引,從limit開始後面的位置不能操作(包括newLimit索引對應的位置)

      • 緩衝區的限制不能爲負,並且不能大於其容量
      public class Demo04{ 
          public static void main(String[] args) {
              //創建對象
              ByteBuffer buffer = ByteBuffer.allocate(10);
              //添加元素
              buffer.put((byte)11);//索引0
      
              //獲取limit
              //默認情況下所有的位置都可以被使用
              int limit = buffer.limit();
              System.out.println("現在限制位置" + limit);//10
      
              //3索引開始後面的位置不能被使用(包括索引3的位置)
              buffer.limit(3);
              int limit2 = buffer.limit();
              System.out.println("現在限制位置" + limit2);//3
      
              //添加元素
            buffer.put((byte)22);//索引1
            buffer.put((byte)33);//索引2
            buffer.put((byte)44);//索引3   因爲限制了3索引,所以在存放44的時候出現了異常:BufferOverflowException
          }
      }
      

在這裏插入圖片描述

  • position() :位置代表將要存放的元素的索引。位置不能小於0,也不能大於limit.

    • 不加參數表示獲取當前position,加參數表示設置position
    public class Demo05 {
        public static void main(String[] args) {
            //創建對象
            ByteBuffer buffer = ByteBuffer.allocate(10);
            //添加元素
            buffer.put((byte)11);
            buffer.put((byte)22);
            buffer.put((byte)33);
    
            //獲取位置
            //int position = buffer.position();
            //System.out.println("當前的位置" + position);  //3
    
            //設置位置
          buffer.position(6);
            //添加元素
          buffer.put((byte)44);
    
            //打印
            System.out.println(Arrays.toString(buffer.array()));
            //[11, 22, 33, 0, 0, 0, 44, 0, 0, 0]
        }
    }
    

在這裏插入圖片描述

  • 標記 (mark)與重置(reset) : 標記是一個索引,通過 Buffer 中的 mark() 方法指定 Buffer 中一個特定的 position,之後可以通過調用 reset() 方法恢復到這個 position標記。

  • public class Demo06{
        public static void main(String[] args) {
             //創建對象
            ByteBuffer buffer = ByteBuffer.allocate(10);
            //添加元素
            buffer.put((byte)11);
            buffer.put((byte)22);
            buffer.put((byte)33);
    
            //標記當前索引
            buffer.mark();
    
            buffer.put((byte)44);
            buffer.put((byte)55);
    
            //打印數組
            System.out.println(Arrays.toString(buffer.array()));
            //[11, 22, 33, 44, 55, 0, 0, 0, 0, 0]
    
          //重置
            buffer.reset();
    
            //添加元素
            buffer.put((byte)66);
    
          //打印數組
            System.out.println(Arrays.toString(buffer.array()));
            //[11, 22, 33, 66, 55, 0, 0, 0, 0, 0]
    
            //重置 還是會重置到原來mark位置
            buffer.reset();
    
            //添加元素
            buffer.put((byte)77);
    
            //打印數組
            System.out.println(Arrays.toString(buffer.array()));
            //[11, 22, 33, 77, 55, 0, 0, 0, 0, 0]
    	}
    }
    

在這裏插入圖片描述

說明:

​	1.如果不在調用mark()方法,那麼mark一直標記的是最之前的position位置,如果在調用reset()方法還是會回到最開始的position位置。

​	2.0 <= mark <= position <= limit <= capacity
  • Buffer clear():還原緩衝區的狀態。 清空緩衝區並返回對緩衝區的引用

    • 將position設置爲:0
    • 將限制limit設置爲容量capacity
    • 丟棄標記mark
    public class Demo07{
      public static void main(String[] args) {
            //創建對象
            ByteBuffer buffer = ByteBuffer.allocate(10);
    
            //添加元素
            buffer.put((byte)11);
            buffer.put((byte)22);
            buffer.put((byte)33);
    
            //限制
            buffer.limit(6);
    
            //容量是10 位置是3 限制是6
            System.out.println("容量是" + buffer.capacity() + " 位置是" + buffer.position() + " 限制是" + buffer.limit());
            
    
            //還原
            buffer.clear();
    
            //容量是10 位置是0 限制是10
            System.out.println("容量是" + buffer.capacity() + " 位置是" + buffer.position() + " 限制是" + buffer.limit());
        }
    }
    
  • flip():縮小limit的範圍。 切換。在讀寫數據之間要調用這個方法。

    • 將limit設置爲當前position位置
    • 將當前position位置設置爲0
    • 丟棄標記
    public class Demo08{
        public static void main(String[] args) {
             //創建對象
            ByteBuffer buffer = ByteBuffer.allocate(10);
            //添加元素
            buffer.put((byte)11);
            buffer.put((byte)22);
            buffer.put((byte)33);
            //限制
            buffer.limit(6);
    
            //容量是10 位置是3 限制是6
            System.out.println("容量是" + buffer.capacity() + " 位置是" + buffer.position() + " 限制是" + buffer.limit());
    
            //切換讀模式
            buffer.flip();
    
            //容量是10 位置是0 限制是3
            System.out.println("容量是" + buffer.capacity() + " 位置是" + buffer.position() + " 限制是" + buffer.limit());
        }
    }
    

說明:

1.如果要操作數組中的數據,必須先使用flip方法切換成讀模式。

2.循環再次操作數據的時候,必須先使用clear清空數組。

在這裏插入圖片描述

**獲取Buffer中的數據: **

abstract  byte get() 讀取單個字節。讀取此緩衝區當前位置的字節,然後該位置position遞增。
ByteBuffer get(byte[] dst)批量讀取多個字節到 dst 中,position移動到最後
abstract  byte get(int index)讀取指定索引位置的字節(不會移動 position)

代碼演示:

public void test7(){
        ByteBuffer buf = ByteBuffer.allocate(10);
        buf.put("abcde".getBytes());
        //切換讀取數據模式
        buf.flip();
        //容量是10 位置是0 限制是5
        System.out.println("容量是" + buf.capacity() + " 位置是" + buf.position() + " 限制是" + buf.limit());
        //4. 利用 get() 讀取緩衝區中的數據
        //4.1 abstract  byte get() 讀取單個字節。讀取此緩衝區當前位置的字節,然後該位置遞增。
//        byte b = buf.get();
//        System.out.println((char)b);//'a'
//        System.out.println((char)buf.get());//'b'
//        //容量是10 位置是2 限制是5
//        System.out.println("容量是" + buf.capacity() + " 位置是" + buf.position() + " 限制是" + buf.limit());
        //4.2 ByteBuffer get(byte[] dst)批量讀取多個字節到 dst 中
//        byte[] dst = new byte[buf.limit()];
//        System.out.println(dst.length);//5
//        buf.get(dst);
//        System.out.println(new String(dst, 0, dst.length));//abcde
//        //容量是10 位置是5 限制是5
//        System.out.println("容量是" + buf.capacity() + " 位置是" + buf.position() + " 限制是" + buf.limit());
        //4.3abstract  byte get(int index)讀取指定索引位置的字節(不會移動 position)
//        System.out.println((char)buf.get(0));//a
//        System.out.println((char)buf.get(1));//b
//        //容量是10 位置是0 限制是5
//        System.out.println("容量是" + buf.capacity() + " 位置是" + buf.position() + " 限制是" + buf.limit());
        for (int i = 0; i < buf.limit(); i++) {
            System.out.println((char)buf.get(i));
        }
    }

4.Channel通道

4.1概述

​ 1.由 java.nio.channels 包定義的。Channel(通道)表示 IO 源與目標打開的連接。Channel 類似於傳統的“流”。只不過 Channel 本身不能直接訪問數據,Channel 只能與Buffer 進行交互。

​ 2.Channel通道,可以去做讀取和寫入的操作。相當於我們之前學過的IO流。Channel是雙向的,一個對象既可以調用讀取的方法也可以調用寫出的方法。

​ 3.Channel在讀取和寫出的時候,要使用ByteBuffer作爲緩衝數組。

4.2分類

  • FileChannel:從本地文件讀寫數據的
  • DatagramChannel:讀寫UDP網絡協議數據
  • SocketChannel:讀寫TCP網絡協議數據,客戶端
  • ServerSocketChannel:可以監聽新進來的 TCP 連接,對每一個新進來的連接都會創建一個 SocketChannel,服務端

4.3獲取通道

獲取通道的一種方式是對支持通道的對象調用getChannel() 方法。支持通道的類如下:

FileInputStream : 獲取的是FileChannel通道

FileOutputStream:獲取的是FileChannel通道

 RandomAccessFile:獲取的是FileChannel通道

說明:

RandomAccessFile表示隨機訪問文件類,用來操作文件的。
    構造方法:
    	RandomAccessFile(String name, String mode) 
			參數:
    			name - 操作文件的路徑
    			mode -表示操作文件的模式。
    				取值:
    					“r”:以只讀的方式打開
    					“rw”:以讀、寫方式打開,支持文件的讀取或寫入。若文件不存在,則創建

 DatagramSocket:獲取的是DatagramChannel,關於UDP的通道

Socket:獲取的是SocketChannel客戶端的通道

ServerSocket:獲取的是ServerSocketChannel服務器端的通道

4.4 FileChannel基本使用

  • java.nio.channels.FileChannel (抽象類):用於讀、寫文件的通道。

  • FileChannel是抽象類,我們可以通過FileInputStream、FileOutputStream和RandomAccessFile的getChannel()方法方便的獲取一個它的子類對象。

需求:使用FileChannel將D:\test\故事.txt複製到項目根目錄下爲故事.txt
說明:源文件故事.txt文件大小是134字節

/*
        演示FileChannel完成文件複製
        需求:使用FileChannel將D:\test\故事.txt複製到項目根目錄下爲故事.txt
            說明:源文件故事.txt文件大小是134字節
     */
    @Test
    public void test8() throws Exception{
        //創建字節輸入流
        FileInputStream fis = new FileInputStream("D:\\test\\故事.txt");
        //創建字節輸出流
        FileOutputStream fos = new FileOutputStream("故事.txt");

        //獲取Channel
        FileChannel c1 = fis.getChannel();
        FileChannel c2 = fos.getChannel();
        //創建緩衝數組
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //從通道c1中讀取數據到buffer中
        while ((c1.read(buffer)) != -1) {
            c2.write(buffer);
        }

        //關流
        c1.close();
        c2.close();
        fos.close();
        fis.close();
    }

說明:按照上述代碼操作我們發現在項目根目錄下生成的故事.txt文件中都是空,並且文件大小是890字節.顯然代碼是有問題的。

解釋如下圖所示:

在這裏插入圖片描述

說明:當前position的位置是在索引爲134的位置,那麼如果直接使用write()方法寫數據,直接會將134索引後面的數據寫到指定文件中,所以就會出現上面的問題。

解決辦法:需要在寫之前調用flip()方法切換爲讀模式,position拿到0索引位置,limit拿到之前position位置。然後在寫,每次寫完之後調用clear()方法進行還原以達到所有指針都恢復到之前的位置方便下次讀寫。

在這裏插入圖片描述
修改後的代碼:

public void test8() throws Exception{
        //創建字節輸入流
        FileInputStream fis = new FileInputStream("D:\\test\\故事.txt");
        //創建字節輸出流
        FileOutputStream fos = new FileOutputStream("故事.txt");

        //獲取Channel
        FileChannel c1 = fis.getChannel();
        FileChannel c2 = fos.getChannel();
        //創建緩衝數組
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //從通道c1中讀取數據到buffer中
        while ((c1.read(buffer)) != -1) {
            //切換讀模式
            buffer.flip();
            c2.write(buffer);
            //還原
            buffer.clear();
        }

        //關流
        c1.close();
        c2.close();
        fos.close();
        fis.close();
    }

小結:我們將通過上述示例讓大家體會NIO的操作過程。執行三個基本的操作:

​ 1)創建一個Buffer;2)然後從源文件讀取數據到緩衝區 3)然後再將緩衝區寫入目標文件。

4.5 FileChannel結合MappedByteBuffer實現高效讀寫

​ 上例直接使用FileChannel結合ByteBuffer實現的管道讀寫,但並不能提高文件的讀寫效率。ByteBuffer有個子類:MappedByteBuffer,它可以創建一個“直接緩衝區”,並可以將文件直接映射至內存,可以提高大文件的讀寫效率。

原理:

在這裏插入圖片描述

鋼鐵俠和黑寡婦兩個人在不同的地方,使用全息投影技術將黑寡婦投影到鋼鐵俠家,比在不同的地方交流效率高。

在這裏插入圖片描述

說明:如果將數據從C盤複製到D盤,那麼在硬盤中複製效率不如將數據放到系統內存(直接緩衝區)中效率高。

  • ByteBuffer(抽象類)

    ​ |–MappedByteBuffer(抽象類)

  • 可以調用FileChannel的map()方法獲取一個MappedByteBuffer,map()方法的原型:

    MappedByteBuffer map(FileChannel.MapMode mode, long position, long size);

    	參數:
    		mode:FileChannel.MapMode.READ_ONLY 只讀映射模式。			                 			      FileChannel.MapMode.READ_WRITE 讀取/寫入映射模式 
    		position:文件中的位置,映射區域從此位置開始
    		size:要映射的區域大小
    

    ​ 說明:將節點中從position開始的size個字節映射到返回的MappedByteBuffer中。把硬盤中的讀寫變成內存中的讀寫。

  • FileChannel類的方法abstract long size() 返回此通道的文件的當前大小。

  • 2G以下文件複製

  • 需求:將D:\test\故事.txt賦值到項目根目錄

      	@Test
        public void test9() throws Exception{
            //RandomAccessFile
            //r是read的意思,也就是說能對這個文件做讀的操作
            RandomAccessFile f1 = new RandomAccessFile("D:\\test\\故事.txt","r");
    
            //rw是read/write的意思,也就是說能做讀寫的操作
            RandomAccessFile f2 = new RandomAccessFile("故事.txt","rw");
    
            //獲取Channel通道
            FileChannel c1 = f1.getChannel();
            FileChannel c2 = f2.getChannel();
    
            //獲取文件的大小
            long size = c1.size();
            //System.out.println("size = " + size);//size = 134
            //映射直接把硬盤中的數據映射到內存中
            //MapMode mode   操作方式
            //long position  起始位置
            // long size     大小
            MappedByteBuffer buffer1 = c1.map(FileChannel.MapMode.READ_ONLY, 0, size);
            MappedByteBuffer buffer2 = c2.map(FileChannel.MapMode.READ_WRITE, 0, size);
    
            //循環完成字節的複製
            for (long i = 0; i < size; i++) {
                //獲取一個字節
                byte b = buffer1.get();
                //保存到第二個數組中 由於是在直接緩衝區中,至於數組中的數據如何被寫到物理磁盤上,那麼不是我們能夠決定的了
                //而是系統決定的
                buffer2.put(b);
            }
    
            c2.close();
            c1.close();
            f2.close();
            f1.close();
        }
        }
    }
    
  • 2G以上文件複製(分塊操作)

  需求:複製D:\\上課視頻.zip文件到F:\\上課視頻.zip

如果繼續使用上述代碼進行復制超過2G以上的文件就會報錯。

代碼如下:

  @Test
      public void test10() throws Exception{
          //RandomAccessFile
          //r是read的意思,也就是說能對這個文件做讀的操作
          RandomAccessFile f1 = new RandomAccessFile("D:\\上課視頻.zip","r");
  
          //rw是read/write的意思,也就是說能做讀寫的操作
          RandomAccessFile f2 = new RandomAccessFile("F:\\上課視頻.zip","rw");
  
          //獲取Channel通道
          FileChannel c1 = f1.getChannel();
          FileChannel c2 = f2.getChannel();
  
          //獲取文件的大小
          long size = c1.size();  
          //System.out.println("size = " + size);//size = 134
          //映射直接把硬盤中的數據映射到內存中
          //MapMode mode   操作方式
          //long position  起始位置
          // long size     大小
          MappedByteBuffer buffer1 = c1.map(FileChannel.MapMode.READ_ONLY, 0, size);
          MappedByteBuffer buffer2 = c2.map(FileChannel.MapMode.READ_WRITE, 0, size);
  
          //循環完成字節的複製
          for (long i = 0; i < size; i++) {
              //獲取一個字節
              byte b = buffer1.get();
              //保存到第二個數組中 由於是在直接緩衝區中,至於數組中的數據如何被寫到物理磁盤上,那麼不是我們能夠決定的了
              //而是系統決定的
              buffer2.put(b);
          }
  
          c2.close();
          c1.close();
          f2.close();
          f1.close();
      }

報異常:

在這裏插入圖片描述

因爲我們這裏是將硬盤文件放到內存中,使用MappedByteBuffer類緩衝區,對於FileChannel類的映射方法map會對文件大小進行判斷,如果文件大小size超過int的最大值(大概是2G),那麼就會報異常。不能複製大於2G的文件,如果要想複製,我們可以採用將文件分塊的方式進行復制,如下圖所示:

說明:完成分塊複製需要使用到如下幾個變量:

  size              文件大小2600M
  everySize         期望每次複製的大小  500M
  count             塊數   size%everySize == 0 ? size/everySize:size/everySize+1
  start             每次複製的起始位置  everySize*i
      				for(int i=0;i<5;i++){}
  trueSize          實際每次複製的大小   size-start > everySize ? everySize : size - start
  • 圖解

在這裏插入圖片描述

  • 代碼

    public class Demo {
       @Test
        public void test11() throws Exception {
            //RandomAccessFile
            //r是read的意思,也就是說能對這個文件做讀的操作
            RandomAccessFile f1 = new RandomAccessFile("D:\\上課視頻.zip", "r");
    
            //rw是readwrite的意思,也就是說能做讀寫的操作
            RandomAccessFile f2 = new RandomAccessFile("F:\\上課視頻.zip", "rw");
    
            //獲取Channel通道
            FileChannel c1 = f1.getChannel();
            FileChannel c2 = f2.getChannel();
    
            //獲取文件的大小
            long size = c1.size();
            System.out.println("文件的大小 " + size);
    
            //每次期望複製500M
            //1)1024字節表示1KB  2) 1024*1024表示1MB   3)1024*1024*500 表示500MB
            int everySize = 1024 * 1024 * 500;
    
            //一共需要複製幾次
            long count = (size % everySize == 0 ? size / everySize : size / everySize + 1);
    
            //分塊複製 :如果size是2600M,每次複製大小是500M  那麼count=2600/500 + 1;
            //使用循環控制複製的次數
            for (long i = 0; i < count; i++) {
                //每次的開始位置
                long start = everySize * i;
                //每次實際複製大小
                long trueSize = (size - start > everySize ? everySize : size - start);
                //MapMode mode   操作方式
                //long position  起始位置  start 0 500 1000 1500 ....
                //long size     大小   trueSize 前面幾乎都一樣,最後一個有可能值會有變化
                MappedByteBuffer b1 = c1.map(FileChannel.MapMode.READ_ONLY, start, trueSize);
                MappedByteBuffer b2 = c2.map(FileChannel.MapMode.READ_WRITE, start, trueSize);
    
                //一個字節一個字節獲取和添加 把b1數值中的內容都複製到b2數組中
                for (long l = 0; l < trueSize; l++) {
                    byte b = b1.get();
                    b2.put(b);
                }
            }
    
            c2.close();
            c1.close();
            f2.close();
            f1.close();
        }
    }
    

4.6 網絡編程收發信息

  • 客戶端

    ​ 1.Java NIO中的SocketChannel是一個連接到TCP網絡套接字的通道,代替之前的Socket

    ​ 2.創建客戶端對象:

    ​ 創建客戶端對象不再使用構造方法,而是使用SocketChannel中的靜態方法,有兩種方式可以連接服務器端:

  1)使用靜態方法static SocketChannel open(SocketAddress add) 打開套接字通道並將其連接到遠程地址。 
     			 參數: add - 與新通道連接的遠程地址,屬於SocketAddress類型
                       --| SocketAddress 屬於抽象類
                           --| 子類構造方法:InetSocketAddress(String hostname, int port)                              根據主機名和端口號創建套接字地址。
      							參數:
                                      hostname:服務器ip地址
                                      port:服務器端口號
    代碼舉例:  SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
  2)使用靜態方法:
      	static SocketChannel open() 打開套接字通道。 但是沒有指定服務器端的ip地址和端口號
      	調用SocketChannel的實例方法connect(SocketAddress add)連接服務器:
   代碼舉例:
          //創建對象
          SocketChannel sc = SocketChannel.open();
          //連接服務器
          sc.connect(new InetSocketAddress("127.0.0.1",9898));

​ 3.向服務器發送數據必須藉助於Buffer緩衝區存儲數據,然後使用如下方法向通道寫數據

   int write(ByteBuffer src) 將字節序列從給定的緩衝區中寫入此通道。 

客戶端代碼如下:

  package com.itheima.sh.demo_17;
  
  import java.net.InetSocketAddress;
  import java.nio.ByteBuffer;
  import java.nio.channels.SocketChannel;
  
  public class ClientDemo {
      public static void main(String[] args) throws Exception{
          //創建對象
          //Socket s = new Socket("127.0.0.1",8888);
  
          //創建對象
  //        SocketChannel sc = SocketChannel.open();
  //        //連接服務器
  //        sc.connect(new InetSocketAddress("127.0.0.1",8888));
          SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
          //客戶端發數據
          //創建數組
          ByteBuffer buffer = ByteBuffer.allocate(1024);
          //數組中添加數據
          buffer.put("你好啊~".getBytes());
          //切換讀模式
          buffer.flip();
          //發出數據
          sc.write(buffer);
          //關流
          sc.close();
      }
  }
  

如果不調用flip方法,服務器端會接收到一大堆空的數據:

在這裏插入圖片描述

  • 服務器端

    ​ 1.Java NIO中的 ServerSocketChannel 是一個可以監聽新進來的TCP連接的通道,就像標準IO中

    的ServerSocket一樣.即ServerSocketChannel代替之前的ServerSocket,來完成網絡編程的收發數據。

    ​ 2.創建服務器端對象

    創建服務器端對象不再使用構造方法,而是使用ServerSocketChannel中的靜態方法:

  1)使用靜態方法static ServerSocketChannel open() 打開服務器套接字通道,通道的套接字最初未綁定;
               必須通過其套接字的bind方法將其綁定到特定地址,才能接受連接。
  2)ServerSocketChannel bind(SocketAddress local) 將通道的套接字綁定到本地地址,並配置套接字以	              監聽連接。  
               注:java.net.SocketAddress(抽象類):代表一個Socket地址。
               我們可以使用它的子類:java.net.InetSocketAddress()
    		     構造方法:InetSocketAddress(int port):指定本機監聽端口。
    代碼舉例:
          //創建
          ServerSocketChannel ssc = ServerSocketChannel.open();
          //服務器綁定端口
          ssc.bind(new InetSocketAddress(8888));     

​ 3.獲取客戶端連接的通道

  abstract SocketChannel accept() 接受與此通道套接字的連接。返回值是一個客戶端的 SocketChannel   

服務器端代碼演示:

  package com.itheima.sh.demo_17;
  
  import java.net.InetSocketAddress;
  import java.nio.ByteBuffer;
  import java.nio.channels.ServerSocketChannel;
  import java.nio.channels.SocketChannel;
  
  public class ServerDemo {
      public static void main(String[] args) throws Exception{
          //創建對象
          //ServerSocket ss = new ServerSocket(8888);
  
          //創建服務器對象
          ServerSocketChannel ssc = ServerSocketChannel.open();
          //服務器綁定端口
          ssc.bind(new InetSocketAddress(8888));
  
          //連接上客戶端
          System.out.println(1);
          //accept()此時阻塞了,一直在偵聽客戶端
          SocketChannel sc = ssc.accept();
          System.out.println(2);
          //服務器端接受數據
          //創建數組
          ByteBuffer buffer = ByteBuffer.allocate(1024);
          //接收數據
          int len = sc.read(buffer);
          //打印結構
          System.out.println(new String(buffer.array(),0,len));
  
          //關閉資源
          sc.close();
      }
  }
  

4.7 accept阻塞問題

​ 之前的accept是阻塞的方法,如果連接不到客戶端就一直等着。在NIO中可以設置爲非阻塞,設置非阻塞之後,就不會在accept()方法上一直停留。

​ 設置方式:

使用ServerSocketChannel調用:
    //參數block的值是 true表示阻塞,false表示非阻塞.非阻塞的意思是不管有沒有客戶端他都會往下執行不會停留
    //如果沒有客戶端,那麼ssc.accept()獲取的是null
	SelectableChannel configureBlocking(boolean block);
  • 服務器端
public class ServerDemo {
    public static void main(String[] args) throws Exception{
        //創建對象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //綁定端口
        ssc.bind(new InetSocketAddress(8888));

        //設置非阻塞連接
        /*
            寫成false叫非阻塞,非阻塞的意思是不管有沒有客戶端他都會往下執行不會停留
            如果沒有客戶端,那麼ssc.accept()獲取的是null
         */
        ssc.configureBlocking(false);
//        System.out.println(1);
//        SocketChannel sc = ssc.accept();
//        System.out.println(2);
//        System.out.println("sc = " + sc);//sc = null

        while(true) {
            //獲取客戶端連接
            SocketChannel sc = ssc.accept();
            System.out.println("sc = " + sc);

            if(sc != null){
                //不等於null說明連接上了客戶端
                System.out.println("連接上了。。");
                //讀取數據操作
                break;
            }else{
                //沒連接上客戶端
                System.out.println("打會兒遊戲~");
                Thread.sleep(2000);
            }
        }
    }
}
  • 客戶端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class ClientDemo{
    public static void main(String[] args) throws IOException {
      //創建客戶端套接字對象連接服務器端
      SocketChannel sc = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
    }
}

上述非阻塞應用場景舉例:

舉例:取快遞。快遞員相約12:00在大門見面
阻塞:我提前到大門口等待,對於我什麼都做不了就是阻塞。
非阻塞:快遞員什麼時候到,給我打電話我再去,期間我可以在家做其他事情,不至於一直等待什麼都做不了。非阻塞效率更高
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章