1. 程式人生 > 實用技巧 >NIO程式設計文件

NIO程式設計文件

目錄

一.NIO概述

  1.1什麼是NIO?

  1.2 NIO與IO的區別

二.Buffer的資料存取

  2.1Buffer的概述

  2.2 make與rest用法

三.直接緩衝區與非直接緩衝區別

  3.1通道(Channel)的原理獲取

  3.2直接緩衝區與非直接緩衝耗時計算

  3.3分散讀取與聚集寫入

四.字符集 Charset

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

一.NIO概述

  1.1什麼是NIO?

      

Java NIO(New IO)是一個可以替代標準Java IO API的IO API(從Java 1.4開始),Java NIO提供了與標準IO不同的IO工作方式。

Java NIO: Channels and Buffers(通道和緩衝區)

標準的IO基於位元組流和字元流進行操作的,而NIO是基於通道(Channel)和緩衝區(Buffer)進行操作,資料總是從通道讀取到緩衝區中,或者從緩衝區寫入到通道中。

Java NIO: Non-blocking IO(非阻塞IO)

Java NIO可以讓你非阻塞的使用IO,例如:當執行緒從通道讀取資料到緩衝區時,執行緒還是可以進行其他事情。當資料被寫入到緩衝區時,執行緒可以繼續處理它。從緩衝區寫入通道也類似。

Java NIO: Selectors(選擇器)

Java NIO引入了選擇器的概念,選擇器用於監聽多個通道的事件(比如:連線開啟,資料到達)。因此,單個的執行緒可以監聽多個數據通道。

注意:傳統IT是單向。 NIO類似

      

      

  1.2 NIO與IO的區別

IO

NIO

面向流

面向緩衝區

阻塞IO

非阻塞IO

選擇器

二.Buffer的資料存取

一個用於特定基本資料類行的容器。有java.nio包定義的,所有緩衝區都是抽象類Buffer的子類。

  Java NIO中的Buffer主要用於與NIO通道進行互動,資料是從通道讀入到緩衝區,從緩衝區寫入通道中的。

  Buffer就像一個數組,可以儲存多個相同型別的資料。根據型別不同(boolean除外),有以下Buffer常用子類:

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

  2.1Buffer的概述

       

1)容量(capacity):表示Buffer最大資料容量,緩衝區容量不能為負,並且建立後不能修改。

2)限制(limit):第一個不應該讀取或者寫入的資料的索引,即位於limit後的資料不可以讀寫。緩衝區的限制不能為負,並且不能大於其容量(capacity)。

3)位置(position):下一個要讀取或寫入的資料的索引。緩衝區的位置不能為負,並且不能大於其限制(limit)。

4)標記(mark)與重置(reset):標記是一個索引,通過Buffer中的mark()方法指定Buffer中一個特定的position,之後可以通過呼叫reset()方法恢復到這個position。

       

/**
 * (緩衝區)buffer 用於NIO儲存資料 支援多種不同的資料型別 <br>
 * 1.byteBuffer <br>
 * 2.charBuffer <br>
 * 3.shortBuffer<br>
 * 4.IntBuffer<br>
 * 5.LongBuffer<br> 
 * 6.FloatBuffer <br>
 * 7.DubooBuffer <br>
 * 上述緩衝區管理的方式 幾乎<br>
 * 通過allocate() 獲取緩衝區 <br>
 * 二、緩衝區核心的方法 put 存入資料到緩衝區 get <br> 獲取緩衝區資料 flip 開啟讀模式
 * 三、緩衝區四個核心屬性<br>
 * capacity:緩衝區最大容量,一旦宣告不能改變。 limit:介面(緩衝區可以操作的資料大小) limit後面的資料不能讀寫。
 * position:緩衝區正在操作的位置
 */
public class Test004 {

    public static void main(String[] args) {
        // 1.指定緩衝區大小1024
        ByteBuffer buf = ByteBuffer.allocate(1024);
        System.out.println("--------------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());
        // 2.向緩衝區存放5個數據
        buf.put("abcd1".getBytes());
        System.out.println("--------------------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());
        // 3.開啟讀模式
        buf.flip();
        System.out.println("----------開啟讀模式...----------");
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());
        byte[] bytes = new byte[buf.limit()];
        buf.get(bytes);
        System.out.println(new String(bytes, 0, bytes.length));
        System.out.println("----------重複讀模式...----------");
        // 4.開啟重複讀模式
        buf.rewind();
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());
        byte[] bytes2 = new byte[buf.limit()];
        buf.get(bytes2);
        System.out.println(new String(bytes2, 0, bytes2.length));
        // 5.clean 清空緩衝區  資料依然存在,只不過資料被遺忘
        System.out.println("----------清空緩衝區...----------");
        buf.clear();
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());
        System.out.println((char)buf.get());
    }

}

  2.2 make與rest用法

      標記(mark)與重置(reset):標記是一個索引,通過Buffer中的mark()方法指定Buffer中一個特定的position,之後可以通過呼叫reset()方法恢復到這個position。

      

public class Test002 {

