前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NIO学习(一)Buffer缓冲区

NIO学习(一)Buffer缓冲区

作者头像
虞大大
发布2020-08-26 17:19:41
4410
发布2020-08-26 17:19:41
举报
文章被收录于专栏:码云大作战码云大作战

一、IO和NIO的区别

IO是传统的面向流的阻塞IO,而NIO是面向缓冲区的非阻塞式IO。在NIO中使用了一个线程来作为Selectors-选择器,来管理多个输入通道,即在使用时只需要将通道注册到选择器中,即可处理输入的通道和选择已经准备好的通道进行管理。

二、Buffer缓冲区与传统IO流

· 传统IO流是对字节数组的流动,单向的输入流和输出流,即面对流的传输。

· NIO本质上也是一个输入输出流。但是NIO中使用了Buffer缓冲区,会将需要传输的数据存放到缓冲区中再结合通道来进行输入与输出,即面对缓冲区的传输。

· NIO的核心也在于缓冲区与通道,简单来说,缓冲区用于存储需要传输的数据,通用用于传输时的连接。

三、学习Buffer缓冲区

上述已提到,Buffer在NIO中主要负责数据的存储。并且根据数据类型的不同,有着不一样的数据缓冲区。

查看Buffer类的引用可以看到,有各种各样的子类Buffer代表着可以存储不同数据的缓冲区,如:ByteBuffer、FloatBuffer、DoubleBuffer等。

· 缓冲区核心属性值

在缓冲区父类Buffer中存在这些核心的参数用于缓冲区取值与放置值的操作。

代码语言:javascript
复制
// Invariants: mark <= position <= limit <= capacity
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;

(1)capacity-容量,代表缓冲区中最大存储数据容量,一旦创建不允许改变。

(2)limit-界限,代表缓冲区中可以操作数据的大小。在源码中可以看到这样一个注释即limit<=capacity,再结合limit的含义,表示只能操作0~limit之间的数据,超过limit后的数据是不能进行操作的。

(3)position-正在操作的位置,代表缓冲区中正在操作的位置,即position<=limit<=capacity。

(4)mark-辅助标记,可以记录当前position的记录。

· 创建缓冲区

在使用缓冲区存储数据之前,需要创建缓存区,可以使用allocate方法来创建缓冲区并分配大小。

即:创建和分配了一个1024字节的Byte缓冲区。

代码语言:javascript
复制
ByteBuffer buffer = ByteBuffer.allocate(1024);

观察此时的capacity、limit、position。如下图所示:

代码语言:javascript
复制
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    System.out.println(buffer.capacity());
    System.out.println(buffer.limit());
    System.out.println(buffer.position());
}102410240

刚创建缓冲区后三者属性关系如下图所示:

· 缓冲区中放入值

可以使用put,来往缓冲区中放入值。

代码语言:javascript
复制
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("abcde".getBytes());
    System.out.println(buffer.capacity());
    System.out.println(buffer.limit());
    System.out.println(buffer.position());
}1024
10245

往缓冲区中放入值后三者属性关系如下图所示:

· 缓冲区中取值

上述的缓冲区可以看到position为5,并且limit和capacity都为1024,代表着还可以写数据从5~limit,此时的缓冲区为写的模式。如果想要从缓冲区中取值,需要调用flip来切换模式。

代码语言:javascript
复制
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("abcde".getBytes());
    //切换模式
    buffer.flip();
    System.out.println(buffer.capacity());
    System.out.println(buffer.limit());
    System.out.println(buffer.position());
}102450

切换模式后的缓冲区三者属性关系如下图所示:

切换为读模式后,可以看到limit和position都切换了位置,表示在读数据模式中可以操作的数组大小为0~5。

使用get方法获取缓冲区中的数据。

