Java入門系列-23-NIO(使用緩衝區和通道對文件操做)

NIO 是什麼

java.nio全稱java non-blocking(非阻塞) IO(其實是 new io),是指jdk1.4 及以上版本里提供的新api(New IO) ,爲全部的原始類型(boolean類型除外)提供緩存支持的數據容器,使用它能夠提供非阻塞式的高伸縮性網絡。java

NIO與IO的區別

IO NIO
面向流(Stream Oriented) 面向緩衝區(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞(Non Blocking IO)
選擇器(Selectors)

NIO系統的核心是:通道(Channel)和緩衝區(Buffer)api

緩衝區(Buffer)

位於 java.nio 包,全部緩衝區都是 Buffer 抽象類的子類,使用數組對數據進行緩衝。數組

除了 boolean 類型,Buffer 對每種基本數據類型都有針對的實現類:緩存

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

建立緩衝區經過 xxxBuffer.allocate(int capacity)方法網絡

ByteBuffer buf1 = ByteBuffer.allocate(512);
LongBuffer buf2 = LongBuffer.allocate(1024);
……

緩衝區的屬性

容量(capacity):表示緩衝區存儲數據的最大容量,不能爲負數,建立後不可修改。app

限制:第一個不能夠讀取或寫入的數據的索引,即位於 limit 後的數據不能讀寫。不能爲負數,不能大於容量。dom

位置(position):下一個要讀取或寫入的數據的索引,位置不能爲負數,不能大於 limitpost

標記(mark):標記是一個索引,經過 Buffer 中的 mark() 方法指 Buffer 中一個特定的 position,以後能夠經過 reset() 方法回到這個 postion。性能

Buffer 的經常使用方法

方法名稱 說明
Buffer clear() 清空緩衝區並返回對緩衝區的引用
Buffer flip() 將緩衝區的 limit 設置爲當前位置,並將當前位置重置爲0
int capacity() 返回 Buffer 的容量大小
boolean hasRemaining() 判斷緩衝區是否還有元素
int limit() 返回 限制的位置
Buffer limit(int n) 將設置緩衝區界限爲 n,並返回一個具備新 limit 的緩衝區對象
Buffer mark() 對緩衝區設置標記
int position() 返回緩衝區的當前位置 position
Buffer position(int n) 將設置緩衝區的當前位置爲 n,並返回修改後的 Buffer 對象
int remaining() 返回 position 和 limit 之間的元素個數
Buffer reset() 將位置 position 轉到之前設置的 mark 所在的位置
Buffer rewind() 將位置設置爲 0,取消設置的 mark

Buffer 全部子類提供了兩個操做的數據的方法:get() 方法和 put() 方法code

緩衝區存取數據操做

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer1 {

    public static void main(String[] args) {
        testuse();
    }

    public static void testuse() {
        //1.分配一個指定大小的緩衝區
        ByteBuffer buf=ByteBuffer.allocate(1024);

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

        //2.利用 put() 存入數據到緩衝區中
        String str="hello";
        //將字符串轉爲 byte 數組存入緩衝區
        buf.put(str.getBytes());
        System.out.println("---------------put()----------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

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

        //4.利用get() 讀取緩衝區中的數據
        byte[] data=new byte[buf.limit()];
        System.out.println("---------------get()----------------");
        buf.get(data);
        System.out.println(new String(data,0,data.length));
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //5.rewind() 重複讀
        buf.rewind();
        System.out.println("---------------rewind()----------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //6.clear() 清空緩衝區,但緩衝區中的數據依然存在
        buf.clear();
        System.out.println("---------------clear()----------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());
        System.out.println((char)buf.get());
    }
}

使用 mark()方法標記

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer2 {

    public static void main(String[] args) {
        testmark();
    }

    public static void testmark() {
        String str="jikedaquan.com";
        //建立緩衝區
        ByteBuffer buf=ByteBuffer.allocate(1024);
        //存入數據
        buf.put(str.getBytes());
        //切換模式
        buf.flip();
        //臨時數組用於接收緩衝區獲取的數據,長度與緩衝區 limit 一值
        byte[] data=new byte[buf.limit()];
        //獲取緩衝區的數據從0開始獲取4個,存入 data 數組中
        buf.get(data, 0, 4);
        //將數組轉爲字符串打印
        System.out.println(new String(data,0,4));
        //打印 position
        System.out.println(buf.position());

        //標記
        buf.mark();
        System.out.println("---------------再次獲取----------------");
        //從索引4開始,獲取6個字節(餘下數據)
        buf.get(data, 4, 6);
        System.out.println(new String(data,4,6));
        System.out.println(buf.position());

        //恢復到標記位置
        buf.reset();
        System.out.println("---------------reset()----------------");
        System.out.println(buf.position());

        //判斷緩衝區是是有還有剩餘數據
        if (buf.hasRemaining()) {
            //獲取緩衝區中能夠操做的數量
            System.out.println("可操做數量:"+buf.remaining());
        }
    }
}

mark <= position <= limit <= capacity

雖然使用了緩衝區提升了必定的IO速度,但這樣的效率仍然不是最高的。非直接緩衝區在與物理磁盤操做中須要通過內核地址空間copy操做,直接緩衝區不通過copy操做,直接操做物理內存映射文件,
使用直接緩衝區將大大提升效率。

直接緩衝區進行分配和取消分配所需成本工廠高於非直接緩衝區,通常狀況下,最好僅在直接緩衝區能在程序性能方面帶來明顯好處時分配它們。

直接緩衝區能夠經過調用此類的 allocateDirect()工廠方法建立

建立直接緩衝區

package testnio;

import java.nio.ByteBuffer;

public class TestBuffer3 {

    public static void main(String[] args) {
        testAllocateDirect();
    }

    public static void testAllocateDirect() {
        //建立直接緩衝區
        ByteBuffer buf=ByteBuffer.allocateDirect(1024);
        
        //是不是直接緩衝區
        System.out.println(buf.isDirect());
    }
}

通道(Channel)

緩衝區僅是運載數據的容器,須要對數據讀寫還須要有一條通道,這二者是密不可分的。

Channel 接口的主要實現類:

  • FileChannel:用於讀取、寫入、映射和操做文件的通道
  • DatagramChannel:經過 UDP 讀寫網絡中的數據通道
  • ScoketChannel:經過 TCP 讀寫網絡中的數據
  • ServerScoketChannel:能夠監聽新進來的 TCP 連接,對每個新進來的鏈接都會建立一個 SocketChannel

如何獲取通道?

一、經過支持通道的對象調用 getChannel() 方法

支持通道的類:

  • FileInputStream
  • FileOutputStream
  • RandomAccessFile
  • DatagramScoket
  • Socket
  • ServerScoket

使用通道和緩衝區實現文件讀和寫

package testnio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class TestChannel {

    public static void main(String[] args) {
        FileInputStream fis=null;
        FileOutputStream fos=null;
        
        FileChannel inChannel=null;
        FileChannel outChannel=null;
        
        try {
            //建立輸入流
            fis=new FileInputStream("F:/1.jpg");
            //建立輸出流
            fos=new FileOutputStream("F:/2.jpg");
            //獲取通道
            inChannel=fis.getChannel();
            outChannel=fos.getChannel();
            
            //分配指定大小的緩衝區
            ByteBuffer buf=ByteBuffer.allocate(1024);
            
            //將通道中的數據存入緩存區
            while(inChannel.read(buf)!=-1) {
                //切換讀取數據的模式
                buf.flip();
                //將讀入的緩衝區存入寫數據的管道
                outChannel.write(buf);
                //清空緩存區(清空才能再次讀入)
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (fis!=null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos!=null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inChannel!=null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outChannel!=null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
}

二、經過通道類的靜態方法 open()

package testnio;

import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class TestOpenAndMapped {

    public static void main(String[] args) {
        FileChannel inChannel=null;
        FileChannel outChannel=null;
        try {
            //經過open建立通道
            inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("F:/b.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

            //內存映射文件   直接緩衝區
            MappedByteBuffer inMappedBuf=inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedBuf=outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());

            //直接對緩衝區進行數據的讀寫操做
            byte[] data=new byte[inMappedBuf.limit()];
            inMappedBuf.get(data);//讀
            outMappedBuf.put(data);//寫
        } 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();
                }
            }
        }
    }
}

JDK 1.7 新增的方法 open(),參數 path 一般表明一個依賴系統的文件路徑,經過Paths.get()獲取。

參數 StandardOpenOption 是一個枚舉類型,經常使用值以下:

  • READ :打開讀訪問
  • WRITE:打開寫訪問
  • APPEND:向後追加
  • CREATE:建立新文件,存在則覆蓋
  • CREATE_NEW:建立新文件,存在則報錯

MappedByteBuffer:直接字節緩衝區,其內容是文件的內存映射區域。

通道數據傳輸

將數據從源通道傳輸到其餘 Channel 中,transferTo() 和 transferFrom()

package testnio;

import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class TestChannelTransfer {

    public static void main(String[] args) {
        FileChannel inChannel=null;
        FileChannel outChannel=null;
        try {
            //經過open建立管道
            inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);
            outChannel = FileChannel.open(Paths.get("F:/b.jpg"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
            //將inChannel中全部數據發送到outChannel
            //inChannel.transferTo(0, inChannel.size(), outChannel);
            outChannel.transferFrom(inChannel, 0, inChannel.size());
        } 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();
                }
            }
        }
    }
}

Channel 負責傳輸,Buffer 負責存儲

相關文章
相關標籤/搜索