NIO中Buffer的重要屬性關系解析


Buffer 是java NIO中三個核心概念之一 緩存, 在java的實現體系中Buffer作為頂級抽象類存在

簡單說,Buffer在做什么?

我們知道,在java IO中體系中, 因為InputStream和OutputStream是抽象類,而java又不可以多重繼承,於是任何一個流要么只讀,要么只寫.而無法完成同時讀寫的工作

於是: Buffer來了

NIO中,對數據的讀寫,都是在Buffer中完成的,也就是說,同一個buffer我們可以先讀后寫, 它底層維護着一個數組,這個數組被三個重要的屬性控制,有機的工作結合,使buffer可讀可寫;

此外,Buffer是線程不安全的,並發訪問需要同步

三個重要屬性:

  • capacity: 容量
    • Buffer中元素的個數
    • 永遠不能為負數
    • 永遠不會變化
  • limit: 限制
    • 實際上它是Buffer所維護的那個數組中的一個下標
    • limit是第一個不能被讀,或者第一個不能被寫的元素的index
    • limit永遠不會是負數
    • 永遠不會超過capacity
  • Position: 定位
    • 指數組中下一個將要被讀或者將要被寫的元素的索引

圖解,Buffer是如何維護的數組

圖片

一開始: 我們初始化它的大小為6 初始狀態,Capacity和Limit都在最后一個不能被讀或者不能被寫的位置上


圖片

接着我們讀入兩個數據.position跳轉到下一個將被讀的index


接下來,准備寫把buffer中的數據寫出去兩個, 於是我們需要反轉數值

buffer.flip();

反轉的邏輯:

limit=position;
position=0;

於是從0寫 ,寫到哪個位置? 寫到limi前

圖片

寫完畢后:如下圖:

圖片

現在可以看到,pisition == limit

如果再想讀入新的數據,同樣需要反轉數據flip()

但是此時,limit仍然是剛剛position的所在的最后的位置

Buffer架構體系:

圖片

模擬Buffer實現一個相同的繼承體系,進一步了解成員變量在他們之間是怎么維護的

首先,和Buufer等級一樣的頂級父類

public abstract class ParentSupper {
    private int position;
    private int capacity;

    ParentSupper(int position,int capacity){
        this.position=position;
        this.capacity=capacity;
        System.out.println("ParentSupper 的構造方法執行了... ");
    }

    final int nextPutIndex(){
        if (position>=capacity){
             throw new RuntimeException("索引異常");
        }
        return position++;
    }
 int i=0;

    final int nextGetIndex(){
        if (i >=position){
           throw new RuntimeException("索引異常");
        }
        return i++;
    }
}

接着是它的實現類, 和IntBuffer 等級一樣

public abstract class Parent extends ParentSupper {
    // 抽象類中的成員變量,必須放在構造方法中
    final int[] arr1;
    int tag;

    // todo 執行父類的構造方法
    Parent(int a, int[] arr1) {
        // 調用父類的構造函數
        super(0,a);
        this.arr1 = arr1;
        this.tag = a;
    }

    // 構造器
    public static Parent allocate(int capacity) {
        return new Child(capacity);
    }

    // 抽象的方法
    public abstract int get();
    public abstract void put(int number);
}

作為抽象類的它,有自己的抽象方法, get put, 同時它里面維護着 核心數組, final不可變類型的, 抽象類中的變量不能單獨存在,必須依附構造函數,於是我們添加它的構造函數

再就是它的實現類:

  class Child extends Parent {
    public Child(int capacity) {
        super(capacity,new int[capacity]);
    }

    // todo 重寫父類的構造方法
    @Override
    public int get() {
        return arr1[nextGetIndex()];
    }

    @Override
    public void put(int num) {
        arr1[nextPutIndex()]=num;
    }
}

注意,Child的類上並沒有public 修飾,意味着他只是可以包內訪問

下面測試:

public class text {
    public static void main(String[] args) {
        // 初始化
        Parent allocate = Parent.allocate(9);
         for (int i=0;i<9;i++){
             allocate.put(i);
         }
        for (int i=0;i<=8;i++){
            System.out.println( allocate.get());
        }
    }
}

