NIO Buffer中各重要状态属性的含义与关系以及源码解析(二)

本文深入探讨Java NIO的ByteBuffer特性,包括类型化的put与get方法、slice方法的应用、只读缓冲区的功能及其限制,以及直接缓冲区的工作原理。通过实例说明了如何高效地使用这些特性。
ByteBuffer 不仅仅存放Byte类型 还能存放其他类型,并且必须按照对应的顺序解析 否则将报错。(类似于自定义协议,按照规则去解析)
/**
 * ByteBuffer类型化的put 与 get
 */
public class NioTest5 {
    public static void main(String[] args) {

        ByteBuffer byteBuffer = ByteBuffer.allocate(10000);

        byteBuffer.putInt(15);
        byteBuffer.putLong(50000000L);
        byteBuffer.putChar('哈');
        byteBuffer.flip();

        System.out.println(byteBuffer.getInt());
        System.out.println(byteBuffer.getLong());
        System.out.println(byteBuffer.getChar());
    }
}

 打印结果:

15
50000000
哈



/**
 * Creates a new byte buffer whose content is a shared subsequence of
 * this buffer's content.
 *
 * <p> The content of the new buffer will start at this buffer's current
 * position.  Changes to this buffer's content will be visible in the new
 * buffer, and vice versa; the two buffers' position, limit, and mark
 * values will be independent.
 *
 * <p> The new buffer's position will be zero, its capacity and its limit
 * will be the number of bytes remaining in this buffer, and its mark
 * will be undefined.  The new buffer will be direct if, and only if, this
 * buffer is direct, and it will be read-only if, and only if, this buffer
 * is read-only.  </p>
 *
 * @return  The new byte buffer
 */
public abstract ByteBuffer slice();

通过slice 生成的新的buffer 与老的buffer 共享buffer内容

 

package nio;

import java.nio.ByteBuffer;

/**
 * Slice Buffer
 */
public class NioTest6
{
    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);

        for (int i = 0; i < byteBuffer.capacity(); i++) {
            byteBuffer.put((byte)i);
        }

        byteBuffer.position(2);
        byteBuffer.limit(6);

        ByteBuffer sliceBuffer = byteBuffer.slice();

        for (int i = 0; i <sliceBuffer.capacity() ; i++) {
           byte b = sliceBuffer.get(i);
            b *=2;
            sliceBuffer.put(i,b);
        }
        byteBuffer.position(0);
        byteBuffer.limit(byteBuffer.capacity());

        while (byteBuffer.hasRemaining()){
            System.out.println(byteBuffer.get());
        }

    }
}

打印结果:


0
1
4
6
8
10
6
7
8
9


下面在看只读buffer,为什么使用只读buffer 因为与客户端交换数据时,防止客户端修改buffer,为调用端提供数据的读取

/**
 * 只读buffer,我们可以随时将一个普通buffer调用asReadOnlyBuffer 方法返回一个只读buffer,但不能将一个只读Buffer转换成读写buffer

 * @author lm
 *
 */
public class NioTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		ByteBuffer buffer = ByteBuffer.allocate(10);
		
		for (int i = 0; i < buffer.capacity(); i++) {
			buffer.put((byte)i);
		}

		ByteBuffer readOnlyBuffer =buffer.asReadOnlyBuffer();
		
		System.out.println(readOnlyBuffer.getClass());
		
		readOnlyBuffer.position(0);
		
		readOnlyBuffer.put((byte)2);
	}

}
打印结果:
class java.nio.HeapByteBufferR
Exception in thread "main" java.nio.ReadOnlyBufferException
	at java.nio.HeapByteBufferR.put(HeapByteBufferR.java:166)
	at com.nio.NioTest.main(NioTest.java:24)

我们分析一下 查看ByteBuffer源码  ByteBuffer。allocate 方法返回的是HeapByteBuffer 


 

 /**
     * Allocates a new byte buffer.
     *
     * <p> The new buffer's position will be zero, its limit will be its
     * capacity, its mark will be undefined, and each of its elements will be
     * initialized to zero.  It will have a {@link #array backing array},
     * and its {@link #arrayOffset array offset} will be zero.
     *
     * @param  capacity
     *         The new buffer's capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }  


asReadOnlyBuffer 是一个抽象方法 

解释一下注释:创建一个新的只读buffer 与之前的buffer共享 它的内容 新的只读buffer内容是不能修改的,并且两个buffer的limti,position,mark都是独立的 

  
/**
     * Creates a new, read-only byte buffer that shares this buffer's
     * content.
     *
     * <p> The content of the new buffer will be that of this buffer.  Changes
     * to this buffer's content will be visible in the new buffer; the new
     * buffer itself, however, will be read-only and will not allow the shared
     * content to be modified.  The two buffers' position, limit, and mark
     * values will be independent.
     *
     * <p> The new buffer's capacity, limit, position, and mark values will be
     * identical to those of this buffer.
     *
     * <p> If this buffer is itself read-only then this method behaves in
     * exactly the same way as the {@link #duplicate duplicate} method.  </p>
     *
     * @return  The new, read-only byte buffer
     */
    public abstract ByteBuffer asReadOnlyBuffer();

