Java IO

Before

IO 分為:
同步、異步
阻塞、非阻塞

同步和異步是目的,阻塞和非阻塞是實現(xiàn)方式。

一個IO操作其實分成了兩個步驟:發(fā)起IO請求和實際的IO操作。
同步IO和異步IO的區(qū)別就在于第二個步驟是否阻塞,如果實際的IO讀寫阻塞請求進程,那么就是同步IO。
阻塞IO和非阻塞IO的區(qū)別在于第一步,發(fā)起IO請求是否會被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO,如果不阻塞,那么就是非阻塞IO。

IO操作可以分為3類:同步阻塞(即早期的IO操作)、同步非阻塞(NIO)、異步(AIO)

BIO、NIO、AIO適用場景分析:

  • BIO方式適用于連接數(shù)目比較小且固定的架構,這種方式對服務器資源要求比較高,并發(fā)局限于應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
  • NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構,比如聊天服務器,并發(fā)局限于應用中,編程比較復雜,JDK1.4開始支持。
  • AIO方式使用于連接數(shù)目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與并發(fā)操作,編程比較復雜,JDK7開始支持。

IO

https://www.cnblogs.com/ylspace/p/8128112.html

Java I/O主要包括如下幾個層次,包含三個部分:
1.流式部分,IO的主體部分;
2.非流式部分,主要包含一些輔助流式部分的類,如:File類、RandomAccessFile類和FileDescriptor等類;
3.其他類,文件讀取部分的與安全相關的類,如:SerializablePermission類,以及與本地操作系統(tǒng)相關的文件系統(tǒng)的類,如:FileSystem類和Win32FileSystem類和WinNTFileSystem類。

流:代表任何有能力產出數(shù)據的數(shù)據源對象或者是有能力接受數(shù)據的接收端對象

IO流的分類

  • 根據處理數(shù)據類型的不同分為:字符流和字節(jié)流
  • 根據數(shù)據流向不同分為:輸入流和輸出流
  • 按數(shù)據來源(去向)分類:
    1、File(文件): FileInputStream, FileOutputStream, FileReader, FileWriter
    2、byte[]:ByteArrayInputStream, ByteArrayOutputStream
    3、Char[]: CharArrayReader,CharArrayWriter
    4、String:StringBufferInputStream, StringReader, StringWriter
    5、網絡數(shù)據流:InputStream,OutputStream, Reader, Writer

流序列中的數(shù)據既可以是未經加工的原始二進制數(shù)據,也可以是經一定編碼處理后符合某種格式規(guī)定的特定數(shù)據。因此Java中的流分為兩種:

  1. 字節(jié)流:數(shù)據流中最小的數(shù)據單元是字節(jié)
  2. 字符流:數(shù)據流中最小的數(shù)據單元是字符, Java中的字符是Unicode編碼,一個字符占用兩個字節(jié)。

字符流的由來: Java中字符是采用Unicode標準,一個字符是16位,即一個字符使用兩個字節(jié)來表示。為此,JAVA中引入了處理字符的流。因為數(shù)據編碼的不同,而有了對字符進行高效操作的流對象。本質其實就是基于字節(jié)流讀取時,去查了指定的碼表。

NIO

http://www.iteye.com/magazines/132-Java-NIO

三個關鍵組成:
Buffer
Channel
Selector

深入淺出NIO之Channel、Buffer
http://www.itdecent.cn/p/052035037297

Buffer

一塊緩存區(qū),內部使用字節(jié)數(shù)組存儲數(shù)據,并維護幾個特殊變量,實現(xiàn)數(shù)據的反復利用。

1、mark:初始值為-1,用于備份當前的position;
2、position:初始值為0,position表示當前可以寫入或讀取數(shù)據的位置,當寫入或讀取一個數(shù)據后,position向前移動到下一個位置;
3、limit:寫模式下,limit表示最多能往Buffer里寫多少數(shù)據,等于capacity值;讀模式下,limit表示最多可以讀取多少數(shù)據。
4、capacity:緩存數(shù)組大小

ByteBuffer的實現(xiàn)類包括"HeapByteBuffer"和"DirectByteBuffer"兩種。

  • HeapByteBuffer通過初始化字節(jié)數(shù)組hd,在虛擬機堆上申請內存空間。

  • DirectByteBuffer通過unsafe.allocateMemory申請堆外內存,并在ByteBuffer的address變量中維護指向該內存的地址。
    unsafe.setMemory(base, size, (byte) 0)方法把新申請的內存數(shù)據清零。

Channel

