1.Junit單元測試框架
1.1 概述
junit是單元測試,你想測哪個方法就寫一個對應的測試方法,然後用junit運行。每個方法之間是獨立的,非常靈活。而且測試方法一般不會直接寫在原類中,而是單獨的測試類,這樣測試代碼就完全與邏輯代碼分開了。
Junit是Java語言編寫單元測試框架。Junit屬於第三方工具,一般情況下需要導入jar包,而多數Java開發環境都集成了Junit。
1.2 使用方式
問題:在之前,我們寫了一個方法,我們都需要使用通過main方法來調用。當我們需要測試的方法很多的時候,每次使用main方法來調用時是非常麻煩的。所以我們有了接下來的單元測試,在開發中使用調用方法就顯得特別的簡單。
創建java項目,並創建“com.itheima.sh.junit”包
-
編寫測試類
說明:如果以前想讓一個方法運行必須在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模型中的 Socket
和 ServerSocket
相對應的 SocketChannel
和 ServerSocketChannel
兩種不同的套接字通道實現。
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在大門見面
阻塞:我提前到大門口等待,對於我什麼都做不了就是阻塞。
非阻塞:快遞員什麼時候到,給我打電話我再去,期間我可以在家做其他事情,不至於一直等待什麼都做不了。非阻塞效率更高