java----NIO

IO 面向流,堵塞

管道能夠理解爲水管,能夠直接運輸水流(字節數據)java

 

 

 

NIO  面向緩衝區,非堵塞

管道能夠理解鐵路,須要依賴火車(緩衝區)才能運輸數據。linux

 

 

 

Java NIO系統的核心在於:通道/管道(Channel)和緩衝區(Buffer)。通道表示打開到I0設備(例如:文件、套接字)的鏈接。若須要使用NIO系統,須要獲取用於鏈接I0設備的通道以及用於容納數據的緩衝區。而後操做緩衝區,對數據進行處理。簡而言之,Channel負責傳輸,Buffer負責存儲編程

 

緩衝區

概念

緩衝區就是數組,用戶存儲不一樣數據類型的數據,根據數據類型不一樣(boolean除外),提供了相應類型的緩衝區
  ByteBuffer
  CharBuffer
  ShortBuffer
  IntBuffer
  LongBuffer
  FloatBuffer
  DoubleBuffer
上述緩衝區的管理方式幾乎一致,經過allocate()獲取緩衝區,緩衝區存儲數據的連個核心方法 put()、get()windows

緩衝區中的四個核心屬性:

capacity:容量,表示緩衝區中最大存儲數據的容量。一旦聲明不能改變。
limit:   界限,表示緩衝區中能夠操做數據的大小。(limit後數據不能進行讀寫)
position:位置,表示緩衝區中正在操做數據的位置。
mark:標記,表示記錄當前position的位置。能夠經過reset()恢復到mark的位置
0<=mark<=position<=limit<=capacity

 

基本操做

public class Main {

    public static void main(String[] args) {
        //test1();
        test2();
    }
    public static void test1(){
        ByteBuffer byteBuffer= ByteBuffer.allocate(1024);

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

        byteBuffer.put("abcde".getBytes());
        System.out.println("-----put()-----");
        System.out.println(byteBuffer.position());
        System.out.println(byteBuffer.limit());
        System.out.println(byteBuffer.capacity());

        //切換讀取模式
        byteBuffer.flip();

        System.out.println("-----flip()-----");
        System.out.println(byteBuffer.position());
        System.out.println(byteBuffer.limit());
        System.out.println(byteBuffer.capacity());

        System.out.println("-----get()-----");
        byte[] bytes = new byte[byteBuffer.limit()];
        byteBuffer.get(bytes);//若是bytes的空間大於byteBuffer.limit(),會報錯
        System.out.println(new String(bytes,0,byteBuffer.limit()));

        System.out.println(byteBuffer.position());
        System.out.println(byteBuffer.limit());
        System.out.println(byteBuffer.capacity());

        //可重複讀取數據
        byteBuffer.rewind();
        System.out.println("-----rewind()-----");
        System.out.println(byteBuffer.position());
        System.out.println(byteBuffer.limit());
        System.out.println(byteBuffer.capacity());

        //清空緩衝區(緩衝區的數據並無真正意義上的清空,但處於被遺忘的狀態)
        byteBuffer.clear();
        System.out.println("-----clear()-----");
        System.out.println(byteBuffer.position());
        System.out.println(byteBuffer.limit());
        System.out.println(byteBuffer.capacity());
    }
    public static void test2(){
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("abcde".getBytes());
        byteBuffer.flip();
        byte[] bytes = new byte[5];
        byteBuffer.get(bytes, 0, 2);
        byteBuffer.mark();
        System.out.println(new String(bytes,0,2));
        System.out.println(byteBuffer.position());
        byteBuffer.get(bytes, 0, 2);
        //position又回到了mark標記的配置
        byteBuffer.reset();
        byteBuffer.get(bytes, 0, 2);
        System.out.println(new String(bytes,0,2));
        System.out.println(byteBuffer.position());
    }
}

  

直接緩衝區和非直接緩衝區

非直接緩衝區:經過allocate()方法分配緩衝區,將緩衝區創建在JVM的內存中數組

直接緩衝區:經過allocateDirect()方法分配直接緩衝區,將緩衝區創建在物理內存中。能夠提升效率緩存

 

 

 

 

內核空間與用戶空間
內核空間主要指操做系統用於程序調度、虛擬內存的使用或者鏈接硬件資源等的程序邏輯。爲了保證操做系統的穩定向,運行在操做系統中的用戶進程不能訪問操做系統所使用的內存空間。若是用戶程須要訪問硬件資源,如網絡鏈接等,能夠調用操做系統提供的接口來實現,這個接口的調用其實也是系統調用。每次系統調用都會存在兩個內存空間的切換,一般的網絡傳輸也是一次系統調用,經過網絡傳輸的數據先是從內核空間從遠程主機接受數據,而後再從內核空間複製到用戶空間,供程序使用。這種複製手段很費時,雖然包住了程序運行時的安全性與穩定性,可是也犧牲了部分效率。如今linux系統上提供了sendfile文件傳輸方式來減小這種複製方式的成本。
內核空間和用戶空間大小分配也是個須要權衡的問題,若是是一臺登陸服務器要分配更多的內核空間,由於沒有個登陸用戶操做系統都會初始化一個用戶進程,這個進程大部分在內核空間運行。當前windows內核:用戶爲1:1(也就是大約2G內核空間,2G用戶空間),linux爲1:3。安全

補充參考:https://blog.csdn.net/u012129558/article/details/82878994服務器

 

通道

DMA技術的重要性在於,利用它進行數據傳送時不須要CPU的參與。每臺電腦主機板上都有DMA控制器,一般計算機對其編程,並用一個適配器上的ROM(如軟盤驅動控制器上的ROM)來儲存程序,這些程序控制DMA傳送數據。一旦控制器初始化完成,數據開始傳送,DMA就能夠脫離CPU,獨立完成數據傳送。網絡

參考:https://baike.baidu.com/item/DMA%E9%80%9A%E9%81%93/7492727?fr=aladdin多線程

 

通道(Channe1):用於源節點與目標節點的鏈接。在Java NIO中負責緩衝區中數據的傳輸。Channe1自己不存儲數據,所以須要配合緩衝區進行傳輸。

通道的主要實現類

 

注意FileChannel不能切換非堵塞模式,經過上面的圖能夠看出SelectableChannel(監聽器),下面沒有FileChannel

獲取通道

1.Java針對支持通道的類提供了getChanne1()方法
  本地IO:
    FileInputStream/FileOutputStream
    RandomAccessFile
  網絡IO:
    Socket
    ServerSocket
    DatagramSocket
2.在JDK1.7中的NIO.2針對各個通道提供了靜態方法open()
3.在JDK 1.7中的NIO.2的Files 工具類的newByteChannel()

 

一、利用通道完成文件複製(使用非直接緩存區),速度比面向流塊

public static void main(String[] args) {
        String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt";
        String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt";
        copyFile(from_file,to_file);
    }
    private static void copyFile(String from_file, String to_file) {
        try {
            //建立輸入文件通道
            FileChannel fcIn = new FileInputStream(from_file).getChannel();
            //建立輸出文件通道
            FileChannel fcOut = new FileOutputStream(to_file).getChannel();

            //建立緩衝區
            ByteBuffer buf = ByteBuffer.allocate(1024);
            while(fcIn.read(buf)!=-1){
                buf.flip();
                fcOut.write(buf);
                buf.clear();
            }
            fcIn.close();
            fcOut.close();
            System.out.println("copy successful");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

二、利用通道完成文件複製(使用直接緩存區,內存映射),速度比非直接緩衝區塊

public static void main(String[] args) throws IOException {
        String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt";
        String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt";
        copyFile(from_file,to_file);
    }
    private static void copyFile(String from_file, String to_file) throws IOException {
        FileChannel inchannel = FileChannel.open(Paths.get(from_file), StandardOpenOption.READ);
        FileChannel outchannel = FileChannel.open(Paths.get(to_file), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

        //內存映射文件
        MappedByteBuffer inByteBuffer = inchannel.map(FileChannel.MapMode.READ_ONLY, 0, inchannel.size());
        MappedByteBuffer outByteBuffer = outchannel.map(FileChannel.MapMode.READ_WRITE, 0, inchannel.size());

        //直接對緩衝區進行數據讀寫操做
        byte bytes[] = new byte[1024];
        for(int i=0;i<inchannel.size();i++){
            outByteBuffer.put(inByteBuffer.get());
        }
        inchannel.close();
        outchannel.close();
    }

二、利用通道完成文件複製(使用直接緩存區,內存映射),速度比非直接緩衝區快(transferTo、transferFrom)

    public static void main(String[] args) throws IOException {
        String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt";
        String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt";
        copyFile(from_file,to_file);
    }
    private static void copyFile(String from_file, String to_file) throws IOException {
        FileChannel inchannel = FileChannel.open(Paths.get(from_file), StandardOpenOption.READ);
        FileChannel outchannel = FileChannel.open(Paths.get(to_file), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

        inchannel.transferTo(0,inchannel.size(),outchannel);
        inchannel.close();
        outchannel.close();
    }

分散和彙集

    public static void main(String[] args) throws IOException, InterruptedException {
        String from_file = "C:\\Users\\zhengyan\\Desktop\\test1\\x.txt";
        String to_file = "C:\\Users\\zhengyan\\Desktop\\test1\\t.txt";
        copyFile(from_file,to_file);
    }
    private static void copyFile(String from_file, String to_file) throws IOException, InterruptedException {
        RandomAccessFile r = new RandomAccessFile(from_file, "rw");
        RandomAccessFile rw = new RandomAccessFile(to_file, "rw");
        FileChannel rChannel = r.getChannel();
        FileChannel rwChannel = rw.getChannel();

        ByteBuffer byteBuffer1 = ByteBuffer.allocate(1);
        ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);

        //分散讀取數據
        ByteBuffer byteBuffers[] = {byteBuffer1,byteBuffer2};
        rChannel.read(byteBuffers);
        while (rChannel.read(byteBuffers)!=-1){
            for (ByteBuffer byteBuffer:byteBuffers){
                byteBuffer.flip();
            }
            //聚合寫入數據
            rwChannel.write(byteBuffers);
            for (ByteBuffer byteBuffer:byteBuffers){
                byteBuffer.clear();
            }
        }
        r.close();
        rw.close();
    }

字符集編碼和解碼

    private static void charsettest() throws IOException {
        Charset charset = Charset.forName("UTF-8");
        ByteBuffer estr = charset.encode("sdfsd的df");
        //estr.flip();
        CharBuffer decode = charset.decode(estr);
        System.out.println(decode.toString());

        estr.rewind();
        Charset gbk = Charset.forName("GBK");
        CharBuffer decode1 = gbk.decode(estr);
        //會出現亂碼
        System.out.println(decode1.toString());
    }

  

 

NIO核心非堵塞

注意堵塞和非堵塞以及同步和異步的區別

網絡過程當中IO堵塞

  假如服務器只有一個線程來處理用戶請求,因爲某種緣由(數據還沒到達)形成線程堵塞(線程放棄了CPU執行權),此時若是有其餘的用戶請求,該線程就不能及時的處理該請求。傳統的解決方式就是開一個線程池,多線程來處理用戶請求,可是這樣可能依然會形成堵塞的狀況。

NIO解決非堵塞

  利用的是select選擇器。將用戶的請求註冊到select上,select來監聽全部的請求數據(經過單獨的一個線程),若是請求的數據準備完畢,纔將該請求任務分配到服務器的一個或者多個線程上執行。

一、使用NIO,阻塞式完成通信

服務端

//服務端
public class BlockNIOServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //獲取通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //綁定端口
        serverSocketChannel.bind(new InetSocketAddress(8090));

        while (true){
            //獲取客戶端鏈接通道
            SocketChannel socketChannel = serverSocketChannel.accept();
            System.out.println("鏈接成功,等待用戶發送數據");
            SocketAddress remoteAddress = socketChannel.getRemoteAddress();
            String s = remoteAddress.toString();
            String[] split = s.split(":");
            //寫入本地
            String path = "C:\\Users\\zhengyan\\Desktop\\test1\\"+split[1]+".txt";
            FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
            //接受客戶端的數據而且寫入文件
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            while (socketChannel.read(byteBuffer)!=-1){
                byteBuffer.flip();
                fileChannel.write(byteBuffer);
                byteBuffer.clear();
            }
            //能夠給客戶端提供反饋信息
            byteBuffer.put("數據已經接受完畢...".getBytes());
            byteBuffer.flip();
            socketChannel.write(byteBuffer);

            fileChannel.close();
            socketChannel.close();
            System.out.println("寫入數據成功....");
        }
    }
}

客戶端

//客戶端
public class BlockNIOClient {

    public static void main(String[] args) throws IOException, InterruptedException {
        //獲取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8090));
        FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"), StandardOpenOption.READ);

        //System.out.println("模擬10秒以後發送數據...");
        //能夠開啓兩個客戶端,一個睡10秒發送數據(先請求),一個不用睡眠(後請求),發現,必須等第一個用戶處理完畢以後,第二個用戶才能夠被處理
        //Thread.sleep(10000);

        //分配緩衝區大小
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //讀取本地文件發送到服務器
        while (fileChannel.read(byteBuffer)!=-1){
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
        }

        //告訴服務器,個人數據已經發送完畢
        socketChannel.shutdownOutput();

        //接受服務器返回來的消息
        StringBuffer stringBuffer = new StringBuffer();
        int len =-1;
        while ((len=socketChannel.read(byteBuffer))!=-1){
            byteBuffer.flip();
            stringBuffer.append(new String(byteBuffer.array(),0,len));
            byteBuffer.clear();
        }
        System.out.println(stringBuffer);


        socketChannel.close();
        fileChannel.close();
    }
}

一、使用NIO,非阻塞式完成通信(經過select做爲監聽器)

服務端

//服務端
public class BlockNIOServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //獲取通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //切換非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //綁定端口
        serverSocketChannel.bind(new InetSocketAddress(8090));

        //獲取選擇器
        Selector selector = Selector.open();
        //將該通道註冊到select中,讓select監聽該通道的鏈接是否準備就緒
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        Iterator<SelectionKey> iterator = null;
        //經過選擇器輪詢獲取已經準備就緒的事件
        while (selector.select()>0){
            iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                //若是獲取的是準備鏈接就緒的事件
                if (selectionKey.isAcceptable()){
                    System.out.println("有客戶端已經準備好鏈接了....");
                    //開始接受鏈接客戶端
                    SocketChannel accept = serverSocketChannel.accept();
                    //切換非阻塞模式
                    accept.configureBlocking(false);
                    //將通道註冊到selector中,讓select監聽該通道的數據是否準備就緒
                    accept.register(selector,SelectionKey.OP_READ);
                }
                else if (selectionKey.isReadable()){
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    Random random = new Random();
                    int i = random.nextInt(100);
                    String path = "C:\\Users\\zhengyan\\Desktop\\test1\\"+i+".txt";
                    FileChannel fileChannel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    while (socketChannel.read(byteBuffer)!=-1){
                        byteBuffer.flip();
                        fileChannel.write(byteBuffer);
                        byteBuffer.clear();
                    }
                    byteBuffer.put("數據已經接受完畢...".getBytes());
                    byteBuffer.flip();
                    socketChannel.write(byteBuffer);

                    fileChannel.close();
                    socketChannel.close();
                    System.out.println("寫入數據成功....");
                }
                //取消選擇鍵
                iterator.remove();
            }
        }
    }
}

客戶端

//客戶端
public class BlockNIOClient {

    public static void main(String[] args) throws IOException, InterruptedException {
        //獲取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8090));
        FileChannel fileChannel = FileChannel.open(Paths.get("C:\\Users\\zhengyan\\Desktop\\test1\\x.txt"), StandardOpenOption.READ);

        //System.out.println("模擬10秒以後發送數據...");
        //能夠開啓兩個客戶端,一個睡10秒發送數據(先請求),一個不用睡眠(後請求),發現,必須等第一個用戶處理完畢以後,第二個用戶才能夠被處理
        //Thread.sleep(20000);

        //分配緩衝區大小
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //讀取本地文件發送到服務器
        while (fileChannel.read(byteBuffer)!=-1){
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
            byteBuffer.clear();
        }

        //告訴服務器,個人數據已經發送完畢
        socketChannel.shutdownOutput();
        //接受服務器返回來的消息
        StringBuffer stringBuffer = new StringBuffer();
        int len =-1;
        while ((len=socketChannel.read(byteBuffer))!=-1){
            byteBuffer.flip();
            stringBuffer.append(new String(byteBuffer.array(),0,len));
            byteBuffer.clear();
        }
        System.out.println(stringBuffer);

        socketChannel.close();
        fileChannel.close();
    }
}

  

