Java--NIO入門

Java–NIO

Java傳統的IO是阻塞式的,而NIO則提供了非阻塞式的IO.

NIO即New IO, java從jdk1.4開始引入,用於解決阻塞式的處理,所以也可以說是No block IO.

NIO主要是用來處理Socket中阻塞的問題。

1、NIO組件

Java NIO 由以下幾個核心部分組成:

  • Channels
  • Buffers
  • Selectors

Channel

所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流。 數據可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。

Channel和Buffer有好幾種類型。下面是JAVA NIO中的一些主要Channel的實現:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

正如你所看到的,這些通道涵蓋了UDP 和 TCP 網絡IO,以及文件IO。

Buffer

以下是Java NIO裏關鍵的Buffer實現:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

這些Buffer覆蓋了你能通過IO發送的基本數據類型:byte, short, int, long, float, double 和 char。

selector

Selector允許單線程處理多個 Channel。如果你的應用打開了多個連接(通道),但每個連接的流量都很低,使用Selector就會很方便。例如,在一個聊天服務器中。

這是在一個單線程中使用一個Selector處理3個Channel的圖示:

img

要使用Selector,得向Selector註冊Channel,然後調用它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等。

2、Channel、Buffer

Channel和Buffer在一起使用,channel連接一個目的地,從Buffer中進行讀寫。

1、FileChannel、ByteBuffer

Java NIO中的FileChannel是一個連接到文件的通道。可以通過文件通道讀寫文件。

FileChannel無法設置爲非阻塞模式,它總是運行在阻塞模式下。

Buffer可以創建對應大小的緩存區。

打開FileChannel、創建ByteBuffer

通過使用一個InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例.

RandomAccessFile file=new RandomAccessFile("./a.txt","rw");
        
FileChannel fChannel= file.getChannel();

Buffer沒有公開的構造方法,使用allocate方法創建實例,傳入一個數字作爲容量。

ByteBuffer buffer=ByteBuffer.allocate(48);

Buffer中關於數據的讀寫起始位置,可以看做指針:

Buffer有三個屬性:

  • capacity:容量,即創建時指定的數值
  • position:定位,即當前緩衝中指向的位置
  • limit:極限:即當前Buffer中讀寫所處的極限位置。
操作Buffer和Channel

Channel和Buffer之間進行數據讀寫,和普通IO基本一樣,通過write和read方法。

在對Buffer進行讀寫,可以使用get,put方法。

Buffer進行讀寫之前需要對Buffer中的三個屬性進行調整,以便數據讀寫正確。

向Buffer中寫數據,需要使用clear()與compact()方法,如果調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。

如果Buffer中仍有未讀的數據,且後續還需要這些數據,但是此時想要先先寫些數據,那麼使用compact()方法。

從Buffer中讀數據,需要使用flip方法。

關閉Channel

Buffer不需要關閉,jvm會自動處理。

Channel關閉使用close方法即可。

從文件中讀寫數據
RandomAccessFile file=new RandomAccessFile("./a.txt","rw");
        
        FileChannel fChannel= file.getChannel();//獲取通道

        ByteBuffer buffer=ByteBuffer.allocate(48);//get Buffer

        int len=fChannel.read(buffer);//返回長度

        buffer.flip();//進入讀模式

        byte[] b=new byte[64];

        buffer.get(b,0,len);//讀到b中len個字節

        System.out.println(new String(b,0,len));//輸出

        buffer.clear();//全部讀完,進入寫模式

        String data="\nnow is: "+System.currentTimeMillis();
        buffer.put(data.getBytes());//向通道中寫入
        
        buffer.flip();//進入讀模式
        
        fChannel.write(buffer);//從Buffer中讀出

        file.close();

2、SocketChannel、ServerSocketChannel

Socket纔是NIO真正使用的地方。

ServerSocketChannel可以設置爲非阻塞式,這種情況下的網絡Io即不會阻塞。

configureBlocking(false);
打開/關閉Channel

Socket通過open打開連接,需要一個Socket地址。關閉使用close,非阻塞情況下連接可以自動關閉,無需顯示調用。

SocketChannel sChannel=SocketChannel.open();
 sChannel.connect(new InetSocketAddress("127.0.0.1", 8001));

操作

操作基本一樣,如下:

使用客戶端向服務端發送數據,服務端返回數據。

客戶端
SocketChannel sChannel=SocketChannel.open();

        sChannel.connect(new InetSocketAddress("127.0.0.1", 8001));

        ByteBuffer buffer=ByteBuffer.allocate(1024);

        buffer.put("hello world".getBytes());
        buffer.flip();

        sChannel.write(buffer);


        buffer.clear();
        int len=sChannel.read(buffer);

        buffer.flip();
        byte[] b=new byte[1024];
        buffer.get(b, 0, len);

        System.out.println(new String(b,0,len));
服務端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",8001));

        serverSocketChannel.configureBlocking(false);
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        while(true){
			
            //這裏不再阻塞,直接向下執行
            SocketChannel socketChannel =
                    serverSocketChannel.accept();

            if(socketChannel!=null){
                int len=socketChannel.read(buffer);

                buffer.flip();

                byte[] b=new byte[1024];

                buffer.get(b,0,len);
                System.out.println(new String(b,0,len));

                buffer.clear();

                buffer.put("yes".getBytes());

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

                break;

            }

3、Scatter|Gather

分散與聚集,通道可以與多個Buffer建立關聯。

寫入多個Buffer時,如果某個Buffer滿了,則轉入下個Buffer。

同樣,讀取時,某個Buffer被讀完,則從其他Buffer讀。

file=“b.txt”

abcdefghijkhello world
 RandomAccessFile rFile=new RandomAccessFile(file, "rw");

        ByteBuffer buffer1=ByteBuffer.allocate(10);
        ByteBuffer buffer2=ByteBuffer.allocate(10);
        ByteBuffer buffer3=ByteBuffer.allocate(10);

        ByteBuffer[] buffers={buffer1,buffer2,buffer3};

        FileChannel fc= rFile.getChannel();

        long l=fc.read(buffers);//讀到buffers中,buffer1滿則讀到buffer2
        System.out.println("buffers "+l);
        buffer3.flip();
        buffer2.flip();
        buffer1.flip();
        System.out.println((char)buffer1.get());
        System.out.println((char)buffer2.get());
        System.out.println((char)buffer3.get());


        buffer1.clear();
        buffer2.clear();
        buffer3.clear();
      
        buffer1.put("scatter".getBytes());
        buffer2.put("gatter".getBytes());

        buffer3.flip();
        buffer2.flip();
        buffer1.flip();
       
        fc.write(buffers);//從buffers中寫出,buffer1寫完,則從buffer2中寫。
       
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章