Java流NIO

什麼是NIO

Java NIO(NEW IO)是從Java1.4開始引入的新版IO,用來替代標準的Java IO API
NIO於原來的IO有相同的功能,但是他們之間的使用方式是完全不同的,NIO是面向緩衝區,面向通道的的IO操作,NIO擁有更加高效的進行文件讀寫。
另外NIO在網絡編程可以是一個無阻塞的IO交互,可以大大提升Socket交互的效率。

NIO於IO的區別

IO NIO
面向流(Stream Oriented) 面向緩衝區(Buffer Oriented)
阻塞IO(Block IO) 無阻塞IO(Non Block IO)
Selectors(選擇器)
單向通道 雙向通道

普通IO傳輸圖
NIO讀寫文件示意圖

NIO 三大核心內容

通道channel

在NIO中文件通道是不具備文件傳輸功能的,如果需要文件傳輸,必須依賴於緩衝區的存在。
通道常見實現類有:

ChannelFileChannelDatagramChannelSocketChannelServerSoc本地文件通道UDP網絡通道TCP/IP網絡通道TCP/IP網絡通道ChannelFileChannelDatagramChannelSocketChannelServerSoc

創建通道的方式FileChannel爲例

  1. Java 針對支持通道的類提供了getChannel()方法
  • 本地文件IO

  • FileInputStream/FileOutInputStream

  • RandomAccessFile

  • 網絡文件IO

  • Socket

  • ServerSocket

  • DatagramSocket

  1. 在JDK 1.7中NIO.2針對各個通道提供了靜態方法open()

  2. 在JDK 1.7中NIO.2的Files工具類的newByteChannel()

/**
* 採用支持通道的類,創建對應的文件通道
* 
**/
	public void test6() throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(new File("D:\\1.txt"));

        FileChannel channel = fileInputStream.getChannel();
    }

/**
     * 使用靜態方法獲得
     */
	public void test3()throws IOException{

        FileChannel open = FileChannel.open(Paths.get("src/psb (2).jpg"), StandardOpenOption.READ);

        FileChannel channel = FileChannel.open(Paths.get("psb.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
    }

Buffer緩衝區

緩衝區(buffer):在Java NIO 中負責數據存儲。緩衝區就是數組。用於存儲不同數據類型的數據
根據數據類型不同(Boolean除外),提供了相應類型的緩衝區:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntegerBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

在buffer緩衝區中有四個核心參數:

  • capacity:緩衝區中最大存儲數據的容量。一旦聲明不能改變
  • limit:界限,表示緩衝區中可以操作數據的大小。(limit後數據不能進行讀寫)
  • position:位置,表示緩衝區中正在操作數據的位置
  • mark:記錄,表示記錄當前position的位置。可以通過reset()恢復到mark的位置
  • mark <= position <= limit <= capacity 他們之間存在這樣的關係

buffer緩衝區分類

  • 非直接緩衝區
    通過allocate()方法分配非直接緩衝區,將緩衝區建立在jvm內存中
public class TestBuffer {

    public static void main(String[] args) {

        // 打開1024字節大小的緩衝區
        ByteBuffer allocate = ByteBuffer.allocate(1024);

        System.out.println("-------------allocate-----------");
        System.out.println(allocate.position());
        System.out.println(allocate.limit());
        System.out.println(allocate.capacity());

        // 利用put方法存入數據到緩衝區中

        System.out.println("--------------put---------------");
        allocate.put(new String("abc").getBytes());
        System.out.println(allocate.position());
        System.out.println(allocate.limit());
        System.out.println(allocate.capacity());

        // filp()方法進行讀寫模式轉換
        allocate.flip();

        System.out.println("--------------get---------------");

        // 將數據讀取到字節數組中
        byte[] bytes = new byte[allocate.limit()];
        allocate.get(bytes);

        System.out.println(new String(bytes,0,bytes.length));


        // rewind():可重複讀

        allocate.rewind();

        // clear:清空緩衝區,但是緩衝區中的數據依然存在,但是處於被遺忘狀態



        // 判斷緩衝區中是否還有可以操作的數據

        if (allocate.hasRemaining()) {
            // 打印還有多少數據可以操作
            System.out.println(allocate.remaining());
        }
    }

}

  • 直接緩衝區
    直接字節緩衝區可以通過調用此類的allocateDirect()工廠方法創建,此方法返回的緩衝區進行分配和取消分配所需成本通常高於非直接緩衝區。直接緩衝區的內容可以主流在常規的垃圾回收堆之外,因此,他們對應用程序內存需求量造成的影響可能並不明顯。所以,建議講直接緩衝區主要分配給那些易受基礎系統的本機IO操作影響、持久的緩衝區。一般情況下,最好盡在直接緩衝區能在程序性能方面帶來明顯好處時分配他們。

直接緩衝區還可以通過FilChannelmap()方法將文件區域直接銀蛇到內存中來創建。該方法返回MappedByteBuffer。Java平臺的實現有助於。

執行方法後各個參數值變化示意圖

  1. ByteBuffer allocate = ByteBuffer.allocate(1024); 分配了一個1024空間大小的緩衝區
    allocate分配空間後
  2. allocate.put(new String("abc").getBytes());向緩衝區放入三個字節數的字符串
    執行put方法後
  3. allocate.flip();利用filp()方法進行讀寫反轉
    filp()方法進行反轉

Selector選擇器

NIO之所以能在網絡編程上面有很大的提升,於這個關鍵元素有很大的關係
在這裏插入圖片描述

Selector
	選擇器,網絡編程使用NIO的大哥!!!
	服務器可以執行一個線程,運行Selector程序,進行監聽操作。
	新連接, 已經連接, 讀取數據,寫入數據

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

採用無阻塞IO實現網絡聊天室

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * <p>Title:TestBlockIO</p>
 * <p>Description:TestBlockIO ,測試無阻塞NIO</p>
 *
 * @author justLym
 * @version 1.0.0
 * @date 2019/11/23
 **/
public class TestBlockIO {

    @Test
    public void client() throws IOException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",6888));

        socketChannel.configureBlocking(false);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put("黎亞明".getBytes());

        buffer.flip();
        socketChannel.write(buffer);
        buffer.clear();

        socketChannel.close();
    }

    @Test
    public void server() throws IOException{

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.configureBlocking(false);

        serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",6888));

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        Selector  selector = Selector.open();

        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while(selector.select()>0){
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                if(selectionKey.isAcceptable()){
                    SocketChannel accept = serverSocketChannel.accept();
                    System.out.println(accept);
                    accept.configureBlocking(false);
                    accept.register(selector,SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()){
                    SocketChannel channel = (SocketChannel)selectionKey.channel();
                    channel.configureBlocking(false);
                    int len = 0;
                    while((len=channel.read(buffer))!=-1){
                        buffer.flip();
                        System.out.println(new String(buffer.array(),0,len));
                        buffer.clear();
                    }
                }
                iterator.remove();
            }
        }
        serverSocketChannel.close();
        selector.close();
    }
}

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