代码语言:javascript
复制
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("abcde".getBytes());
    //切换模式
    buffer.flip();
    //读取全部缓存区数据或者读取一个数据
    byte[] bytes = new byte[buffer.limit()];
    buffer.get(bytes);
    //buffer.get();
    System.out.println(new String(bytes, 0, bytes.length));
    System.out.println(buffer.capacity());
    System.out.println(buffer.limit());
    System.out.println(buffer.position());
}abcde
1024
55

读缓冲区数据后的三者属性关系如下图所示:

此时的position和limit已经相等,如果再继续使用get获取数据就会抛出异常。

后续如果还需要从0开始读取,可以使用flip方法或者rewind方法。

· 缓冲区回到最初状态

可以使用buffer.clear来清空缓冲区,回到最初状态。

代码语言:javascript
复制
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("abcde".getBytes());
    //切换模式
    buffer.flip();
    //读取全部缓存区数据或者读取一个数据
    byte[] bytes = new byte[buffer.limit()];
    buffer.get(bytes);
    buffer.clear();
    System.out.println(buffer.capacity());
    System.out.println(buffer.limit());
    System.out.println(buffer.position());
    System.out.println((char) buffer.get());
}102410240
a

上述运行结果可以看到,虽然缓冲区回到了最初状态,但是缓冲区中还存在着之前存放的值,并且可以被读取到。

因此在不知道之前缓冲区存放多少数据的情况,即不知道position的情况时,可以使用mark属性,记录一个position的快照,并且快速回到position快照的位置。

· 缓冲区mark标记

mark标记时,主要使用mark方法来标记position当时的快照位置,然后使用reset方法使position回到mark标记的位置。

代码语言:javascript
复制
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("abcde".getBytes());
    //切换模式
    buffer.flip();
    //读取全部缓冲区数据或者读取一个数据
    buffer.mark();
    byte[] bytes = new byte[buffer.limit()];
    buffer.get(bytes);
    System.out.println(buffer.position());
    buffer.reset();
    System.out.println(buffer.position());
}5
0

上述代码中的运行结果显示,在读取时先标记当时position的位置,mark此时为0。进行数据读取后,position指向了5,最后使用reset使position回到了快照的位置0。

操作mark标记前后四者属性关系如下图:

四、非直接缓冲区

文章上述所示的使用buffer.allocate方法创建和分配的缓冲区都为非直接缓冲区,他是将缓冲区建立在JVM内存中。

非直接缓冲区工作图如下所示:

读操作时都是会将数据copy到JVM中与应用程序交互或者写操作时会将数据copy到JVM中与cpu内存、磁盘进行交互。

非直接缓冲区的IO操作时,都会将数据copy到JVM中与磁盘或程序进行交互,如果认为不需要有copy这一步操作的可以使用直接缓冲区来进行IO操作。

观察allocate源码,操作的是堆内存:

五、直接缓冲区

直接缓冲区内存工作图如下所示:

程序的IO操作可以直接与物理内存进行操作,不需要有copy的一步来减少数据复制的开销,提高IO效率。这里的物理内存是申请的一份空间,主要是作为磁盘和程序数据传输的一个中间媒介。

直接缓冲区存在的问题:

(1)JVM外空间分配比JVM空间分配更加耗时、有着更多的消耗。

(2)数据写入物理内存缓冲区中,物理内存并不会立即向磁盘同步这些数据,而写入磁盘只能由操作系统决定什么时候同步到磁盘中,并且应该程序无法干涉这些数据。

对于一些大数据,由于在JVM中这些大对象会因为内存空间不足而引起full gc导致系统卡顿时,对这些大数据的操作可以使用直接缓冲区操作。

· 如何创建直接缓冲区

可以通过allocateDirect方法来创建直接缓冲区。

代码语言:javascript
复制
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    System.out.println(buffer.capacity());
    System.out.println(buffer.limit());
    System.out.println(buffer.position());
}

底层代码中调用的是内存页。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码云大作战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
大数据
全栈大数据产品,面向海量数据场景,帮助您 “智理无数,心中有数”!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档