運行流程是怎樣的呢?

當我們使用 Parent.allocate(10);創建對象時,底層確實 new Child(int i), 同時把傳遞給Child, 而在Child()相應的構造函數中,接着調用的是spuer()方法,同時把10 傳遞給super也就是Parent,同時實例化了Parent的 數組, 在Supper的構造方法中,把0,和傳遞進來的10 傳遞給了SuperParent, 讓他維護兩個值

思考, 各個部分之間的作用

  • Buffer : 維護着當前數據的 position limit 和 capaciy值,這是數組的下標值,但是Buffer卻沒有數組
  • Buffer的直接實現類,如IntBuffer , 依然是抽象類, 它有數組作為緩存, 數組的維護它交給了它的父類Buffer, 針對數組的初始化,讀寫,他交給了自己的實現類

數組的維護工作是一樣的,所以抽象成Buffer

  • HeapIntBuffer: 它繼承了IntBuffer, 同時把它的父類的數組進行了實例化,並且重寫了父類的get put方法

不同類型的數據,具體的讀寫是有區別的,所有抽象成不同的子類

在回去看,allocate(); 顯然我們得到的是最外層的子類對象,這也就意味着他是最強的那個對象,它擁有父類的數據,並且這個數據的讀寫由它的爺爺替自己把關,這就是Buffer的設計模式

Buffer中常見的API

Buffer的所有子類全部定義了兩套關於get()put()的方法

  • Relative: 相對的讀寫, 相對的讀寫都是從當前的position的位置開始,每次的get/put都會是position的位置發生改變
  • Avsolute: 絕對的讀寫 , 用戶指定索引, 從指定的索引里面get,或者直接往指定的索引里面put

clear()

將buffer置為初始狀態的值,實際上就是讓新讀入buffer中的值,覆蓋掉原來的值

position=0;
limit=capacity;

flip()

反轉buffer

limit = position;
positon=0;

isReadOnly()

判斷是否是只讀的buffer

分片Buffer

//限制前后 准備切片
byteBuffer.position(2);
byteBuffer.limit(6);
// 切片
ByteBuffer slice = byteBuffer.slice();

新得到的buffer和原buffer共享內存空間

類型化的Buffer

ByteBuffer allocate = ByteBuffer.allocate(123);
    allocate.putInt(1);
    allocate.putChar('你');
    allocate.putDouble(123.123123);
    allocate.putShort((short) 2);
    allocate.putLong(3L);

    allocate.flip();

    System.out.println(allocate.getInt());
    System.out.println(allocate.getChar());
    System.out.println(allocate.getDouble());
    System.out.println(allocate.getShort());
    System.out.println(allocate.getLong());

類型化 的put和get 但是呢!!! 怎么存進去的 就得怎么取出來 , 否者會出現亂碼

NIO拷貝文件

        // 獲取輸入流
        FileInputStream fileInputStream = new FileInputStream("123.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("output123.txt");
        // 獲取一個通道,關聯上數據
        FileChannel channel = fileInputStream.getChannel();
        FileChannel outputStreamChannel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        while (true) {
            // todo 每次讀滿一次緩存后, 重新初始化buffer
            byteBuffer.clear();

            // 將數據讀入 緩存
            int read = channel.read(byteBuffer);
            System.out.println("read == "+read);
            if (read == -1) {
                break;
            }
            // 反轉   limit=position   position=0
            byteBuffer.flip();

            // 通過channel 往輸出流中寫入緩存的數據
            outputStreamChannel.write(byteBuffer);
        }
        // 釋放資源
        fileInputStream.close();
        fileInputStream.close();
        System.out.println("結束...");
    }

每次循環一開始,都要重置buffer,否則第一輪讀取結束之后, limit==position 從而channel里面新的數據讀不進去,而 limit==position也不會引發異常,隨后的flip()將buffer反轉,position為0, 使得當前buffer中的數據被循環寫到文件中,成為死循環


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM