首頁技術(shù)文章正文

Java培訓(xùn):NIO的基本用法

更新時(shí)間:2022-10-14 來源:黑馬程序員 瀏覽量:

IT培訓(xùn)班

  NIO是New I/O的簡稱,與舊式基于流的I/O相對,從名字上來看,它表示新的一套I/O標(biāo)準(zhǔn)。它是從JDK1.4中被納入到JDK中的。

  與舊式的IO流相比,NIO是基于Block的,它以塊為單位來處理數(shù)據(jù),最為重要的兩個(gè)組件是緩沖區(qū)Buffer和通道Channel。緩沖區(qū)是一塊連續(xù)的內(nèi)存塊,是NIO讀寫數(shù)據(jù)的載體;通道表示緩沖數(shù)據(jù)的源頭和目的地,它用于向緩沖區(qū)讀取或者寫入數(shù)據(jù),是訪問緩沖區(qū)的接口。

  

1665727774552_1.jpg

  Buffer的基本原理

  Buffer中最重要的3個(gè)參數(shù):位置(position)、容量(capacity)、上限(limit)。他們3者的含義如下

位置(position): 表示當(dāng)前緩沖區(qū)的位置,從position位置之后開始讀寫數(shù)據(jù)。
容量(capacity): 表示緩沖區(qū)的最大容量
上限(limit):      表示緩沖區(qū)的實(shí)際上限,它總是小于或等于容量

  

1665727850048_2.jpg

  緩沖區(qū)的容量(capacity)是不變的,而位置(position)和上限(limit)和以根據(jù)實(shí)際需要而變化。也就是說,可以通過改變當(dāng)前位置和上限來操作緩沖區(qū)內(nèi)任意位置的數(shù)據(jù)。

  Buffer的常用方法

  NIO提供一系列方法來操作Buffer的位置(position)和上限(limit),以及向緩沖區(qū)讀寫數(shù)據(jù)。

put() //向緩沖區(qū)position位置添加數(shù)據(jù)。并且position往后移動,不能超過limit上限。
get() //讀取當(dāng)前position位置的數(shù)據(jù)。并且position往后移動,不能超過limit上限。
flip() //將limit置位為當(dāng)前position位置,再講position設(shè)置為0
rewind() //僅將當(dāng)前position位置設(shè)置為0
remaining //獲取緩沖區(qū)中當(dāng)前position位置和limit上限之間的元素?cái)?shù)(有效的元素?cái)?shù))
hasRemaining() //判斷當(dāng)前緩沖區(qū)是否存在有效的元素?cái)?shù)
mark() //在當(dāng)前position位置打一個(gè)標(biāo)記
reset() //將當(dāng)前position位置恢復(fù)到mark標(biāo)記的位置。
duplicate() //復(fù)制緩沖區(qū)

  創(chuàng)建緩沖區(qū)

//創(chuàng)建一個(gè)容量為10的緩沖區(qū)
ByteBuffer byteBuffer1=ByteBuffer.allocate(10);

//創(chuàng)建一個(gè)包裹數(shù)據(jù)的緩沖區(qū)
ByteBuffer byteBuffer2 = ByteBuffer.wrap("abcde".getBytes());

  獲取/設(shè)置緩沖區(qū)參數(shù)

//創(chuàng)建一個(gè)容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
System.out.println("位置:"+byteBuffer.position());  //0
System.out.println("上限:"+byteBuffer.limit()); //10
System.out.println("容量:"+byteBuffer.capacity());//10

  

1665728068930_3.jpg

  添加數(shù)據(jù)到緩沖區(qū)

//創(chuàng)建一個(gè)容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數(shù)據(jù)到緩沖區(qū)
byteBuffer.put("abcde".getBytes());
System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10

  

1665728114407_4.jpg

  rewind重置緩沖區(qū)

  rewind函數(shù)將position置為0位置,并清除標(biāo)記。

//創(chuàng)建一個(gè)容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數(shù)據(jù)到緩沖區(qū)
byteBuffer.put("abcde".getBytes());

System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10

System.out.println("------------");

//重置緩沖區(qū)
byteBuffer.rewind();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity());//10

  

1665728220903_5.jpg

  flip重置緩沖區(qū)

  flip函數(shù)現(xiàn)將limit設(shè)置為position位置,再將position置為0位置,并清除mar標(biāo)記。

//創(chuàng)建一個(gè)容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//添加數(shù)據(jù)到緩沖區(qū)
byteBuffer.put("abcde".getBytes());

System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity()); //10

System.out.println("------------");

//重置緩沖區(qū)
byteBuffer.flip();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //5
System.out.println("capacity容量:"+byteBuffer.capacity());//10

  

1665728287506_8.jpg

  clear清空緩沖區(qū)

  clear方法也將position置為0,同時(shí)將limit置為capacity的大小,并清除mark標(biāo)記。

//創(chuàng)建一個(gè)容量為10的緩沖區(qū)
ByteBuffer byteBuffer=ByteBuffer.allocate(10);
//設(shè)置上限為5
byteBuffer.limit(5);
//添加數(shù)據(jù)到緩沖區(qū)
byteBuffer.put("abcde".getBytes());

System.out.println("position位置:"+byteBuffer.position()); //5
System.out.println("limit上限:"+byteBuffer.limit()); //5
System.out.println("capacity容量:"+byteBuffer.capacity()); //10

System.out.println("------------");

//重置緩沖區(qū)
byteBuffer.clear();
System.out.println("position位置:"+byteBuffer.position()); //0
System.out.println("limit上限:"+byteBuffer.limit()); //10
System.out.println("capacity容量:"+byteBuffer.capacity());//10

  

1665728359721_9.jpg

  標(biāo)記和恢復(fù)

ByteBuffer buffer = ByteBuffer.allocate(10);
//添加數(shù)據(jù)到緩沖區(qū)
buffer.put("abcde".getBytes());
//打一個(gè)標(biāo)記
buffer.mark();
System.out.println("標(biāo)記位置:"+buffer.position()); //5
//再添加5個(gè)字節(jié)數(shù)據(jù)到緩沖區(qū)
buffer.put("fijkl".getBytes());
System.out.println("當(dāng)前位置:"+buffer.position()); //10
//將position恢復(fù)到mark標(biāo)記位置
buffer.reset();
System.out.println("恢復(fù)標(biāo)記位置:"+buffer.position());//5

  FileChannel通道

  FileChannel是用于操作文件的通道,可以用于讀取文件、也可以寫入文件

//創(chuàng)建讀取文件通道
FileChannel fisChannel = new FileInputStream("day05/src/a.txt").getChannel();
//創(chuàng)建寫入文件的通道
FileChannel fosChannel = new FileOutputStream("day05/src/b.txt").getChannel();
//創(chuàng)建緩沖區(qū)
ByteBuffer buffer = ByteBuffer.allocate(2);
while (fisChannel.read(buffer)!=-1){
  System.out.println("position:"+buffer.position()); //0
  System.out.println("limit:"+buffer.limit());//2
  //重置緩沖區(qū)(為輸出buffer數(shù)據(jù)做準(zhǔn)備)
  buffer.flip();
  fosChannel.write(buffer);
  //重置緩沖區(qū)(為輸入buffer數(shù)據(jù)做準(zhǔn)備)
  buffer.clear();
}
//關(guān)閉通道
fisChannel.close();
fosChannel.close();

1665728468541_10.jpg

  SocketChannel通道

  下面代碼使用SocketChannel通道上傳文件到服務(wù)器

public class Client {
    public static void main(String[] args) throws IOException {
        //創(chuàng)建通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 10000));
        //創(chuàng)建緩沖區(qū)
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //讀取本地文件數(shù)據(jù)到緩沖區(qū)
        FileChannel fisChannel = new FileInputStream("day05/src/a.txt").getChannel();
        while (fisChannel.read(buffer)!=-1){
            buffer.flip(); //為寫入數(shù)據(jù)做準(zhǔn)備
            socketChannel.write(buffer);
            buffer.clear(); //為讀取數(shù)據(jù)做準(zhǔn)備
        }
        //關(guān)閉本地通道
        fisChannel.close();
        //socketChannel.shutdownOutput();

        //讀取服務(wù)端回寫的數(shù)據(jù)
        buffer.clear();
        int len = socketChannel.read(buffer);
        System.out.println(new String(buffer.array(), 0, len));

        //關(guān)閉socket通道
        socketChannel.close();
    }
}

  ServerSocketChannel通道

  下面代碼使用ServerSocketChannel通道接收文件并保存到服務(wù)器

public class Server {
    public static void main(String[] args) throws IOException {
        //1.創(chuàng)建ServerSocketChannel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2.綁定端口號
        serverSocketChannel.bind(new InetSocketAddress(10000));
        //3.設(shè)置非阻塞
        serverSocketChannel.configureBlocking(false);
        System.out.println("服務(wù)器已開啟");
        while (true) {
            //4.獲取客戶端通道,如果有客戶端連接返回客戶端通道,否則返回null
            SocketChannel socketChannel = serverSocketChannel.accept();
            if(socketChannel!=null){
                socketChannel.configureBlocking(false);
                //創(chuàng)建本地通道,用于往文件中寫數(shù)據(jù)
                UUID uuid = UUID.randomUUID();
                FileChannel fosChannel=new FileOutputStream("day05/src/"+uuid+".txt").getChannel();
                ByteBuffer buffer=ByteBuffer.allocate(1024);
                while (socketChannel.read(buffer)>0){
                    buffer.flip(); //準(zhǔn)備把緩沖區(qū)數(shù)據(jù)輸出
                    fosChannel.write(buffer);
                    buffer.clear();//準(zhǔn)備讀取數(shù)據(jù)到緩沖區(qū)
                }
                fosChannel.close();

                //回寫數(shù)據(jù)到客戶端
                ByteBuffer resultBuffer=ByteBuffer.wrap("上傳文件成功".getBytes());
                socketChannel.write(resultBuffer);

                //關(guān)閉客戶端通道
                socketChannel.close();
            }
        }
    }
}

  NIO Selector選擇器

  Selector一般稱為選擇器,當(dāng)然你也可以翻譯為 **多路復(fù)用器** 。它是Java NIO核心組件中的一個(gè),用于檢查一個(gè)或多個(gè)NIO Channel(通道)的狀態(tài)是否處于可讀、可寫。如此可以實(shí)現(xiàn)單線程管理多個(gè)channels,也就是可以管理多個(gè)網(wǎng)絡(luò)鏈接。

1665728556457_10.jpg

  使用Selector的服務(wù)器模板代碼

  有了模板代碼我們在編寫程序時(shí),大多數(shù)時(shí)間都是在模板代碼中添加相應(yīng)的業(yè)務(wù)代碼

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("localhost", 8080));
ssc.configureBlocking(false);

Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);

while(true) {
    int readyNum = selector.select();
    if (readyNum == 0) {
        continue;
    }
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> it = selectedKeys.iterator();
    while(it.hasNext()) {
        SelectionKey key = it.next();
        if(key.isAcceptable()) {
            // 接受連接
        } else if (key.isReadable()) {
            // 通道可讀
        } else if (key.isWritable()) {
            // 通道可寫
        }
        it.remove();
    }
}

  NIO Selector服務(wù)端

  按照上面的模板代碼,改寫接收文件的服務(wù)端。

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress("localhost", 10000));
            ssc.configureBlocking(false);

            Selector selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);

            FileChannel fosChannel=null;
            while (true) {
                int readyNum = selector.select();
                System.out.println(readyNum);
                if (readyNum == 0) {
                    continue;
                }
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    SocketChannel socketChannel1=null;
                    SocketChannel socketChannel2=null;
                    if (key.isAcceptable()) {
                        System.out.println("isAcceptable");
                        // 創(chuàng)建新的連接,并且把連接注冊到selector上,而且,
                        // 聲明這個(gè)channel只對讀操作感興趣。
                        socketChannel1 = ssc.accept();
                        socketChannel1.configureBlocking(false);
                        socketChannel1.register(selector, SelectionKey.OP_READ);
                        UUID uuid = UUID.randomUUID();
                        fosChannel=new FileOutputStream("day05/src/"+uuid+".txt").getChannel();
                    } else if (key.isReadable()) {
                        System.out.println("isReadable");
                        // 通道可讀
                        socketChannel2 = (SocketChannel) key.channel();
                        //創(chuàng)建本地通道,用于往文件中寫數(shù)據(jù)
                        ByteBuffer readBuff=ByteBuffer.allocate(1024);
                        while (socketChannel2.read(readBuff)>0){
                            readBuff.flip(); //準(zhǔn)備把緩沖區(qū)數(shù)據(jù)輸出
                            fosChannel.write(readBuff);
                            readBuff.clear();//準(zhǔn)備讀取數(shù)據(jù)到緩沖區(qū)
                        }
                        fosChannel.close();
                        key.interestOps(SelectionKey.OP_WRITE);
                    } else if (key.isWritable()) {
                        System.out.println("isWritable");
                        // 通道可寫
                        ByteBuffer writeBuff=ByteBuffer.allocate(1024);
                        writeBuff.put("上傳成功".getBytes());
                        writeBuff.flip();
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        socketChannel.write(writeBuff);
                        key.interestOps(SelectionKey.OP_READ);
                    }
                    it.remove();
                }
            }
        } catch (Exception e) {
            //e.printStackTrace();
        } finally {

        }
    }
}


分享到:
在線咨詢 我要報(bào)名
和我們在線交談!