java之NIO入門學習介紹及NIO網絡編程Demo

        一句話簡介:相對於傳統IO,NIO的讀寫是基於buffer的。對於網絡編程來說,NIO通過了選擇器實現了非阻塞網絡編程。

從Java1.4開始就有了NIO,隨着程序對性能的要求越來越高,NIO得到了廣泛使用。IO與NIO的區別如下:

IO NIO
面向流 面向緩衝區
阻塞 非阻塞
/ 有選擇器

目錄

 

Buffer常用API:

Channel常用API

NIO 的Client-Server小Demo

 

      以下代碼分爲三個部分,第一部分爲Buffer的調用,第二部分爲使用Buffer進行IO操作,第三部分爲NIO網絡編程。

Buffer常用API:

package com.example.nio;

import org.junit.Test;

import java.nio.Buffer;
import java.nio.ByteBuffer;

/**
 * 一、緩衝區(buffer):在Java NIO中負責數據的存取,緩衝區就是數組,用於存儲不同數據類型的數據
 * <p>
 * 根據數據類型的不同(boolean 除外),提供了相應類型的緩衝區:
 * ByteBuffer
 * IntBuffer
 * LongBuffer
 * ShortBuffer
 * FloatBuffer
 * DoiubleBuffer
 * CharBuffer
 * <p>
 * 上述緩衝區的管理方式幾乎一致,通過allocate*()獲取緩衝區
 * <p>
 * 二、緩衝區存取數據的兩個核心方法
 * put():存入數據到緩衝區
 * get():獲取緩衝區數據。
 * <p>
 * 三、緩衝區中的四個核心屬性、
 * capacity:容量,
 * limit:界限,表示緩衝區中可以操作數據的大小,(limit後的數據不能進行讀寫)
 * position:位置,表示緩衝區中正在操作數據的位置
 * mark:標記,表示記錄當前position的位置,可以通過reset()使position恢復到mark位置。
 * 0 <= mark <= position <= limit <=capacity
 */
public class TestBuffer {
    @Test
    public void test2() {
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put("abcde".getBytes());
        System.out.println(buffer.position());

        buffer.flip();
        byte[] dts = new byte[buffer.limit()];
        buffer.get(dts, 0, 2);
        System.out.println(new String(dts,0,2));
        System.out.println(buffer.position());
        //mark標記一下
        buffer.mark();

        buffer.get(dts, 2, 2);
        System.out.println(new String(dts,2,2));
        System.out.println(buffer.position());

        //恢復position到mark標記處
        buffer.reset();
        buffer.get(dts, 4, 1);
        System.out.println(new String(dts,4,1));
        System.out.println(buffer.position());
        System.out.println(new String(dts));

        //判斷緩衝區是否還有剩餘數據
        if (buffer.hasRemaining()){
            //獲取緩衝區中剩餘數據數量
            System.out.println(buffer.remaining());
        }
    }

    @Test
    public void test() {
        //1、分配一個制定大小的緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);

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

        //2、使用put()存入數據到緩衝區中
        String string = "abcde";
        buffer.put(string.getBytes());
        System.out.println("---------------------put()------------------------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //3、切換讀取數據模式
        buffer.flip();
        System.out.println("---------------------flip()------------------------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //4、讀取數據
        byte[] dst = new byte[buffer.limit()];
        buffer.get(dst);
        System.out.println(new String(dst, 0, dst.length));

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

        //5、rewind():可重複度
        buffer.rewind();
        System.out.println("---------------------rewid()------------------------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());

        //6、clear(),清空緩衝區,緩衝區中的數據還存在, 但是出於“被遺忘”狀態。
        buffer.clear();
        buffer.rewind();
        System.out.println("---------------------clear()------------------------------");
        System.out.println(buffer.position());
        System.out.println(buffer.limit());
        System.out.println(buffer.capacity());
        System.out.println((char) buffer.get());
    }
}

Channel常用API

package com.example.nio;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
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.Map;
import java.util.Set;

/**
 * 一、通道(Channel):用於原節點與目標節點的連接。在Java NIO中負責緩衝區中數據的傳輸。
 * Channel本身不存儲數據,需要配合緩衝區buffer進行傳輸。
 * <p>
 * 二、通道的主要實現類
 * Java.nio.channels.Channel 接口
 * |--FileChannel
 * |--SocketChannel
 * |--ServerSocketChannel
 * |--DatagramChannel
 * <p>
 * 三、獲取通道
 * 1.Java 針對支持通道的類提供了getChannel()方法
 * 本地IO:
 * FileInputStream/FileOutputStream
 * RandomAccessFile
 * <p>
 * 網絡IO:
 * Socket
 * ServerSocket
 * DatagramSocket
 * <p>
 * 2.在JDK 1.7中NIO.2 針對各個通道提供了靜態方法 open()
 * <p>
 * 3.在JDK1.7中NIO.2的Files 工具類的 newByteChannel()
 * <p>
 * 四、.通道之間的數據傳輸
 * transferFrom()
 * transferTo()
 * <p>
 * 五、分散(Scatter)與聚集(Gather)
 * 分散讀取(Scattering Reads):將通道中的數據分散到多個緩衝區中
 * 聚集寫入(Gather Writers):將多個緩衝區中的數據聚集到一個通道中
 *
 * 六、字符集:Charsert
 * 編碼:字符串-> 字節數組
 * 解碼:字節數組-> 字符串
 *
 */
public class TestChannel {

    @Test
    //字節編碼、解碼
    public void test6() throws CharacterCodingException {
        Charset cs1 = Charset.forName("GBK");

        //獲取編碼器
        CharsetEncoder ce = cs1.newEncoder();
        //獲取解碼器
        CharsetDecoder cd = cs1.newDecoder();

        CharBuffer buf = CharBuffer.allocate(1024);

        buf.put("架構師之路");
        buf.flip();

        //編碼
        ByteBuffer bBuf = ce.encode(buf);
        for (int i = 0; i < bBuf.limit(); i++) {
            System.out.println(bBuf.get());
        }
        bBuf.flip();
        //解碼
        CharBuffer cbuf = cd.decode(bBuf);
        System.out.println(cbuf.toString());


    }

    @Test
    //看下提供哪些中編碼解碼類型。
    public void test5(){
        Map<String, Charset> map = Charset.availableCharsets();
        Set<Map.Entry<String, Charset>> entries = map.entrySet();
        for (Map.Entry<String,Charset> entry: entries) {
            System.out.println(entry.getValue() + "==" + entry.getValue());
        }
    }

    @Test
    //分散讀取與聚集寫入
    public void test4() throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");

        //1.獲取通道
        FileChannel channel1 = raf1.getChannel();

        //2.分配製定大小的緩衝區
        ByteBuffer buf1 = ByteBuffer.allocate(100);
        ByteBuffer buf2 = ByteBuffer.allocate(1024);

        //3.分散讀取
        ByteBuffer[] bufs = {buf1, buf2};
        channel1.read(bufs);
        for (ByteBuffer byteBuffer : bufs) {
            byteBuffer.flip();
        }

        System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
        System.out.println("-----------------------------------------------------");
        System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));

        //聚集寫入
        RandomAccessFile raf2 = new RandomAccessFile("2.txt","rw");
        FileChannel channel2 = raf2.getChannel();
        channel2.write(bufs);

        channel1.close();
        channel2.close();

    }

