关于Buffer读写问题(包含Socket中的读写)

这里演示的是CharBuffer,ByteBuffer等同理



  1. 首先讲Buffer的存储方式:
    在这里插入图片描述
    buffer中有四个参数:
    1) mark:作position定位用,后面会讲使用;
    2)position:缓冲内数据定位,起始为0;
    3)limit:有效数据位界限,起始为capacity:
    4)capacity:缓冲总大小界限。
    在这里插入图片描述
    【使用原理】
    a) 初始化,给定总容量,position=0, limit=capacity
    b) 当使用put方法存入数据是,通过position来记录存储的容量变化,position不断后移,直到存储结束(写完成)
    c)写完成需要调用flip方法刷新,limit=position,position=0
    保障limit记录的是可读写区域的大小,position已读部分重置为空
    d) 读数据直到读完成,需要调用clear方法,position=0, limit=capacity
import java.nio.CharBuffer;

public class Buffer {
    public static void main(String[] args) {
        CharBuffer buffer = CharBuffer.allocate(8);
        System.out.println("--------初始化---------");
        System.out.println("这是capacity:"+buffer.capacity());
        System.out.println("这是limit:"+buffer.limit());
        System.out.println("这是position:"+buffer.position());

        buffer.put('h');
        buffer.put('e');
        System.out.println("--------put后---------");
        System.out.println("这是capacity:"+buffer.capacity());
        System.out.println("这是limit:"+buffer.limit());
        System.out.println("这是position:"+buffer.position());

        buffer.flip();
        System.out.println("--------flip后---------");
        System.out.println("这是capacity:"+buffer.capacity());
        System.out.println("这是limit:"+buffer.limit());
        System.out.println("这是position:"+buffer.position());


        System.out.println("--------get1后---------");
        System.out.println(buffer.get());
        System.out.println("这是capacity:"+buffer.capacity());
        System.out.println("这是limit:"+buffer.limit());
        System.out.println("这是position:"+buffer.position());

    }
}

结果如下:

在这里插入图片描述

接下来说mark的用法:

        //此时标记获取一个char后position位置
        buffer.mark();

        System.out.println("--------get2后---------");
        System.out.println(buffer.get());
        System.out.println("这是capacity:"+buffer.capacity());
        System.out.println("这是limit:"+buffer.limit());
        System.out.println("这是position:"+buffer.position());

        //重置position为上一标记点
        buffer.reset();
        System.out.println("--------reset后---------");
        System.out.println("这是capacity:"+buffer.capacity());
        System.out.println("这是limit:"+buffer.limit());
        System.out.println("这是position:"+buffer.position());

reset后position位置重置为上一mark标记点
原理是,调用mark方法时,将position值传给mark,调用reset时再将mark值传给position

在这里插入图片描述
这是mark和reset的源码:

    public final Buffer mark() {
        mark = position;
        return this;
    }

    /**
     * Resets this buffer's position to the previously-marked position.
     *
     * <p> Invoking this method neither changes nor discards the mark's
     * value. </p>
     *
     * @return  This buffer
     *
     * @throws  InvalidMarkException
     *          If the mark has not been set
     */
    public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

注: put值后一定要flip刷新,否则会读取到null值或者文件格式不支持;

clear方法旨在清除原有数据标记相当于再次初始化

        buffer.clear();
        System.out.println("--------clear后---------");
        System.out.println("这是capacity:"+buffer.capacity());
        System.out.println("这是limit:"+buffer.limit());
        System.out.println("这是position:"+buffer.position());

        System.out.println(buffer.get());

在这里插入图片描述

Socket的write和read操作对于Buffer的影响

对于buffer来说,Socket的write操作和read操作都是对buffer进行put/get操作,如:

 ByteBuffer write = ByteBuffer.allocate(128);
        write.put("hello client , i am server!".getBytes());
        System.out.println("------------- server flip之前");
        System.out.println("position:"+write.position());
        System.out.println("limit:"+write.limit());
        System.out.println("capacity:"+write.capacity());
        write.flip();
        System.out.println("------------- server flip之后");
        System.out.println("position:"+write.position());
        System.out.println("limit:"+write.limit());
        System.out.println("capacity:"+write.capacity());
        socket.write(write);
        System.out.println("------------ server -write之后-------------");
        System.out.println("position:"+write.position());
        System.out.println("limit:"+write.limit());
        System.out.println("capacity:"+write.capacity());

在这里插入图片描述
这里是向Socket中写入ByteBuffer,我们先向buffer中put进了数据,之后将buffer flip刷新,切换到预备读状态,socket写操作之后,position变为了27,为有效数据的长度,limit未变。

 ByteBuffer read = ByteBuffer.allocate(128);
        System.out.println("-------------read之前");
        System.out.println("position:"+read.position());
        System.out.println("limit:"+read.limit());
        System.out.println("capacity:"+read.capacity());
        socketChannel.read(read);

        System.out.println("-------------read之后---flip之前");
        System.out.println("position:"+read.position());
        System.out.println("limit:"+read.limit());
        System.out.println("capacity:"+read.capacity());

        read.flip();

        System.out.println("-------------read之后---flip之后");
        System.out.println("position:"+read.position());
        System.out.println("limit:"+read.limit());
        System.out.println("capacity:"+read.capacity());

在这里插入图片描述

这里是Socket中数据读入ByteBuffer,socket读操作之后,position变为了27,为有效数据的长度,limit未变。
由此可见,Socket进行write或者read时都是对buffer进行了put/get操作
write底层源码如下:

    private static int writeFromNativeBuffer(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
    //这块是write进的position值,因为经过了flip,所以为0
        int var5 = var1.position();
        //这块是limit值,27
        int var6 = var1.limit();

        assert var5 <= var6;

        int var7 = var5 <= var6 ? var6 - var5 : 0;
        boolean var8 = false;
        if (var7 == 0) {
            return 0;
        } else {
            int var9;
            //var2是 是否使用了reset,否为-1.
            if (var2 != -1L) {
                var9 = var4.pwrite(var0, ((DirectBuffer)var1).address() + (long)var5, var7, var2);
            } else {
            //这里调用底层c进行写入,sun.nio.ch.NativeDispatcher#write,真正的写入数据,第一个参数是内存中的偏移量,第二个参数是写入数据的大小
                var9 = var4.write(var0, ((DirectBuffer)var1).address() + (long)var5, var7);
            }

            if (var9 > 0) {
                var1.position(var5 + var9);
            }

            return var9;
        }
    }

从上面的代码看,最终就是调用到了sun.nio.ch.NativeDispatcher#write方法,在调用flip之后var5是0,var7是4,此时就是在堆外分配的内存地址address+0的基础上偏移4个字节,正好是"hehe"这个字符串,而在调用flip之前,var5是4, var7是1020,调用write方法是是在address+4的基础上再偏移1020个字节,结合上面put的解释,这段内存空间根本就没有数据。这就解释了为什么调用fileChannel.write之前要掉用flip方法。
参考文章:https://zhuanlan.zhihu.com/p/439380628?utm_id=0

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值