NIO把它支持的I/O對象抽象為Channel,Channel又稱“通道”,類似于原I/O中的流(Stream),但有所區(qū)別:
1、流是單向的,通道是雙向的,可讀可寫。
2、流讀寫是阻塞的,通道可以異步讀寫。
3、流中的數(shù)據可以選擇性的先讀到緩存中,通道的數(shù)據總是要先讀到一個緩存中,或從緩存中寫入

目前已知Channel的實現(xiàn)類有:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

FileChannel的read、write和map通過其實現(xiàn)類FileChannelImpl實現(xiàn)。
通過上述實現(xiàn)可以看出,基于channel的文件數(shù)據讀取步驟如下:
1、申請一塊和緩存同大小的DirectByteBuffer bb。
2、讀取數(shù)據到緩存bb,底層由NativeDispatcher的read實現(xiàn)。
3、把bb的數(shù)據讀取到dst(用戶定義的緩存,在jvm中分配內存)。
read方法導致數(shù)據復制了兩次。

通過上述實現(xiàn)可以看出,基于channel的文件數(shù)據寫入步驟如下:
1、申請一塊DirectByteBuffer,bb大小為byteBuffer中的limit - position。
2、復制byteBuffer中的數(shù)據到bb中。
3、把數(shù)據從bb中寫入到文件,底層由NativeDispatcher的write實現(xiàn)
write方法也導致了數(shù)據復制了兩次

File file = new RandomAccessFile("data.txt", "rw");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
    System.out.println("Read " + bytesRead);
    buffer.flip();
    while(buffer.hasRemaining()){
        System.out.print((char) buffer.get());
    }
    buffer.clear();
    bytesRead = channel.read(buffer);
}
file.close();

注意buffer.flip() 的調用,首先將數(shù)據寫入到buffer,然后變成讀模式,再從buffer中讀取數(shù)據。

Selector

深入淺出NIO之Selector實現(xiàn)原理
http://www.itdecent.cn/p/0d497fe5484a

為了實現(xiàn) Selector 管理多個 SocketChannel,必須將具體的 SocketChannel 對象注冊到 Selector,并聲明需要監(jiān)聽的事件(這樣 Selector 才知道需要記錄什么數(shù)據),一共有4種事件:

1、connect:客戶端連接服務端事件,對應值為SelectionKey.OP_CONNECT(8)
2、accept:服務端接收客戶端連接事件,對應值為SelectionKey.OP_ACCEPT(16)
3、read:讀事件,對應值為SelectionKey.OP_READ(1)
4、write:寫事件,對應值為SelectionKey.OP_WRITE(4)

這個很好理解,每次請求到達服務器,都是從connect開始,connect成功后,服務端開始準備accept,準備就緒,開始讀數(shù)據,并處理,最后寫回數(shù)據返回。

所以,當SocketChannel有對應的事件發(fā)生時,Selector都可以觀察到,并進行相應的處理。

SocketChannel、ServerSocketChannel 和 Selector 的實例初始化都通過 SelectorProvider 類實現(xiàn),其中 Selector 是整個 NIO Socket 的核心實現(xiàn)。

實例

服務端

public class NIOServer {

    /*接受數(shù)據緩沖區(qū)*/
    private ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
    /*發(fā)送數(shù)據緩沖區(qū)*/
    private  ByteBuffer receivebuffer = ByteBuffer.allocate(1024);

    private Selector selector;

    public NIOServer(int port) throws IOException {
        // 打開服務器套接字通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 服務器配置為非阻塞
        serverSocketChannel.configureBlocking(false);
        // 檢索與此通道關聯(lián)的服務器套接字
        ServerSocket serverSocket = serverSocketChannel.socket();
        // 進行服務的綁定
        serverSocket.bind(new InetSocketAddress(port));
        // 通過open()方法找到Selector
        selector = Selector.open();
        // 注冊到selector,等待連接
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server Start----:");
    }