    //通道之間的數據傳輸(直接緩衝區)
    @Test
    public void test3() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("2.txt"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

//        inChannel.transferTo(0,inChannel.size(),outChannel);
        outChannel.transferFrom(inChannel, 0, inChannel.size());
        inChannel.close();
        outChannel.close();
    }

    //快速直接緩衝區完成文件的複製(內存映射文件)
    @Test
    public void test2() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

        //內存映射文件
        MappedByteBuffer inMapperBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMapperBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

        //直接對緩衝區進行數據的讀寫操作
        byte[] dts = new byte[inMapperBuf.limit()];
        inMapperBuf.get(dts);
        outMapperBuf.put(dts);

        inChannel.close();
        outChannel.close();
    }

    @Test
    //利用通道完成文件複製(非直接緩衝區)
    public void test() {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            fis = new FileInputStream("1.jpg");
            fos = new FileOutputStream("2.jpg");

            //獲取通道
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();

            //分配指定大小的緩衝區
            ByteBuffer buf = ByteBuffer.allocate(1024);
            //將input通道的數據放入緩衝區中
            while (inChannel.read(buf) != -1) {
                buf.flip();//切換讀取數據的模式
                //將緩衝區的數據寫入output通道
                outChannel.write(buf);
                buf.clear();//清空緩衝區
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

NIO 的Client-Server小Demo

package com.example.nio;


import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 一、使用NIO 完成網絡通信的三個核心;
 * <p>
 * 1、通道(Channel):負責連接
 * <p>
 * Java.nio.channels.Channel接口:
 *      |--SelectableChannel
 *          |--ServerSocketChannel
 *          |--SocketChannel
 *          |-DatagramChannel
 *
 *          |--Pipe.SinkChannel
 *          |--Pipe.SourceChannel
 * <p>
 * <p>
 * 2、緩衝區(Buffer):負責數據的存儲
 * <p>
 * 3、選擇器(Selector):就是SelectableChannel的多路複用器。用於監視SelectableChannel的IO狀態
 */

public class TestNonBlockingNIO {
    @Test
    //客戶端
    public void client() throws IOException, InterruptedException {
        //1、獲取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9527));

        //2、切換成非阻塞模式
        sChannel.configureBlocking(false);

        //3、分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);

        //4、發送數據到服務端
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        for (String s : list) {
            Thread.sleep(5000);
            buf.put((new Date().toString() + "\n" + s).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        //5.關閉通道
        sChannel.close();
    }
    @Test
    //服務端
    public void server() throws IOException {

        //1、獲取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();

        //2、切換成非阻塞模式
        ssChannel.configureBlocking(false);

        //3、綁定端口
        ssChannel.bind(new InetSocketAddress(9527));

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

        //5、把通道註冊在選擇器選,第二個參數含義:表示選擇器監聽的類型,可用“\”包含多個。
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        //6、輪訓獲取選擇器上已經“準備就係的實踐”
        while (selector.select() > 0){
            //7.獲取當前選擇器中所有註冊的選擇鍵
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            System.out.println("0000000");
            while (iterator.hasNext()){
                //8、獲取準備“就緒”的事件
                SelectionKey sk = iterator.next();
                //9、判斷具體是什麼事件準備就緒
                if(sk.isAcceptable()){
                    System.out.println("------------");
                    //10、若 “接收就緒”,獲取客戶端連接
                    SocketChannel sChannel = ssChannel.accept();
                    //11、切換到非阻塞模式
                    sChannel.configureBlocking(false);
                    //12、將該通道註冊到選擇器上
                    sChannel.register(selector,SelectionKey.OP_READ);
                }else if (sk.isReadable()){
                    System.out.println("+++++++++++");
                    //13、獲取當前選擇器上“讀就緒”狀態的通道
                    SocketChannel sChannel = (SocketChannel)sk.channel();
                    //14、讀取數據
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len =  sChannel.read(buf)) > 0){
                        buf.flip();
                        System.out.println(new String(buf.array(),0,len));
                        buf.clear();
                    }
                }
                //15、取消選擇鍵
                iterator.remove();
            }
        }
    }
}

 

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