Java知識點——NIO和BIO

NIO和BIO

1. NIO和BIO

1.1 BIO概述
BIO
	BIO ==> Basic IO (基本IO), Block IO(阻塞IO)
	Scanner操作,文件讀寫操作,Socket數據傳輸操作... 都是BIO
	
	比如TCP羣聊,私聊聊天室
		Socket涉及到的IO,也是BIO
		資源浪費:
			1. 多線程,每一個Socket會對應一個線程,如果用戶量巨大,會導致線程過
			多,資源處理過多
			2. 採用阻塞狀態,一旦進入阻塞,代碼無法執行其他操作。
			3. 承載量一般,吞吐量比較小,同時可靠性不佳
1.2 NIO概述
NIO
	NIO ==> New IO(新IO), Non-Block IO(非阻塞IO)
	NIO非阻塞IO,允許當前程序在處理IO事務時,不會影響其他程序的運行,可以在不使用多線程的情況下,滿足IO操作要求。
	三大核心部分:
		通道
			Channel
			文件操作,網絡數據傳遞操作使用的通道
		緩衝
			Buffer
			緩衝使用可以提高操作效率,減少不必要的讀寫次數
		選擇器
			Selector
			真·核心 老大 boss

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jvUiQgVM-1584882587306)(img/NIO圖例.png)]

1.3 Buffer Channel完成文件操作
1.3.1 常用API
java.nio.Buffer
Buffer緩衝區
	ByteBuffer 字節緩衝 常用
	ShortBuffer
	IntBuffer
	LongBuffer 
	CharBuffer 字符緩衝 常用
	FloatBuffer
	DoubleBuffer

常用方法:
	public static ByteBuffer allocate(int capacity);
		按照指定的字節數分配對應的緩衝區空間,保存字節數據
	public byte get(); 
		從字節緩衝區對象中讀取一個byte類型數組
	public final Buffer flip();
		翻轉緩衝區,回到緩衝區的開始位置。
	public static ByteBuffer wrap(byte[] arr);
		存入一個byte類型數組到緩衝區,會得到一個新的ByteBuffer
	public static ByteBuffer put(byte[] b);
		將字節數組存入緩衝區
		

Channel接口,通道接口
	FileChannel 文件操作通道
	DatagramChannel UDP協議數據包操作的Channel
	ServerSocketChannel TCP服務端ServerSocket對應Channel
	SocketChannel TCP客戶端Socket對應Channel

首先操作文件,以FileChannel爲例
	public long read(ByteBuffer buffer);
		從通道中讀取數據到ByteBuffer中
	public long write(ByteBuffer buffer);
		從Buffer中寫數據到通道中
	public long transferFrom(ReadableByteChannel src, long position, long count) 
	從指定srcChannel中,指定位置position開始,讀取count個元素,到當前通道中
	文件複製操作。
	
	public long	transferTo(long position, long count, WritableByteChannel target) 
	將當前通道中的數據寫入到target中,從當前通道的position位置開始,計數count
1.3.2 操作文件數據
package com.qfedu.b_niofile;

import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
* @author Anonymous 2020/3/13 15:17
*/
public class FileNioTest {

   /*
   通過NIO寫入數據到文件中的操作
    */
   @Test
   public void testNioFileWrite() throws IOException {
       // 1. 文件操作字節輸出流
       FileOutputStream fos = new FileOutputStream("D:/aaa/1.txt");

       // 2. 利用文件操作輸出字節流對象獲取對應的Channel通道
       FileChannel foc = fos.getChannel();

       // 3. 準備一個緩衝區 4KB緩衝區
       ByteBuffer buffer = ByteBuffer.allocate(1024 * 4);

       // 4. 準備數據,放入緩衝區
       String str = "測試NIO";
       buffer.put(str.getBytes());

       // 5. 存在緩衝區數據放入之後,緩衝區指針發生改變,到達存入數據的末尾
       // 如果此時調用寫入操作,會從存入緩衝區之後開始保存
       // 讓緩衝區指針回到最初的起點,並且操作寫入程序,只會保存緩衝區內的有效數據
       buffer.flip();

       // 6. 緩衝區數據寫入到通道中
       foc.write(buffer);

       // 7. 關閉資源
       fos.close();
   }

   @Test
   public void testNioFileRead() throws IOException {
       // 1. 文件字節操作輸入流
       FileInputStream fis = new FileInputStream("D:/aaa/1.txt");

       // 2. FileChannel
       FileChannel fic = fis.getChannel();

       // 3. 準備緩衝
       ByteBuffer buffer = ByteBuffer.allocate(1024);

       // 4. 從Channel讀取數據保存到緩衝區中
       int read = fic.read(buffer);
       System.out.println(read);

       // 5. 展示數據
       // String(byte[] arr, int off, int count)
       System.out.println(new String(buffer.array(), 0, read));

       // 6. 關閉資源
       fis.close();
   }

