Java粗淺認識-I/O(二)-NIO

NIO簡介

什麼是Java NIO,nio在java1.4時新增,叫做new I/O,就是新的I/O,既是在基於1.0出現的I/O Stream操作之上的新改變,
包括,新的 I/O通信模型,如Buffer,Channels,多路複用(Selector);基於Perl樣式正則表達式的模式匹配工具。

java.nio.Buffer

一個特點原始數據類型(並不包括如String等類)的集合,提供了方便的put,get,數據批量移動等操作,就像我們用數組來緩存每次讀取的數據一樣,但提供了更加方便的操作,還有一個特殊的基於直接內存來分配的ByteBuffer.allocateDirect()的操作,基於直接內存來操作,雖然在gc中並不回收這部分內存,但大數據的操作效率更高。

Buffer內部維護0<=mark<=position<=limit<=capacity的指針,來操作緩存數據,對外提供各種類型的put,get,批量移動;對緩存容器進行創建,翻轉,釋放,壓縮,標記
緩衝區的使用實例

public static void main(String[] args) {
        //1.基於allocate方法調用得到的heap(JVM管理的堆)上的緩存
        ByteBuffer byteBuffer = ByteBuffer.allocate(8);
        byteBuffer.put((byte) 'a');
        byteBuffer.put((byte) 'b');
        byteBuffer.put((byte) 'c');
        //翻轉函數,設置limit = position,position = 0,
        byteBuffer.flip();
        //hasRemaining = limit - position>0
        while (byteBuffer.hasRemaining()) {
            //position = position+1*(1byte)
            System.out.println((char) byteBuffer.get());
        }
        //直接返回當前這個Buffer底層的數組,和position,limit等下標操縱無關
        byte[] bytes = byteBuffer.array();
        for (byte aByte : bytes) {
            System.out.println(aByte);
        }

        //int類型的Buffer,同理其他的Buffer也是如此
        IntBuffer intBuffer = IntBuffer.allocate(8);
        intBuffer.put(1);
        intBuffer.put(2);
        intBuffer.put(3);

        intBuffer.flip();
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }

        //2.基於Wrap()方法得到heap(JVM管理的堆)上的緩存
        //基礎類型的Wrap(),直接創建一個數組長度的緩衝區,position=0,limit =capacity =  length;
        LongBuffer longBuffer = LongBuffer.wrap(new long[]{1L, 2L, 3L});
        //因爲offset=position =0,所以不用再次flip
        while (longBuffer.hasRemaining()) {
            System.out.println("long:" + longBuffer.get());
        }
        //已經到結尾了,所以要重置一下指針,內部的數據並未發生改變
        //position = 0;
        //limit = capacity;
        //mark = -1;
        longBuffer.clear();
        longBuffer.put(4L);
        longBuffer.flip();
        while (longBuffer.hasRemaining()) {
            System.out.println("long:" + longBuffer.get());
        }

        // 這是爲了方便處理字符串處理而實現的特例,包裝CharSequence,如String,StringBuffer,StringBuilder
        CharSequence charBuffer = CharBuffer.wrap("hello world");
        while (((CharBuffer) charBuffer).hasRemaining()) {
            System.out.println("char:" + ((CharBuffer) charBuffer).get());
        }

        //批量移動,把float[]的數批量put到floatBuffer中,然後再get出floatBuffer的數據,放在tmpBuffer中
        FloatBuffer floatBuffer = FloatBuffer.allocate(8);
        floatBuffer.put(new float[]{0.4f, 0.2f, 0.3f});
        float[] tmpBuffer = new float[4];
        floatBuffer.flip();
        floatBuffer.get(tmpBuffer, 0, floatBuffer.remaining());
        for (float v : tmpBuffer) {
            System.out.println("float:" + v);
        }

        DoubleBuffer doubleBuffer = DoubleBuffer.allocate(8);
        doubleBuffer.put(new double[]{0.5d, 0.6d, 0.7d});

        //mark一下position,mark = position
        doubleBuffer.mark();
        doubleBuffer.put(new double[]{0.8d, 0.9d, 0.11d});
        //position =  mark;
        doubleBuffer.reset();
        //position = mark,limit = capacity,所以打印出來了最後兩個沒有實際賦值的0.0
        while (doubleBuffer.hasRemaining()) {
            System.out.println("double:" + doubleBuffer.get());
        }

        ShortBuffer shortBuffer = ShortBuffer.allocate(8);
        shortBuffer.put(new short[]{1, 2, 3, 4, 5, 6});
        System.out.println("shortBuffer.position:" + shortBuffer.position());
        //手動設置position
        shortBuffer.position(3);
        //剩餘position個數據,limit=capacity
        shortBuffer.compact();
        while (shortBuffer.hasRemaining()) {
            System.out.println("short:" + shortBuffer.get());
        }

        //直接內存緩衝區,1char = 2byte,1short=2byte,1int = 4byte,1long = 8byte,1float = 4byte,1double = 8byte
        //不會被gc的區域
        ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(8);
        directByteBuffer.putInt(10).putInt(11);
        directByteBuffer.flip();
        //1.直接類型映射
        /*while (directByteBuffer.hasRemaining()) {
            System.out.println("directByteBuffer:" + directByteBuffer.getInt());
        }*/
        //2.buffer類型轉換
        IntBuffer intBuffer1 = directByteBuffer.asIntBuffer();
        while(intBuffer1.hasRemaining()){
            System.out.println("directByteBuffer:" + intBuffer1.get());
        }
    }