    public static void main(String[] args) {
        ByteBuffer buf = ByteBuffer.allocate(1024);
        String str = "abcd1";
        buf.put(str.getBytes());
        // 開啟讀取模式
        buf.flip();
        byte[] dst = new byte[buf.limit()];
        buf.get(dst, 0, 2);
        buf.mark();
        System.out.println(new String(dst, 0, 2));
        System.out.println(buf.position());
        buf.get(dst, 2, 2);
        System.out.println(new String(dst, 2, 2));
        System.out.println(buf.position());
        buf.reset();
        System.out.println("重置恢復到mark位置..");
        System.out.println(buf.position());
    }

}

三.直接緩衝區與非直接緩衝區別

非直接緩衝區:通過 allocate() 方法分配緩衝區,將緩衝區建立在 JVM 的記憶體中

當我們的程式想要從硬碟中讀取資料 需要

1.先從物理硬碟把資料讀取到實體記憶體中

2.再將內容複製到JVM的記憶體中

3.然後讀取應用程式才可以讀取到內容

讀寫都是這樣需要複製這一個動作 當遇到大文字的檔案時 效率及其低下.

其中傳統的io和 nio的accocate()都是非直接緩衝區.

直接緩衝區:通過 allocateDirect() 方法分配直接緩衝區,將緩衝區建立在實體記憶體中。可以提高效率

位元組緩衝區要麼是直接的,要麼是非直接的。如果為直接位元組緩衝區,則 Java 虛擬機器會盡最大努力直接在此緩衝區上執行本機 I/O 操作。也就是說,在每次呼叫基礎作業系統的一個本機 I/O 操作之前(或之後),虛擬機器都會盡量避免將緩衝區的內容複製到中間緩衝區中(或從中間緩衝區中複製內容)。

直接位元組緩衝區可以通過呼叫此類的 allocateDirect() 工廠方法來建立。此方法返回的緩衝區進行分配和取消分配所需成本通常高於非直接緩衝區。直接緩衝區的內容可以駐留在常規的垃圾回收堆之外,因此,它們對應用程式的記憶體需求量造成的影響可能並不明顯。所以,建議將直接緩衝區主要分配給那些易受基礎系統的本機 I/O 操作影響的大型、持久的緩衝區。一般情況下,最好僅在直接緩衝區能在程式效能方面帶來明顯好處時分配它們。

直接位元組緩衝區還可以通過 FileChannel 的 map() 方法 將檔案區域直接對映到記憶體中來建立。該方法返回MappedByteBuffer 。 Java 平臺的實現有助於通過 JNI 從本機程式碼建立直接位元組緩衝區。如果以上這些緩衝區中的某個緩衝區例項指的是不可訪問的記憶體區域,則試圖訪問該區域不會更改該緩衝區的內容,並且將會在訪問期間或稍後的某個時間導致丟擲不確定的異常。

位元組緩衝區是直接緩衝區還是非直接緩衝區可通過呼叫其 isDirect() 方法來確定。提供此方法是為了能夠在效能關鍵型程式碼中執行顯式緩衝區管理。