使用UDP

服務端

//服務端
public class BlockNIOServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //獲取通道
        DatagramChannel datagramChannel = DatagramChannel.open();
        //切換非阻塞模式
        datagramChannel.configureBlocking(false);
        //綁定端口
        datagramChannel.bind(new InetSocketAddress(8090));
        //獲取選擇器
        Selector selector = Selector.open();
        //只須要監聽數據是否到來
        datagramChannel.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()){
                    DatagramChannel channel = (DatagramChannel) selectionKey.channel();
                    ByteBuffer buf=ByteBuffer.allocate(1024);
                    channel.receive(buf);
                    buf.flip();
                    System.out.println(new String(buf.array(),0,buf.limit()));
                }
                //取消選擇鍵
                iterator.remove();
            }
        }
    }
}

客戶端

//客戶端
public class BlockNIOClient {
    public static void main(String[] args) throws IOException{
        DatagramChannel datagramChannel = DatagramChannel.open();
        ByteBuffer buf=ByteBuffer.allocate(1024);
        buf.put(new Date().toString().getBytes());
        buf.flip();
        datagramChannel.send(buf,new InetSocketAddress("127.0.0.1",8090));
        buf.clear();
        datagramChannel.close();
    }
}

  

管道(Pipe)

Java NIO 管道是兩個線程之間的單向數據鏈接。Pipe有一個source通道和一個sink通道。數據會被寫到sink通道,從source通道讀取。

public class PipeTest {
    public static void main(String[] args) throws IOException, InterruptedException {
        //獲取管道
        Pipe pipe = Pipe.open();
        new Thread(new MyThread1(pipe)).start();
        Thread.sleep(3000);
        new Thread(new MyThread2(pipe)).start();
    }
}

class MyThread1 implements Runnable{
    private Pipe pipe;
    public MyThread1(Pipe pipe){
        this.pipe = pipe;
    }
    @Override
    public void run() {
        Pipe.SinkChannel sink = pipe.sink();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put("ssss".getBytes());
        byteBuffer.flip();
        try {
            sink.write(byteBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        byteBuffer.clear();
    }
}

class MyThread2 implements Runnable{
    private Pipe pipe;
    public MyThread2(Pipe pipe){
        this.pipe = pipe;
    }
    @Override
    public void run() {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        Pipe.SourceChannel source = pipe.source();
        try {
            source.read(byteBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit()));
    }
}
相關文章
相關標籤/搜索