java.nio.channels.Channel

什麼是channel,channel是(A nexus for I/O operations.),一個i/o操作的連接。

FileChannel文件拷貝實例

/**
     * 利用transfer直接拷貝
     */
    private static void copyFile2(String source, String target) throws IOException {
        File fileRead = new File(source);
        File fileWrite = new File(target);

        RandomAccessFile randomAccessFileRead = new RandomAccessFile(fileRead, "r");
        RandomAccessFile randomAccessFileWrite = new RandomAccessFile(fileWrite, "rw");
        FileChannel fileChannelRead = randomAccessFileRead.getChannel();
        FileChannel fileChannelWrite = randomAccessFileWrite.getChannel();
        fileChannelRead.transferTo(0, fileChannelRead.size(), fileChannelWrite);
        fileChannelRead.close();
        fileChannelWrite.close();
    }

    /**
     * 手工拷貝
     * @param source
     * @param target
     * @throws IOException
     */
    private static void copyFile1(String source, String target) throws IOException {

        File fileRead = new File(source);
        File fileWrite = new File(target);

        RandomAccessFile randomAccessFileRead = new RandomAccessFile(fileRead, "r");
        RandomAccessFile randomAccessFileWrite = new RandomAccessFile(fileWrite, "rw");
        //16k
        ByteBuffer buffer = ByteBuffer.allocateDirect(1 << 14);
        FileChannel fileChannelRead = randomAccessFileRead.getChannel();
        FileChannel fileChannelWrite = randomAccessFileWrite.getChannel();
        while (fileChannelRead.read(buffer) != 0) {
            buffer.flip();
            fileChannelWrite.write(buffer);
        }
        fileChannelRead.close();
        fileChannelWrite.close();
    }

不帶Selector的NIO網絡通信實例

server,服務端監聽端口8888,等待服務端連接,連接完成後,直接在當前線程(main線程)中,接受來自客戶端的文件,並寫出到文件中。

private static void server(String filename) throws IOException, InterruptedException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(8888));
        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel == null) {
                System.out.println("等待客戶端連接.");
                TimeUnit.SECONDS.sleep(2);
                continue;
            }
            //16k
            ByteBuffer buffer = ByteBuffer.allocate(1 << 14);
            RandomAccessFile randomAccessFile = new RandomAccessFile(filename, "rw");
            while (socketChannel.read(buffer) != 0) {
                buffer.flip();
                randomAccessFile.getChannel().write(buffer);
            }
            if (socketChannel.isOpen()) {
                socketChannel.close();
            }
            break;
        }
        serverSocketChannel.close();
    }

client

客戶端連接127.0.0.1端口爲8888的服務,然後發送數據,等待服務端接受。

    private static void client(String filename) throws IOException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));
        RandomAccessFile randomAccessFile = new RandomAccessFile(filename, "r");
        //16k
        ByteBuffer buffer = ByteBuffer.allocateDirect(1 << 14);
        while (randomAccessFile.getChannel().read(buffer) != 0) {
            buffer.flip();
            socketChannel.write(buffer);
        }
        randomAccessFile.close();
        socketChannel.close();
    }

java.nio.channels.Selector(對象的多路複用器)

多個channel可以同時註冊到同一個Selector中,Selector通過唯一的一個SelectionKey與一個通道建立聯繫,而每個SelectionKey都設置了一個關注事件類型,包括, OP_ACCEPTOP_READOP_WRITEOP_CONNECT四種事件類型。

使用Selector選擇器的服務端實例(客戶端可以使用上面的客戶端)

 private static void server(String filename) throws IOException, InterruptedException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(8888));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            //阻塞等待連接
            int complement = selector.select();
            if (complement == 0) {
                continue;
            }
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
                    System.out.println("接受連接。");
                    SocketChannel socketChannel = serverSocketChannel1.accept();
                    if(socketChannel==null){
                        continue;
                    }
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    //16k
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1 << 14);
                    FileChannel fileChannel = new RandomAccessFile(filename, "rw").getChannel();
                    while (socketChannel.read(byteBuffer) > 0) {
                        byteBuffer.flip();
                        fileChannel.write(byteBuffer);
                        byteBuffer.clear();
                    }
                    fileChannel.close();
                    socketChannel.close();
                }
                iterator.remove();
            }
        }
    }

這一篇寫了對Nio的理解,主要是Buffer、Channel、Selector的類,下一篇講解在Java7中Nio2。

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