   // 130
   @Test
   public void testCopyFile() throws IOException {
       long start = System.currentTimeMillis();
       // 1. 安排輸出流和輸入流
       FileInputStream fis = new FileInputStream("D:/aaa/1.mp4");
       FileOutputStream fos = new FileOutputStream("D:/aaa/2.mp4");

       // 2. 準備兩個Channel
       FileChannel srcChannel = fis.getChannel();
       FileChannel dstChannel = fos.getChannel();

       // 3. 拷貝方法
       srcChannel.transferTo(0, srcChannel.size(), dstChannel);
       // dstChannel.transferFrom(srcChannel, 0, srcChannel.size());

       // 4. 關閉資源
       fos.close();
       fis.close();

       long end = System.currentTimeMillis();
       System.out.println("Time:" + (end - start));
   }

   // 300
   @Test
   public void testCopyUseBuffer() throws IOException {
       long start = System.currentTimeMillis();
       BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/aaa/1.mp4"));
       BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/aaa/3.mp4"));

       int length = -1;
       byte[] buf = new byte[4 * 1024];

       while ((length = bis.read(buf)) != -1) {
           bos.write(buf, 0, length);
       }

       bos.close();
       bis.close();
       long end = System.currentTimeMillis();
       System.out.println("Time:" + (end - start));
   }
}
1.4 網絡編程使用NIO【重點】
1.4.1 Selector選擇器老大
Selector
	選擇器,網絡編程使用NIO的大哥!!!
	服務器可以執行一個線程,運行Selector程序,進行監聽操作。
	新連接, 已經連接, 讀取數據,寫入數據

Selector常用方法:
	public static Selector Open();
		得到一個選擇器對象
	public int select(long timeout);
		監聽所有註冊通道,存在IO流操作是,會將對應的信息SelectionKey存入到內部的集
		閤中,參數是一個超時時間
	public Set<SelectionKey> selectionKeys();
		返回當前Selector內部集合中保存的所有SelectionKey

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sqrm92Ed-1584882587307)(img/Selector大哥.png)]

1.4.2 SelectionKey
SelectionKey
	表示Selector和網絡通道之間的關係
	int OP_ACCEPT; 16 需要連接
	int OP_CONNECT; 8  已經連接
	int OP_READ; 1  讀取操作
	int OP_WRITE; 4 寫入操作
SelectionKey
	public abstract Selector selector();
		得到與之關聯的 Selector 對象
	public abstract SelectableChannel channel();
		得到與之關聯的通道
	public final Object attachment();
		得到與之關聯的共享數據
	public abstract SelectionKey interestOps(int ops);
		設置或改變監聽事件
	public final boolean isAcceptable();
		是否可以 accept
	public final boolean isReadable();
		是否可以讀
	public final boolean isWritable();
		是否可以寫
1.4.3 ServerSocketChannel
ServerSocketChannel
	服務端Socket程序對應的Channel通道
常用方法:
	public static ServerSocketChannel open();
		開啓服務器ServerSocketChannel通道,等於開始服務器程序
	public final ServerSocketChannel bind(SocketAddress local);
		設置服務器端端口號
	public final SelectableChannel configureBlocking(boolean block);
		設置阻塞或非阻塞模式, 取值 false 表示採用非阻塞模式
	public SocketChannel accept();
		[非阻塞]
			獲取一個客戶端連接,並且得到對應的操作通道
	public final SelectionKey register(Selector sel, int ops);
		[重點方法]
			註冊當前選擇器,並且選擇監聽什麼事件
1.4.4 SocketChannel
SocketChannel
	客戶端Socket對應的Channel對象

常用方法:
	public static SocketChannel open();
		打卡一個Socket客戶端Channel對象
	public final SelectableChannel configureBlocking(boolean block)
		這裏可以設置是阻塞狀態,還是非阻塞狀態
		false,表示非阻塞
	public boolean connect(SocketAddress remote);
		連接服務器
	public boolean finishConnect();
		如果connect連接失敗,可以通過finishConnect繼續連接
	public int write(ByteBuffer buf);
		寫入數據到緩衝流中
	public int read(ByteBuffer buf);	、
		從緩衝流中讀取數據
	public final SelectionKey register(Selector sel, int ops, Object attechment);
		註冊當前SocketChannel,選擇對應的監聽操作,並且可以帶有Object attachment參數
	public final void close();
		關閉SocketChannel
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章