介绍

对于ByteBuffer,其主要有五个属性:mark,position,limit,capacity和array。这五个属性的作用如下:
*
* mark:记录了当前所标记的索引下标;
* position:对于写入模式,表示当前可写入数据的下标,对于读取模式,表示接下来可以读取的数据的下标;
* limit:对于写入模式,表示当前可以写入的数组大小,默认为数组的最大长度,对于读取模式,表示当前最多可以读取的数据的位置下标;
* capacity:表示当前数组的容量大小;
* array:保存了当前写入的数据。
* 这几个数据中,除了array是用于保存数据的以外,这里最终的主要是position,limit和capacity三个属性,因为对于写入和读取模式,这三个属性的表示的含义大不一样。

package com.yangmin.study.io.nio.buffer;

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

import static jdk.nashorn.internal.objects.Global.print;

/**
 * @author yangmin
 * @version 1.0
 * @description: 通道
 * @date 2022/5/9 18:52
 */
public class bufferDemo {

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


    /**
     * 对于ByteBuffer,其主要有五个属性:mark,position,limit,capacity和array。这五个属性的作用如下:
     *
     * mark:记录了当前所标记的索引下标;
     * position:对于写入模式,表示当前可写入数据的下标,对于读取模式,表示接下来可以读取的数据的下标;
     * limit:对于写入模式,表示当前可以写入的数组大小,默认为数组的最大长度,对于读取模式,表示当前最多可以读取的数据的位置下标;
     * capacity:表示当前数组的容量大小;
     * array:保存了当前写入的数据。
     * 这几个数据中,除了array是用于保存数据的以外,这里最终的主要是position,limit和capacity三个属性,因为对于写入和读取模式,这三个属性的表示的含义大不一样。
     */


    /**
     * 写入模式: limit指向的始终是当前可最多写入的数组索引下标,position指向的则是下一个可以写入的数据的索引位置,而capacity则始终不会变化,即为数组大小。
     * 初始状态:     -             -             -             -             -             -
     * position                                                              limit/capacity
     * 写入3个字节:  1             2             3             -             -             -
     * position                                   limit/capacity
     * 切换为读模式: limit=position,position=0;当切换为读取模式之后,limit则指向了最后一个可读取数据的下一个位置,表示最多可读取的数据;
     * position则指向了数组的初始位置,表示下一个可读取的数据的位置;
     * capacity还是表示数组的最大容量。
     * 这里当我们一个一个读取数据的时候,position就会依次往下切换,当期与limit重合时,就表示当前ByteBuffer中已没有可读取的数据了。
     * filp()   :  1             2             3             -             -             -
     * position                     limit                                     capacity
     */
    public void testBuffer() {

        /**
         * 我们首先创建一个最大容量为6的ByteBuffer,此时position为0,即初始状态,而limit为6,与最大容量一致。
         * 当我们往buffer中写入三个字节数据之后,此时的position为3,limit还是6,表示还可以继续往buffer中写入三个数据。
         * 当我们切换为读取模式之后,需要注意,此时buffer中写入了三个字节的数据,也就是说只有三个字节的数据可供读取,
         * 因而切换为读取模式之后,position指向了0,表示下一个可供读取的数据位置,而limit为3,即为之前写入的数据数量,而capacity始终为6,表示buffer的最大大小。
         */
        // 初始化一个大小为6的ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(6);
        print(buffer);  // 初始状态:position: 0, limit: 6, capacity: 6

        // 往buffer中写入3个字节的数据
        buffer.put((byte) 1);
        buffer.put((byte) 2);
        buffer.put((byte) 3);
        print(buffer);  // 写入之后的状态:position: 3, limit: 6, capacity: 6

        System.out.println("************** after flip **************");
        buffer.flip();
        print(buffer);  // 切换为读取模式之后的状态:position: 0, limit: 3, capacity: 6

        buffer.get();
        buffer.get();
        print(buffer);  // 读取两个数据之后的状态:position: 2, limit: 3, capacity: 6
    }


