[ Java ] 最通俗易懂的 Java NIO 講解

IO 與 NIO 的比較

IO NIO
面向流(Stream Oriented) 面向緩衝區(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
選擇器(Selectors)

通道和緩衝區

  • 通道(Channel):負責傳輸(鐵路)
  • 緩衝區(Buffer):負責存儲(火車)

緩衝區(buffer)

一、根據數據類型的不同,提供了相應類型的緩衝區(Boolean 除外)

ByteBuffer
CharBuffer
IntBuffer
LongBuffer
ShortBuffer
FloatBuffer
DoubleBuffer

上述緩衝區的管理方式幾乎一致,通過 allocate() 獲取緩衝區

二、緩衝區存取數據的兩個核心方法

  • put() : 存入數據到緩衝區
  • get() : 獲取緩衝區中的數據

三、緩衝區的四個核心屬性

  • capacity:容量,緩衝區中最大存儲數據的容量,一旦聲明不能改變
  • limit:界限,緩衝區中可以操作的數據的大小,(limit之後的數據不能進行讀寫)
  • position:位置,緩衝區中正在操作的數據的位置
  • mark:記錄當前 position 的位置,可以通過 reset(),恢復到 mark 的位置
  • 0 <= mark <= position <= limit <= capacity
  • positionlimit 就像兩個指針(或者遊標)在 0capacity 之間移動

四、直接緩衝區,非直接緩衝區

  • 非直接緩衝區:通過 allocate() 方法分配緩衝區,將緩衝區建立在 JVM 的內存中
  • 直接緩衝區:通過 alocateDir() 方法分配直接緩衝區,將緩衝區建立在物理內存,可以提高效率
  • 判斷是否爲直接緩衝區:buffer.isDirect();

五、代碼實踐

void contextLoads() {
		// 分配指定大小的緩衝區
		System.out.println("============== allocate ================");
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 0
		System.out.println(buffer.limit()); // 1024
		
		// 添加數據
		System.out.println("============== put ================");
		String str = "brave";
		buffer.put(str.getBytes());
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 5
		System.out.println(buffer.limit()); // 1024

		// 切換到讀數據模式
		System.out.println("============== flip ================");
		buffer.flip();
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 0
		System.out.println(buffer.limit()); // 5

		// 讀取數據
		System.out.println("============== get ================");
		byte[] bst = new byte[buffer.limit()];
		buffer.get(bst);
		System.out.println(new String(bst)); // brave
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 5
		System.out.println(buffer.limit()); // 5

		// 可重複讀
		System.out.println("============== rewind ================");
		buffer.rewind();
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 0
		System.out.println(buffer.limit()); // 5

		// 清空緩衝區(數據依然存在)
		System.out.println("============== clear ================");
		buffer.clear();
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 0
		System.out.println(buffer.limit()); // 1024

		System.out.println((char)buffer.get()); // b
	}
void bufTest(){
		System.out.println("========= mark =========");
		ByteBuffer buffer = ByteBuffer.allocate(1024);

		String str = "brave";
		buffer.put(str.getBytes());
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 5
		System.out.println(buffer.limit()); // 1024

		buffer.flip();
		byte[] bst = new byte[buffer.limit()];
		buffer.get(bst,0,2);
		System.out.println(new String(bst,0,2)); // br
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 2
		System.out.println(buffer.limit()); // 5

		// 標記 position = 2
		buffer.mark();

		buffer.get(bst,2,2);
		System.out.println(new String(bst,2,2)); // av
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 4
		System.out.println(buffer.limit()); // 5

		// 返回到標記點 position = 2
		buffer.reset();
		System.out.println(buffer.capacity()); // 1024
		System.out.println(buffer.position()); // 2
		System.out.println(buffer.limit()); // 5

		// 緩衝區中是否還有數據
		if(buffer.hasRemaining()){
			// 返回緩衝區中剩餘的數據個數
			System.out.println(buffer.remaining()); // 3
		}
	}

通道(channel)

一、通道的主要實現類

  • java.nio.channels.Channel 接口:
    |-- FileChannel(本地文件)
    |-- SocketChannel(TCP)
    |-- ServerSocketChannel(TCP)
    |-- DategramChannel(UDP)

二、獲取通道

  • 1、java 針對支持通道的類提供了 getChannel() 方法
    • 本地IO:
      FileInputStream / FileOutputStream
      RandomAccessFile
    • 網絡IO:
      Socket
      ServerSocket
      DatagramSocket
  • 2、在 JDK1.7 中的 NIO.2 針對各個通道提供了靜態方法 open()
  • 3、在 JDK1.7 中的 NIO.2 的 Files 工具類的 newByteChannel()

三、通道之間的數據傳輸

  • transferFrom()
  • transferTo()

四、分散(Scatter)與聚集(Gather)

  • 分散讀取(Scattering Reads):將通道中的數據分散到多個緩衝區中
  • 聚集寫入(Gathering Writes):將多個緩衝區中的數據聚集到通道中

五、字符集:CharSet

  • 編碼:字符串 -> 字符數組
  • 解碼:字符數組 -> 字符串

使用 NIO 完成本地通信

  • 利用通道完成文件複製
	@Test
	void channelTest() throws Exception{
		FileInputStream fis = new FileInputStream("1.jpg");
		FileOutputStream fos = new FileOutputStream("2.jpg");
		
		// 獲取通道
		FileChannel inChannel = fis.getChannel();
		FileChannel outChannel = fos.getChannel();

		// 分配指定大小的緩衝區
		ByteBuffer buf = ByteBuffer.allocate(1024);

		// 將通道中的數據存入緩衝區
		while(inChannel.read(buf) != -1){
			// 切換到讀數據模式
			buf.flip();
			// 將緩衝區中的數據寫入通道
			outChannel.write(buf);
			// 清空通道
			buf.clear();
		}

		outChannel.close();
		inChannel.close();
		fos.close();
		fis.close();
	} 
  • 使用直接緩衝區完成文件的複製(內存映射文件)
	@Test
	void channelTest() throws Exception{
		FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
		
		// 內存映射文件
		MappedByteBuffer inMappedByteBuffer = inChannel.map(MapMode.READ_ONLY,0, inChannel.size());
		MappedByteBuffer outMappedByteBuffer = outChannel.map(MapMode.READ_WRITE,0, inChannel.size());
		
		// 直接對緩衝區進行數據的讀寫操作
		byte[] dst = new byte[inMappedByteBuffer.limit()];
		inMappedByteBuffer.get(dst);
		outMappedByteBuffer.put(dst);

		outChannel.close();
		inChannel.close();
	} 
  • 通道之間的數據傳輸(直接緩衝區)
	@Test
	void channelTest() throws Exception{
		FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
		FileChannel outChannel = FileChannel.open(Paths.get("5.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
		
		// inChannel.transferTo(0, inChannel.size(),outChannel);
		outChannel.transferFrom(inChannel, 0, inChannel.size());
		outChannel.close();
		inChannel.close();
	} 
  • 分散讀取,聚集寫入
@Test
	void channelTest() throws Exception{
		RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");

		// 獲取通道
		FileChannel channel = raf1.getChannel();

		// 分配指定大小的緩衝區
		ByteBuffer buf1 = ByteBuffer.allocate(100);
		ByteBuffer buf2 = ByteBuffer.allocate(1024);
		
		// 分散讀取
		ByteBuffer[] bufs = {buf1, buf2};
		channel.read(bufs);

		for(ByteBuffer byteBuffer : bufs){
			byteBuffer.flip();
		}

		System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
		System.out.println("---------------------");
		System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));

		// 聚集寫入
		RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");
		FileChannel channel2 = raf2.getChannel();
		channel2.write(bufs);

		raf1.close();
		raf2.close();
	} 
  • 字符編碼
// 查看所有支持的編碼
void charset() throws Exception{
	Map<String,Charset> map = Charset.availableCharsets();
	Set<Entry<String,Charset>> set = map.entrySet();
 
	for(Entry<String,Charset> entry:set){
		System.out.println(entry.getKey()+"="+entry.getValue());
	}
} 

// 編碼器,解碼器
	void charsetTest() throws Exception{
		Charset cs1 = Charset.forName("GBK");

		// 獲取編碼器
		CharsetEncoder ce = cs1.newEncoder();

		// 獲取解碼器
		CharsetDecoder cd = cs1.newDecoder();

		CharBuffer cBuf = CharBuffer.allocate(1024);
		String str = "心有猛虎細嗅薔薇";
		
		cBuf.put(str);
		cBuf.flip();

		// 編碼
		ByteBuffer bBuf = ce.encode(cBuf);

		for(int i= 0;i < str.length() * 2;i++){
			System.out.println(bBuf.get());
		}

		// 解碼
		bBuf.flip();
		CharBuffer cBuf2 = cd.decode(bBuf);
		System.out.println(cBuf2.toString());
	}

使用 NIO 完成網絡通信

一、通道(Channel) :負責連接

  • java . nio. channels .Channel 接口
    • SelectableChannel
      • SocketChannel
      • ServerSocketChannel
      • Datagr amChannel
      • Pipe. SinkChannel
      • Pipe. SourceChannel

二、緩衝區(Buffer) :負責數據的存取
三、迭擇器(Selector) :

  • SelectableChannel 的多路複用器。用於監控 SelectableChannel 的 IO 狀況

四、代碼實踐

  • 阻塞式 IO
public class Aserver {
    public static void main(String[] args) throws Exception{
        // 獲取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        FileChannel outChannel = FileChannel.open(Paths.get("3.txt"),StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
        // 綁定連接
        ssChannel.bind(new InetSocketAddress(9898));
        // 獲取客戶端鏈接的通道
        SocketChannel sChannel = ssChannel.accept();
        // 分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);

        // 接收客戶端的數據,保存到本地
        while(sChannel.read(buf) != -1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
        // 發送反饋給客戶端
        buf.put("服務端接收數據成功".getBytes());
        buf.flip();
        sChannel.write(buf);

        // 關閉通道
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }
}

public class Aclient {
    public static void main(String[] args) throws Exception{
        // 獲取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        FileChannel inChannel = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);

        // 分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);

        // 讀取本地文件,發送到服務器
        while(inChannel.read(buf) != -1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }

        sChannel.shutdownOutput();

        // 接收服務端反饋
        int len = 0;
        while((len = sChannel.read(buf)) != -1){
            buf.flip();
            System.out.println(new String(buf.array(),0,len));
            buf.clear();
        }

        // 關閉通道
        inChannel.close();
        sChannel.close();
    }
}
  • 非阻塞式 IO
public class Aserver {
    public static void main(String[] args) throws Exception{
        // 獲取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        // 切換成非阻塞模式
        ssChannel.configureBlocking(false);

        // 綁定連接
        ssChannel.bind(new InetSocketAddress(9898));

        // 獲取選擇器
        Selector selector = Selector.open();

        // 將通道註冊到選擇器,指定監聽事件
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 輪詢式的獲取選擇器上已經準備就緒的事件
        while(selector.select() >0 ){
            // 獲取當前選擇器中所有註冊的"選擇鍵(已就緒的監聽事件)"
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while(it.hasNext()){
                // 獲取準備就緒的事件
                SelectionKey sk = it.next();

                // 判斷具體是什麼事件準備就緒
                if(sk.isAcceptable()){
                    // 若“接收就緒”,獲取客戶端連接
                    SocketChannel sChannel = ssChannel.accept();

                    // 切換成非阻塞模式
                    sChannel.configureBlocking(false);

                    // 將通道註冊到選擇器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                }else if (sk.isReadable()){
                    // 獲取當前選擇器上“讀就緒”狀態的通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();

                    // 讀取數據
                    ByteBuffer buf = ByteBuffer.allocate(1024);

                    int len = 0;
                    while((len = sChannel.read(buf)) >0 ){
                        buf.flip();
                        System.out.println(new String(buf.array(),0,len));
                        buf.clear();
                    }
                }
                // 取消 SelectionKey
                it.remove();
            }
        }
        // 關閉通道
//        ssChannel.close();
    }
}
public class Aclient {
    public static void main(String[] args) throws Exception{
        // 獲取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));

        // 切換成非阻塞模式
        sChannel.configureBlocking(false);

        // 分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);

        // 發送數據給服務端
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String str = scanner.next();
            buf.put((new Date().toString() + "   " + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }

        // 關閉通道
        sChannel.close();
    }
}

  • 基於 UDP 實現的非阻塞式 IO
public class Areceive {
    public static void main(String[] args) throws IOException {
        DatagramChannel dc = DatagramChannel.open();
        dc.configureBlocking(false);
        dc.bind(new InetSocketAddress(9898));
        Selector selector = Selector.open();
        dc.register(selector, SelectionKey.OP_READ);

        while(selector.select() > 0){
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while (it.hasNext()){
                SelectionKey sk = it.next();
                if(sk.isReadable()){
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    dc.receive(buf);
                    buf.flip();
                    System.out.println(new String(buf.array(),0,buf.limit()));
                    buf.clear();
                }
            }
            it.remove();
        }
    }
}
public class Asend {

    public static void main(String[] args) throws IOException {
        DatagramChannel dc = DatagramChannel.open();

        dc.configureBlocking(false);
        ByteBuffer buf = ByteBuffer.allocate(1024);

        Scanner scan = new Scanner(System.in);
        while(scan.hasNext()){
            String str = scan.next();
            buf.put((new Date().toString() + "    "+ str).getBytes());
            buf.flip();
            dc.send(buf,new InetSocketAddress("127.0.0.1",9898));
            buf.clear();
        }
        dc.close();
    }
}

管道

  • Java NIO 管道是2個線程之間的單向數據連接。
  • Pipe 有一個 source 通道和一個 sink 通道。
  • 數據會被寫到 sink 通道,從 source 通道 讀取。
public class Test {
    public static void main(String[] args) throws IOException {
        // 獲取管道
        Pipe pipe = Pipe.open();
        // 將緩衝區中的數據寫入管道
        ByteBuffer buf = ByteBuffer.allocate(1024);
        Pipe.SinkChannel sinkChannel = pipe.sink();
        buf.put("通過單向管道發送數據".getBytes());
        buf.flip();
        sinkChannel.write(buf);

        // 讀取緩衝區中的數據
        Pipe.SourceChannel sourceChannel = pipe.source();
        buf.flip();
        int len = sourceChannel.read(buf);
        System.out.println(new String(buf.array(),0,len));

        sinkChannel.close();
        sourceChannel.close();

    }
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章