NIO詳解

NIO

前言

NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的做用和目的,但實現方式不一樣,NIO主要用到的是塊,因此NIO的效率要比IO高不少。在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另外一套就是網絡編程NIO。編程

IO與NIO的區別

  • IO流是面向流的,屬於阻塞IO,沒有選擇器。
  • NIO流是面向塊(緩衝區),屬於非阻塞IO有選擇器。
    其中最大的區別就是一個面向流,一個面向緩衝區。
    • 面向流:IO流每次讀取一個字節或者多個,直到讀完爲止。不能對流中的數據進行操做。
    • 面向緩衝區:NIO流是將數據放到一個緩衝區,須要時能夠對緩衝區中的數據進行操做,這樣就能夠更加靈活的操做數據了。(就好像IO流是一根水管,而NIO是一個運水車)

Buffer(緩衝區)

Buffer是一個抽象類;
子類有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer
核心類(經常使用類):ByteBufferCharBuffer 其中ByteBuffer有一個子類MappedByteBufferMappedByteBuffer類可以將文件直接映射到內存中,那麼這樣咱們就能夠像訪問內存同樣訪問文件,很是方便)網絡

建立Buffer

由於Buffer都是抽象類,沒法直接實例化。建立緩衝區要調用XxxBuffer allocate(int capacity),XxxBuffer allocateDirect(int capacity),參數是緩衝區容量。app

eg:獲取ByteBuffer
static ByteBuffer allocate(int capacity)
分配一個新的字節緩衝區(普通Buffer)
static ByteBuffer allocateDirect(int capacity)
分配新的直接字節緩衝區(直接Buffer)dom

兩者的區別:異步

  1. 建立普通Buffer成本低,讀寫的效率不高
  2. 由於建立直接Buffer成本高,因此咱們通常用在Buffer生存週期較長的時候使用
  3. 只有ByteBuffer纔可以建立直接Buffer,其餘的Buffer對象是不可以建立
  4. 若是建立了直接Buffer可是我又想要使用其餘Buffer的功能,能夠將ByteBuffer轉換成其餘Buffer
Buffer參數
  • capacity(容量):緩衝區的容量,不能夠爲負數,一旦建立了就不可以改變
  • limit(界限):是緩衝區讀寫數據的終止點,limit以後的區域沒法訪問
  • position(起始指針):是緩衝區讀寫數據的起始點,初始值爲0。position隨着數據的加入而改變,例如讀取2個數據到Buffer中,則position = 2
  • mark(標記):該索引可以用於下次讀取或者寫入,mark在0~position之間,設置該值就會把position移動到mark處

Buffer方法
  • flip():讀取模式;肯定緩衝區數據的起始點和終止點,爲輸出數據作準備(即寫入通道), 將limit的值改成postion的值,同時將postion歸0
    • 特色: 就是爲下一次數據的讀取作好準備
  • clear():寫入模式;緩衝區初始化,準備再次接收新數據到緩衝區,將limit改成capacity的值,同時將postion歸0
    • 特色: 就是爲下一次數據的寫入作好準備
  • get()和put():獲取元素和存放元素。使用clear()以後,沒法直接使用get()獲取元素,須要使用get(int index)根據索引值來獲取相應元素
  • hasRemaining():判斷postion到limit之間是否還有元素。
public static  void main(String[] args) {
        CharBuffer buffer = CharBuffer.allocate(8);
        // Buffer已經準備好了向Buffer中寫數據    寫模式
        System.out.println("capacity:" + buffer.capacity()); // 8
        System.out.println("limit:" + buffer.limit()); // 8
        System.out.println("position:" + buffer.position()); // 0   
        buffer.put('a');
        buffer.put('b');
        buffer.put('c');
        System.out.println("------------------------"); 
        System.out.println("capacity:" + buffer.capacity()); // 8
        System.out.println("limit:" + buffer.limit()); // 8
        System.out.println("position:" + buffer.position()); // 3   
        System.out.println("------------------------");
        // 切換模式  ,limit變爲position的位置而後將position變爲0
        buffer.flip();
        System.out.println("capacity:" + buffer.capacity()); // 8
        System.out.println("limit:" + buffer.limit()); // 3
        System.out.println("position:" + buffer.position()); // 0
        System.out.println("------------------------");
            System.out.println("------------------");
        buffer.clear(); // 將postion 清 0 ,將limit = capacity  
        System.out.println("capacity:" + buffer.capacity()); // 8
        System.out.println("limit:" + buffer.limit()); // 8
        System.out.println("position:" + buffer.position()); // 0
        // 注意: 調用clear方法只是將讀模式改成寫模式,並不會清空緩衝區的數據

    }