    /**
     * mark(), 把现在position的位置设置为后续position重置的回滚位置
     * 在前面我们讲过,ByteBuffer中海油一个mark属性,这个属性是一个标识的作用,即记录当前position的位置,
     * 在后续如果调用reset()或者flip()方法时,ByteBuffer的position就会被重置到mark所记录的位置。
     * 因而对于写入模式,在mark()并reset()后,将会回到mark记录的可以写入数据的位置;
     * 对于读取模式,在mark()并reset()后,将会回到mark记录的可以读取的数据的位置。
     * 如下是mark()方法分别演示写入和读取数据的示例:
     */
    public void testMark() {
        ByteBuffer buffer = ByteBuffer.allocate(6);
        // position: 0, limit: 6, capacity: 6

        buffer.put((byte) 1);
        buffer.put((byte) 2);
        buffer.put((byte) 3);
        // position: 3, limit: 6, capacity: 6

        buffer.mark();  // 写入三个字节数据后进行标记
        // position: 3, limit: 6, capacity: 6

        buffer.put((byte) 4); // 再次写入一个字节数据
        // position: 4, limit: 6, capacity: 6

        buffer.reset(); // 对buffer进行重置,此时将恢复到Mark时的状态
        // position: 3, limit: 6, capacity: 6

        buffer.flip();  // 切换为读取模式,此时有三个数据可供读取
        // position: 0, limit: 3, capacity: 6

        buffer.get(); // 读取一个字节数据之后进行标记
        buffer.mark();
        // position: 1, limit: 3, capacity: 6

        buffer.get(); // 继续读取一个字节数据
        // position: 2, limit: 3, capacity: 6

        buffer.reset(); // 进行重置之后,将会恢复到mark的状态
        // position: 1, limit: 3, capacity: 6
    }


    /**
     *     reset()和rewind()都是对buffer中的position进行操作,reset()是把position重置到mark()标记的位置,rewind()是把position设置为0,mark设置-1(取消标记)
     *     public final Buffer reset() {
     *         int m = mark;
     *         if (m < 0)
     *             throw new InvalidMarkException();
     *         position = m;
     *         return this;
     *     }
     *
     *     public final Buffer rewind() {
     *         position = 0;
     *         mark = -1;
     *         return this;
     *     }
     */


    /**
     * 对于rewind()方法,它的主要作用在于将当前的position重置为0,并且mark重置为-1,而且无论mark是否进行过标记。
     * 很明显,rewind()和reset()方法都是进行重置的,但是reset()方法则是会优先重置到mark标记的位置。
     * 同理,对于写入模式,rewind()方法会重置为初始写入状态,对于读取模式,rewind()则会重置为初始读取模式,其不会对limit属性有任何影响。
     * 如下是rewind()方法的一个使用示例:
     */
    public void testRewind() {
        ByteBuffer buffer = ByteBuffer.allocate(6);
        // position: 0, limit: 6, capacity: 6

        buffer.put((byte) 1);
        buffer.put((byte) 2);
        buffer.put((byte) 3);
        // position: 3, limit: 6, capacity: 6

        buffer.rewind();  // 调用rewind()方法之后,buffer状态将会重置
        // position: 0, limit: 6, capacity: 6
    }


    static void bufferTest() {
        FileChannel fiChannel = null;
        FileChannel foChannel = null;
        try {
            FileInputStream fi = new FileInputStream("src/main/resources/read.text");
            FileOutputStream fo = new FileOutputStream("src/main/resources/write.text");
            fiChannel = fi.getChannel();
            foChannel = fo.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(4);
            /**
             * fiChannel.read(buffer):把fiChannel中的数据写入到buffer中,此时buffer处于初始状态(写模式)
             */
            while ((fiChannel.read(buffer)) != -1) {
                //切换为读模式
                buffer.flip();
                if (buffer.hasRemaining()) {
                    /**
                     * foChannel把buffer中的数据读取到foChannel中,此时buffer处于读模式
                     */
                    foChannel.write(buffer);
                }
                //清空buffer,此时可以向buffer中写入数据,position=0,limit=capacity
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fiChannel != null) {
                try {
                    fiChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (foChannel != null) {
                try {
                    foChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}