Java非阻塞式IO起步

與傳統IO的區別

  1. Java NIO( New IO) 是從Java 1.4版本開始引入的一個新的IO API,可以替代標準的Java IO API。NIO與原來的IO有同樣的作用和目的,但是使用的方式完全不同, NIO支持面向緩衝區的、基於通道的IO操作,雙向的。 NIO將以更加高效的方式進行文件的讀寫操作。
IO NIO
面向流(Stream Oriented) 面向緩衝區(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
(無) 選擇器(Selectors)
  1. 通道和緩衝區
    Java NIO系統的核心在於:通道(Channel)和緩衝區(Buffer)。通道表示打開到 IO 設備(例如:文件、套接字)的連接。若需要使用 NIO 系統,需要獲取用於連接 IO 設備的通道以及用於容納數據的緩衝區。然後操作緩衝區,對數據進行處理。

簡而言之, hannel 負責傳輸, Buffer 負責存儲

  1. 總體來說NIO有兩大點需要掌握
    (1)通道
    (2)緩衝區

緩衝區

  1. 緩衝區(Buffer):在 Java NIO 中負責數據的存取。緩衝區就是數組。用於存儲不同數據類型的數據根據數據類型不同(boolean 除外),提供了相應類型的緩衝區:
    ByteBuffer
    CharBuffer
    ShortBuffer
    IntBuffer
    LongBuffer
    FloatBuffer
    DoubleBuffer
    上述緩衝區的管理方式幾乎一致,通過 allocate() 獲取緩衝區

  2. 緩衝區存取數據的兩個核心方法:
    put() : 存入數據到緩衝區中
    get() : 獲取緩衝區中的數據

  3. 緩衝區中的四個核心屬性:
    capacity : 容量,表示緩衝區中最大存儲數據的容量。一旦聲明不能改變。
    limit : 界限,表示緩衝區中可以操作數據的大小。(limit 後數據不能進行讀寫)
    position : 位置,表示緩衝區中正在操作數據的位置。
    mark : 標記,表示記錄當前 position 的位置。可以通過 reset() 恢復到 mark 的位置

  • 0 <= mark <= position <= limit <= capacity
  1. 直接緩衝區與非直接緩衝區:
    非直接緩衝區:通過 allocate() 方法分配緩衝區,將緩衝區建立在 JVM 的內存中
    直接緩衝區:通過 allocateDirect() 方法分配直接緩衝區,將緩衝區建立在物理內存中。可以提高效率
import java.nio.ByteBuffer;
import org.junit.Test;
public class TestBuffer {
	
	@Test
	public void test3(){
		//分配直接緩衝區
		ByteBuffer buf = ByteBuffer.allocateDirect(1024);
		
		System.out.println(buf.isDirect());
	}
	
	@Test
	public void test2(){
		String str = "abcde";
		
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		buf.put(str.getBytes());
		
		buf.flip();
		
		byte[] dst = new byte[buf.limit()];
		buf.get(dst, 0, 2);
		System.out.println(new String(dst, 0, 2));
		System.out.println(buf.position());
		
		//mark() : 標記
		buf.mark();
		
		buf.get(dst, 2, 2);
		System.out.println(new String(dst, 2, 2));
		System.out.println(buf.position());
		
		//reset() : 恢復到 mark 的位置
		buf.reset();
		System.out.println(buf.position());
		
		//判斷緩衝區中是否還有剩餘數據
		if(buf.hasRemaining()){
			
			//獲取緩衝區中可以操作的數量
			System.out.println(buf.remaining());
		}
	}
	
	@Test
	public void test1(){
		String str = "abcde";
		
		//1. 分配一個指定大小的緩衝區
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		System.out.println("-----------------allocate()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//2. 利用 put() 存入數據到緩衝區中
		buf.put(str.getBytes());
		
		System.out.println("-----------------put()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//3. 切換讀取數據模式
		buf.flip();
		
		System.out.println("-----------------flip()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//4. 利用 get() 讀取緩衝區中的數據
		byte[] dst = new byte[buf.limit()];
		buf.get(dst);
		System.out.println(new String(dst, 0, dst.length));
		
		System.out.println("-----------------get()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//5. rewind() : 可重複讀
		buf.rewind();
		
		System.out.println("-----------------rewind()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		//6. clear() : 清空緩衝區. 但是緩衝區中的數據依然存在,但是處於“被遺忘”狀態
		buf.clear();
		
		System.out.println("-----------------clear()----------------");
		System.out.println(buf.position());
		System.out.println(buf.limit());
		System.out.println(buf.capacity());
		
		System.out.println((char)buf.get());	
	}
}

通道Channel

  • 通道( Channel):由 java.nio.channels 包定義的。 Channel 表示 IO 源與目標打開的連接。Channel 類似於傳統的“流”。只不過 Channel本身不能直接訪問數據, Channel 只能與Buffer 進行交互。
import org.junit.Test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Objects;


public class ChannelTest {

    //字符集的編碼與解碼
    @Test
    public void test5(){
        //獲取字符集對象
        Charset charset = Charset.forName("UTF-8");
        //根據字符集對象獲取編碼器對象和解碼器對象
        CharsetEncoder charsetEncoder = charset.newEncoder();
        CharsetDecoder charsetDecoder = charset.newDecoder();

        CharBuffer cb = CharBuffer.allocate(1024);
        String str = "nio字符編解碼測試";
        cb.put(str);
        cb.flip();
        try {
            ByteBuffer ebuf = charsetEncoder.encode(cb);
            
            //此時注意:get方法會使position指針後移,
            for (int i = 0; i < ebuf.limit(); i++) {
                System.out.println(ebuf.get());
            }
            
            /**而flip不是轉換讀取模式,源碼中會進行如下操作:
             * limit = position;
             * position = 0;
             * mark = -1;
             * 如果position沒有位移,直接使用position會導致limit爲0
             */
            ebuf.flip();

            CharBuffer dbuf = charsetDecoder.decode(ebuf);
            System.out.println(dbuf.toString());

        } catch (CharacterCodingException e) {
            e.printStackTrace();
        }
    }

    //分散於聚集
    @Test
    public void test4(){
        FileChannel inCha = null;
        FileChannel outCha = null;
        try {
            inCha = FileChannel.open(Paths.get("1.jpg"),
                    StandardOpenOption.READ);
            outCha = FileChannel.open(Paths.get("2.jpg"),
                    StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
            ByteBuffer buf1 = ByteBuffer.allocate(100);
            ByteBuffer buf2 = ByteBuffer.allocate(1024);

            ByteBuffer[] bufs = new ByteBuffer[]{buf1, buf2};

            while (inCha.read(bufs) != -1) {
                //多個緩衝區同時轉換
                for (ByteBuffer buf : bufs) {
                    buf.flip();
                }

                outCha.write(bufs);
                //寫入完成後pos和limit歸位
                for (ByteBuffer buf : bufs) {
                    buf.clear();
                }
            }


        } catch (Exception e) {

        } finally {
            try {
                inCha.close();
                outCha.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    //通道間傳輸
    @Test
    public void test3() {
        FileChannel inCha = null;
        FileChannel outCha = null;
        try {
            inCha = FileChannel.open(Paths.get("1.jpg"),
                    StandardOpenOption.READ);
            outCha = FileChannel.open(Paths.get("2.jpg"),
                    StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
            inCha.transferTo(0, inCha.size(), outCha);
        } catch (Exception e) {

        } finally {
            try {
                inCha.close();
                outCha.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Test
    public void test2() {
        FileChannel inCha = null;
        FileChannel outCha = null;
        try {
            inCha = FileChannel.open(Paths.get("1.jpg"),
                    StandardOpenOption.READ);
            outCha = FileChannel.open(Paths.get("3.jpg"),
                    StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

            //物理內存映射
            MappedByteBuffer inMap = inCha.map(FileChannel.MapMode.READ_ONLY,
                    0, inCha.size());
            MappedByteBuffer outMap = outCha.map(FileChannel.MapMode.READ_WRITE,
                    0, inCha.size());

            byte[] buf = new byte[inMap.limit()];
            inMap.get(buf);
            outMap.put(buf);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                Objects.requireNonNull(inCha).close();
            } catch (IOException e) {
            }
            try {
                Objects.requireNonNull(outCha).close();
            } catch (IOException e) {
            }
        }

    }

    //利用通道進行文件複製
    //非直接緩衝區方式
    @Test
    public void test1() {
        File file1 = new File("1.jpg");
        File file2 = new File("2.jpg");

        FileOutputStream fos = null;
        FileInputStream fis = null;

        FileChannel chi = null;
        FileChannel cho = null;
        try {
            //創建流對象
            fis = new FileInputStream(file1);
            fos = new FileOutputStream(file2);
            //獲取通道
            chi = fis.getChannel();
            cho = fos.getChannel();

            //建立緩衝區
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            while (chi.read(buffer) != -1) {
                //切換讀取模式
                buffer.flip();

                cho.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();

        } finally {
            try {
                fos.close();
                fis.close();
                chi.close();
                cho.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

網絡部分

使用 NIO 完成網絡通信的三個核心:

  1. 通道(Channel):負責連接

    java.nio.channels.Channel 接口:
     	|--SelectableChannel
     		|--SocketChannel
     		|--ServerSocketChannel
     		|--DatagramChannel
    
     		|--Pipe.SinkChannel
     		|--Pipe.SourceChannel
    
  2. 緩衝區(Buffer):負責數據的存取

  3. 選擇器(Selector):是 SelectableChannel 的多路複用器。用於監控 SelectableChannel 的 IO 狀況


基於通道的普通阻塞式網絡通信演示

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class NetSocketTest {
    @Test
    public void client(){
        SocketChannel sc = null;
        FileChannel fc = null;
        try {
            sc = SocketChannel.open(new InetSocketAddress("192.168.0.102",18888));
            fc = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
            ByteBuffer buf = ByteBuffer.allocate(1024*1024);
            while (fc.read(buf)!=-1){
                buf.flip();
                sc.write(buf);
                buf.clear();
            }
            sc.shutdownOutput();
        } catch (IOException e) {
        }finally {
            try {
                fc.close();
                sc.close();
            } catch (IOException e) {
            }
        }
    }
    @Test
    public void Server(){
        ServerSocketChannel ssc = null;
        FileChannel fc = null;
        ServerSocketChannel sscPort = null;
        try {
            ssc = ServerSocketChannel.open();
            sscPort = ssc.bind(new InetSocketAddress(18888));
            fc = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
            ByteBuffer buf =ByteBuffer.allocate(1024*1024);
				
			//阻塞式方法
            SocketChannel accept = sscPort.accept();
            while (accept.read(buf) != -1) {
                buf.flip();
                fc.write(buf);
                buf.clear();
            }
            accept.shutdownInput();
        } catch (IOException e) {
        }finally {
            try {
                fc.close();
                sscPort.close();
                ssc.close();
            } catch (IOException e) {
            }
        }
    }
}

基於選擇器的無阻塞式網絡通信

public class NetSocketTest2 {
    @Test
    public void Client() {
        SocketChannel sc = null;
        FileChannel file = null;
        try {
            sc = SocketChannel.open(new InetSocketAddress("192.168.0.103", 18888));
            file = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
            //切換爲非阻塞式
            sc.configureBlocking(false);

            ByteBuffer buf = ByteBuffer.allocate(1024 * 1024);
            while (file.read(buf) != -1) {
                buf.flip();
                sc.write(buf);
                buf.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                file.close();
                sc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @Test
    public void Server() {
        ServerSocketChannel ssc = null;
        FileChannel file = null;
        try {
            //將客戶端的數據存儲到本地
            file = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

            //獲取服務端通道
            ssc = ServerSocketChannel.open();
            //設置爲非阻塞式
            ssc.configureBlocking(false);
            //綁定服務監聽端口號
            ssc.bind(new InetSocketAddress(18888));

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

            //選擇器監控通道,因此
            //將通道註冊選擇器上,並設置選擇鍵,選擇鍵是明確選擇器監控的是那種狀態
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            //迭代選擇器上已經準備好了的事件(OP_ACCEPT)
            while (selector.select() > 0) {//如果選擇器上準備就緒的事件大於0
                //如果有就緒的事件,就把它們拿出來迭代,
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();

                while (it.hasNext()){
                    SelectionKey selectionKey = it.next();
                    //判斷具體什麼事件準備就緒,
                    if (selectionKey.isAcceptable()) {//如果是接收事件準備就緒
                        //則獲取客戶端的套接字通道
                        SocketChannel accept = ssc.accept();
                        //並把套接字通道設置爲非阻塞式
                        accept.configureBlocking(false);


                        //得到了客戶端的通道後,也註冊到選擇器上,並監聽他的讀事件是否就緒
                        //同個下面的代碼,就明確了無論是客戶端還是服務端獲取的通道,
                        // 都可以放在選擇器中進行監聽狀態
                        accept.register(selector, SelectionKey.OP_READ);
                    } else if (selectionKey.isReadable()) {
                        //如果當前選擇鍵位讀就緒,就獲取他的套接字通道,
                        SocketChannel sc = (SocketChannel) selectionKey.channel();
                        ByteBuffer buf = ByteBuffer.allocate(1024 * 1024);
                        //開始讀寫數據
                        while (sc.read(buf) > 0) {
                            buf.flip();
                            file.write(buf);
                            buf.clear();
                        }
                    }
                    //如果不是上面if else 語句中的事件,或已經執行完成了的,就可以清空掉。
                    it.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                file.close();
                ssc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

UDP版無阻塞式

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Scanner;

public class UDPNetSocketTest {
    public static void main(String[] args) {
        new UDPNetSocketTest().Cline();
    }
    @Test
    public void Cline() {
        DatagramChannel dc = null;
        Scanner sc = null;
        try {
            dc = DatagramChannel.open();
            dc.configureBlocking(false);
            ByteBuffer buf = ByteBuffer.allocate(1024);
            sc = new Scanner(System.in);
            String str = null;
            while ((str = sc.next()) != null) {
                buf.put(str.getBytes());
                buf.flip();
                dc.send(buf, new InetSocketAddress("127.0.0.1", 18888));
                buf.clear();
            }
        } catch (IOException e) {
        } finally {
            if (sc != null) {
                sc.close();
            }
            try {
                if (dc != null) {
                    dc.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @Test
    public void Server() {
        DatagramChannel dc = null;
        try {
            dc = DatagramChannel.open();

            dc.bind(new InetSocketAddress(18888));

            dc.configureBlocking(false);

            Selector selector = Selector.open();
            dc.register(selector, SelectionKey.OP_READ);

            while (selector.select() > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    if (selectionKey.isReadable()) {
                        ByteBuffer buf = ByteBuffer.allocate(1024);

                        dc.receive(buf);
                        buf.flip();
                        System.out.println(new String(buf.array(),0,buf.limit()));
                        buf.clear();
                    }
                    //一定要注意清空
                    iterator.remove();
                }
            }
        } catch (IOException e) {
        } finally {
            try {
                if (dc != null) {
                    dc.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章