// 使用直接緩衝區完成檔案的複製(記憶體對映檔案)
    static public void test2() throws IOException {
        long start = System.currentTimeMillis();
        FileChannel inChannel = FileChannel.open(Paths.get("f://1.mp4"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("f://2.mp4"), StandardOpenOption.WRITE,
                StandardOpenOption.READ, StandardOpenOption.CREATE);
        // 記憶體對映檔案
        MappedByteBuffer inMappedByteBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedByteBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
        // 直接對緩衝區進行資料的讀寫操作
        byte[] dsf = new byte[inMappedByteBuf.limit()];
        inMappedByteBuf.get(dsf);
        outMappedByteBuffer.put(dsf);
        inChannel.close();
        outChannel.close();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    // 1.利用通道完成檔案的複製(非直接緩衝區)
    static public void test1() throws IOException { // 4400
        long start = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream("f://1.mp4");
        FileOutputStream fos = new FileOutputStream("f://2.mp4");
        // ①獲取通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        // ②分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);
        while (inChannel.read(buf) != -1) {
            buf.flip();// 切換為讀取資料
            // ③將緩衝區中的資料寫入通道中
            outChannel.write(buf);
            buf.clear();
        }
        outChannel.close();
        inChannel.close();
        fos.close();
        fis.close();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

  3.1通道(Channel)的原理獲取

    

通道表示開啟到 IO 裝置(例如:檔案、套接字)的連線。若需要使用 NIO 系統,需要獲取用於連線 IO 裝置的通道以及用於容納資料的緩衝區。然後操作緩衝區,對資料進行處理。Channel 負責傳輸, Buffer 負責儲存。通道是由 java.nio.channels 包定義的。 Channel 表示 IO 源與目標開啟的連線。Channel 類似於傳統的“流”。只不過 Channel本身不能直接訪問資料, Channel 只能與Buffer 進行互動。

    

java.nio.channels.Channel 介面:

|--FileChannel

|--SocketChannel

|--ServerSocketChannel

|--DatagramChannel

獲取通道

1. Java 針對支援通道的類提供了 getChannel() 方法

本地 IO:

FileInputStream/FileOutputStream

RandomAccessFile

網路IO:

Socket

ServerSocket

DatagramSocket

2. 在 JDK 1.7 中的 NIO.2 針對各個通道提供了靜態方法 open()

3. 在 JDK 1.7 中的 NIO.2 的 Files 工具類的 newByteChannel()

      

@Test
    // 使用直接緩衝區完成檔案的複製(記憶體對映檔案)
    public void test2() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.READ, StandardOpenOption.WRITE,
                StandardOpenOption.CREATE);
        // 對映檔案
        MappedByteBuffer inMapperBuff = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMapperBuff = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
        // 直接對緩衝區進行資料讀寫操作
        byte[] dst = new byte[inMapperBuff.limit()];
        inMapperBuff.get(dst);
        outMapperBuff.put(dst);
        outChannel.close();
        inChannel.close();
    }

    @Test
    // 1.利用通道完成檔案複製(非直接緩衝區)
    public void test1() throws IOException {
        FileInputStream fis = new FileInputStream("1.png");
        FileOutputStream fos = new FileOutputStream("2.png");
        // ①獲取到通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();

        // ②分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);
        while (inChannel.read(buf) != -1) {
            buf.flip();// 切換到讀取模式
            outChannel.write(buf);
            buf.clear();// 清空緩衝區
        }
        // 關閉連線
        outChannel.close();
        inChannel.close();
        fos.close();
        fis.close();
    }

  3.2直接緩衝區與非直接緩衝耗時計算

      

@Test
    // 使用直接緩衝區完成檔案的複製(記憶體對映檔案) //428、357
    public void test2() throws IOException {
        long startTime = System.currentTimeMillis();
        FileChannel inChannel = FileChannel.open(Paths.get("f://1.mp4"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("f://2.mp4"), StandardOpenOption.READ, StandardOpenOption.WRITE,
                StandardOpenOption.CREATE);
        // 對映檔案
        MappedByteBuffer inMapperBuff = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMapperBuff = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
        // 直接對緩衝區進行資料讀寫操作
        byte[] dst = new byte[inMapperBuff.limit()];
        inMapperBuff.get(dst);
        outMapperBuff.put(dst);
        outChannel.close();
        inChannel.close();
        long endTime = System.currentTimeMillis();
        System.out.println("記憶體對映檔案耗時:"+(endTime-startTime));
    }

    @Test
    // 1.利用通道完成檔案複製(非直接緩衝區)
    public void test1() throws IOException {  //11953 、3207、3337
        long startTime = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream("f://1.mp4");
        FileOutputStream fos = new FileOutputStream("f://2.mp4");
        // ①獲取到通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();

        // ②分配指定大小的緩衝區
        ByteBuffer buf = ByteBuffer.allocate(1024);
        while (inChannel.read(buf) != -1) {
            buf.flip();// 切換到讀取模式
            outChannel.write(buf);
            buf.clear();// 清空緩衝區
        }
        // 關閉連線
        outChannel.close();
        inChannel.close();
        fos.close();
        fis.close();
        long endTime = System.currentTimeMillis();
        System.out.println("非緩衝區:"+(endTime-startTime));
    }

  3.3分散讀取與聚集寫入

    分散讀取(scattering Reads):將通道中的資料分散到多個緩衝區中

聚集寫入(gathering Writes):將多個緩衝區的資料聚集到通道中

    

RandomAccessFile raf1 = new RandomAccessFile("test.txt", "rw");
        // 1.獲取通道
        FileChannel channel = raf1.getChannel();
        // 2.分配指定大小的指定緩衝區
        ByteBuffer buf1 = ByteBuffer.allocate(100);
        ByteBuffer buf2 = ByteBuffer.allocate(1024);
        // 3.分散讀取
        ByteBuffer[] bufs = { buf1, buf2 };
        channel.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);

四.字符集Charset

編碼:字串->位元組陣列

解碼:位元組陣列 -> 字串

public class Test005 {

    public static void main(String[] args) throws CharacterCodingException {
        // 獲取編碼器
        Charset cs1 = Charset.forName("GBK");
        // 獲取編碼器
        CharsetEncoder ce = cs1.newEncoder();
        // 獲取解碼器
        CharsetDecoder cd = cs1.newDecoder();
        CharBuffer cBuf = CharBuffer.allocate(1024);
        cBuf.put("螞蟻課堂牛逼!");
        cBuf.flip();
        // 編碼
        ByteBuffer bBuf = ce.encode(cBuf);
        for (int i = 0; i < 12; i++) {
            System.out.println(bBuf.get());
        }
        // 解碼
        bBuf.flip();
        CharBuffer cBuf2 = cd.decode(bBuf);
        System.out.println(cBuf2.toString());
        System.out.println("-------------------------------------");
        Charset cs2 = Charset.forName("GBK");
        bBuf.flip();
        CharBuffer cbeef = cs2.decode(bBuf);
        System.out.println(cbeef.toString());
    }

}