Channel(通道)

Channel原理相似於傳統的流對象,區別在於:
1.Channel可以將指定的部分或者所有文件映射到內存中
2.程序若是想要讀取Channel中的數據,不可以直接讀寫,必須通過Buffer
簡單來講:Channel經過Buffer(緩衝區)進行讀寫操做。read()表示讀取通道數據到緩衝區,write()表示把緩衝區數據寫入到通道。ide

Channel實現類
  • FileChannel 和文件相關的通道
  • DatagramChannel 和UDP協議傳輸數據相關的通道
  • SocketChannel 和TCP協議相關的數據傳輸通道
  • ServerSocket 和TCP協議相關的數據傳輸通道
  • Pipe.SinkChannel、Pipe.SourceChannel //線程通訊管道傳輸數據
Channel經常使用方法
  • read() : 將Channel中的數據讀取到Buffer中
  • write() : 向Buffer中寫入數據
  • map(): 將channel中的數據所有或者部分映射到Buffer中(MappedByteBuffer,本質也是一個ByteBuffer),map()方法參數(讀寫模式,映射起始位置,數據長度)。
    • inChannel.map(mode, position, size)
    • MappedByteBuffer mappBuffer = inChannel.map(MapMode.READ_ONLY, 0, srcFile.length());
public static void main(String[] args) throws IOException {
        File srcFile = new File("nio-a.txt");
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(new File("nio-b.txt"));
        
        // 獲取Channel對象
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        // 獲取MapByteBuffer對象
        MappedByteBuffer mapBuffer = inChannel.map(MapMode.READ_ONLY, 0, srcFile.length());
        //字符集解碼器    
        Charset charset = Charset.forName("GBK");
        outChannel.write(mapBuffer);
        CharsetDecoder decoder = charset.newDecoder();
        CharBuffer charBuffer = decoder.decode(mapBuffer);
        System.out.println(charBuffer);
        
        
    }

通道能夠異步讀寫,異步讀寫表示通道執行讀寫操做時,也能作別的事情,解決線程阻塞。若是使用文件管道(FileChannel),建議用RandomAccessFile來建立管道,由於該類支持讀寫模式以及有大量處理文件的方法。post

public static void main(String[] args) throws Exception {
        File f = new File("nio-a.txt");
        
        RandomAccessFile raf = new RandomAccessFile(f, "rw");
        
        FileChannel channel = raf.getChannel();
        
        MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
        
//      raf.seek(f.length());
        channel.position(f.length());
        
        channel.write(mapBuffer);
    }

Charset(字符集)

理解爲現實生活的編碼表對象
當使用NIO來獲取文件內容時,若是是文本數據,那麼須要進行轉碼,才能查看正確內容,這就須要解碼器。 若是要把字符數據寫入文件,須要將CharBuffer轉碼成ByteBuffer,這就須要編碼器。編碼

  • 包含了字節和 Unicode 字符之間轉換的 charset,還定義了用於建立解碼器和編碼器以及獲取與 charset 關聯的各類方法
  • CharsetDecoder(解碼器):把字節轉成字符,例如查看文本數據,須要轉成字符才能查看,若是是字節,就看不懂了。
  • CharsetEncoder(編碼器):把字符轉成字節,才能被計算機理解。 由於字節是計算機最小的存儲單位,因此Channel的IO操做都與ByteBuffer有關
  • 解碼器和編碼器都不能直接建立,須要一個Charset對象來建立對應的解碼器和編碼器。
Charset經常使用方法
  • forName():根據傳入的字符集得到對應的字符集對象。
  • defaultCharset():得到當前使用的默認字符集。
  • availableCharsets():得到全部有效的字符集。

NIO遍歷文件

public static void main(String[] args) throws IOException {
            //匿名子對象實現FileVisitor接口
        FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
                System.out.println("正在訪問" + path + "文件");
                if(path.endsWith("NIODemo.java")){
                    System.out.println("恭喜您找到Java");
                    return FileVisitResult.CONTINUE;
                }
                return FileVisitResult.CONTINUE;
            }
            
            @Override
            public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException {
                System.out.println("準備訪問" + path + "文件");
                return FileVisitResult.CONTINUE;
            }
            
            @Override
            public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException {
                System.out.println("準備訪問" + path + "文件失敗");
                System.out.println(exc.getMessage());
                return FileVisitResult.CONTINUE;
            }
        };
            //訪問文件樹
        Files.walkFileTree(Paths.get("D:\\JavaSE"), visitor);
        
    }

以上線程

@Fzxey

相關文章
相關標籤/搜索