再看HeapByteBuffer 的asReadOnlyBuffer 方法 返回的是 HeapByteBufferR 它是一个只读R表示只读


    public ByteBuffer asReadOnlyBuffer() {


        return new HeapByteBufferR(hb,
                                     this.markValue(),
                                     this.position(),
                                     this.limit(),
                                     this.capacity(),
                                     offset);






    }

再看 HeapByteBufferR  put方法 因为是只读的所以直接抛异常 就可以解释 上面的代码为什么抛异常

 public ByteBuffer putChar(int i, char x) {




        throw new ReadOnlyBufferException();

    }


  //  wrap方法会包裹一个数组: 一般这种用法不会先初始化缓存对象的长度,因为没有意义,最后还会被wrap所包裹的数组覆盖掉。   
    //  并且wrap方法修改缓冲区对象的时候,数组本身也会跟着发生变化。                       
    int[] arr = new int[]{1,2,5};  
    IntBuffer buf1 = IntBuffer.wrap(arr);  
    System.out.println(buf1);  
      
    IntBuffer buf2 = IntBuffer.wrap(arr, 0 , 2);//截取1-2下标  
    //这样使用表示容量为数组arr的长度,但是可操作的元素只有实际进入缓存区的元素长度  
    System.out.println(buf2);  

java.nio.HeapIntBuffer[pos=0 lim=3 cap=3]  
java.nio.HeapIntBuffer[pos=0 lim=2 cap=3] 

下面我们看一下直接缓冲buffer

public class NioTest2 {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		FileInputStream fielInput = new FileInputStream("input.txt");
		
		FileOutputStream fielOutput = new FileOutputStream("output.txt");

		
		FileChannel inputChannel =fielInput.getChannel();
		FileChannel outChannel =fielOutput.getChannel();
		//直接缓冲
		ByteBuffer buffer = ByteBuffer.allocateDirect(10);
		
		while (true) {
			buffer.clear();
			
			int read =inputChannel.read(buffer);
			
			System.out.println("read:"+read);
			
			if(-1==read){
				break;
			}
			
			buffer.flip();
			
			outChannel.write(buffer);
			
			
		}
		inputChannel.close();
		outChannel.close();
		
	}

}

我们看一下 ByteBuffer.allocateDirect(10); 源码  

注释:分配一个直接缓冲btyebuffer 其他的和之前一样 不进行解释le


 /**
     * Allocates a new direct byte buffer.
     *
     * <p> The new buffer's position will be zero, its limit will be its
     * capacity, its mark will be undefined, and each of its elements will be
     * initialized to zero.  Whether or not it has a
     * {@link #hasArray backing array} is unspecified.
     *
     * @param  capacity
     *         The new buffer's capacity, in bytes
     *
     * @return  The new byte buffer
     *
     * @throws  IllegalArgumentException
     *          If the <tt>capacity</tt> is a negative integer
     */
    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }


再看DirectByteBuffer 里面 有很多sun公司 提供的未公开的方法


    // Primary constructor
    //
    DirectByteBuffer(int cap) {                   // package-private

        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            base = unsafe.allocateMemory(size); // 分配内存 
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;



    }

之前的HeapByteBuffer  都是由java 来维护底层数组 来操作数据的 都是jvm来可以操作也就是堆内存,

为什么使用堆外内存 由于DirectByteBuffer 通过父类的address  是一个直接内存 来标记堆外内存数据的地址

进行IO操作的时候 会直接在堆外内存中(是有操作系统来管理的) 读取数据 提高速度

 // Used only by direct buffers
 // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;



而我们使用HeapByteBuffer  来进行IO操作的时候 需要把JAVA内存中的数据 拷贝到 操作系统的内存中进行操作,相比DirectByteBuffer 多了一步数据拷贝过程。

其实我们的操作系统可以直接操作JAVA内存模型中的数据,但是JVM中有GC回收,会导致数据问题等,所有为了快速进行零拷贝,才把数据拷贝到堆外的操作系统中。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值