    //
    private void listen() throws IOException {
        while (true) {
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handleKey(selectionKey);
            }
        }
    }

    private void handleKey(SelectionKey selectionKey) throws IOException {
        // 接受請求
        ServerSocketChannel server = null;
        SocketChannel client = null;
        String receiveText;
        String sendText;
        int count=0;
        // 測試此鍵的通道是否已準備好接受新的套接字連接。
        if (selectionKey.isAcceptable()) {
            // 返回為之創(chuàng)建此鍵的通道。
            server = (ServerSocketChannel) selectionKey.channel();
            // 接受到此通道套接字的連接。
            // 此方法返回的套接字通道(如果有)將處于阻塞模式。
            client = server.accept();
            // 配置為非阻塞
            client.configureBlocking(false);
            // 注冊到selector,等待連接
            client.register(selector, SelectionKey.OP_READ);
        } else if (selectionKey.isReadable()) {
            // 返回為之創(chuàng)建此鍵的通道。
            client = (SocketChannel) selectionKey.channel();
            //將緩沖區(qū)清空以備下次讀取
            receivebuffer.clear();
            //讀取服務器發(fā)送來的數(shù)據到緩沖區(qū)中
            count = client.read(receivebuffer);
            if (count > 0) {
                receiveText = new String( receivebuffer.array(),0,count);
                System.out.println("服務器端接受客戶端數(shù)據--:"+receiveText);
                client.register(selector, SelectionKey.OP_WRITE);
            }
        } else if (selectionKey.isWritable()) {
            //將緩沖區(qū)清空以備下次寫入
            sendbuffer.clear();
            // 返回為之創(chuàng)建此鍵的通道。
            client = (SocketChannel) selectionKey.channel();
            sendText="message from server--";
            //向緩沖區(qū)中輸入數(shù)據
            sendbuffer.put(sendText.getBytes());
            //將緩沖區(qū)各標志復位,因為向里面put了數(shù)據標志被改變要想從中讀取數(shù)據發(fā)向服務器,就要復位
            sendbuffer.flip();
            //輸出到通道
            client.write(sendbuffer);
            System.out.println("服務器端向客戶端發(fā)送數(shù)據--:"+sendText);
            client.register(selector, SelectionKey.OP_READ);
        }
    }

    /**
    * @param args
    * @throws IOException
    */
    public static void main(String[] args) throws IOException {
        int port = 8080;
        NIOServer server = new NIOServer(port);
        server.listen();
    }
}

客戶端

public class NIOClient {
    /*接受數(shù)據緩沖區(qū)*/
    private static ByteBuffer sendbuffer = ByteBuffer.allocate(1024);
    /*發(fā)送數(shù)據緩沖區(qū)*/
    private static ByteBuffer receivebuffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException {
        // 打開socket通道
        SocketChannel socketChannel = SocketChannel.open();
        // 設置為非阻塞方式
        socketChannel.configureBlocking(false);
        // 打開選擇器
        Selector selector = Selector.open();
        // 注冊連接服務端socket動作
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        // 連接
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));

        Set<SelectionKey> selectionKeys;
        Iterator<SelectionKey> iterator;
        SelectionKey selectionKey;
        SocketChannel client;
        String receiveText;
        String sendText;
        int count=0;

        while (true) {
            //選擇一組鍵,其相應的通道已為 I/O 操作準備就緒。
            //此方法執(zhí)行處于阻塞模式的選擇操作。
            selector.select();
            //返回此選擇器的已選擇鍵集。
            selectionKeys = selector.selectedKeys();
            //System.out.println(selectionKeys.size());
            iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                selectionKey = iterator.next();
                if (selectionKey.isConnectable()) {
                    System.out.println("client connect");
                    client = (SocketChannel) selectionKey.channel();
                    // 判斷此通道上是否正在進行連接操作。
                    // 完成套接字通道的連接過程。
                    if (client.isConnectionPending()) {
                        client.finishConnect();
                        System.out.println("完成連接!");
                        sendbuffer.clear();
                        sendbuffer.put("Hello,Server".getBytes());
                        sendbuffer.flip();
                        client.write(sendbuffer);
                    }
                    client.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    client = (SocketChannel) selectionKey.channel();
                    //將緩沖區(qū)清空以備下次讀取
                    receivebuffer.clear();
                    //讀取服務器發(fā)送來的數(shù)據到緩沖區(qū)中
                    count=client.read(receivebuffer);
                    if(count>0){
                        receiveText = new String( receivebuffer.array(),0,count);
                        System.out.println("客戶端接受服務器端數(shù)據--:"+receiveText);
                        client.register(selector, SelectionKey.OP_WRITE);
                    }

                } else if (selectionKey.isWritable()) {
                    sendbuffer.clear();
                    client = (SocketChannel) selectionKey.channel();
                    sendText = "message from client--";
                    sendbuffer.put(sendText.getBytes());
                    //將緩沖區(qū)各標志復位,因為向里面put了數(shù)據標志被改變要想從中讀取數(shù)據發(fā)向服務器,就要復位
                    sendbuffer.flip();
                    client.write(sendbuffer);
                    System.out.println("客戶端向服務器端發(fā)送數(shù)據--:"+sendText);
                    client.register(selector, SelectionKey.OP_READ);
                }
            }
            selectionKeys.clear();
        }
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容