文档章节

NIO学习系列:缓冲区内部实现机制

LittlerBeans
 LittlerBeans
发布于 2015/02/26 15:12
字数 2623
阅读 122
收藏 11
点赞 0
评论 0

接上一篇NIO学习系列:核心概念及基本读写 ,本文继续探讨和学习缓冲区的内部实现机制。

5.    缓冲区内部实现 
   从上面对NIO的学习中,我们知道每一个缓冲区都有复杂的内部统计机制,它会跟踪已经读了多少数据以及还有多少空间可以容纳更多的数据,以便我们对缓冲区的操作。在本节我们就将学习NIO的两个重要的缓冲区组件:状态变量和访问方法。虽然NIO的内部统计机制初看起来可能很复杂,但是您很快就会看到大部分的实际工作都已经替您完成了。您只需像平时使用字节数组和索引变量一样进行操作即可。


   1)    状态变量: 
   状态变量是前一节中提到的"内部统计机制"的关键。 每一个读/写操作都会改变缓冲区的状态。通过记录和跟踪这些变化,缓冲区就可能够内部地管理自己的资源。
   每一种Java基本类型的缓冲区都是抽象类Buffer的子类,从Buffer的源代码中可以发现,它定义了三个私有属性:

Java代码  收藏代码

  1. private int position = 0;  

  2. private int limit;  

  3. private int capacity;  

   实际上,这三个属性值可以指定缓冲区在任意时刻的状态和它所包含的数据。
   我们知道,每一个基本类型的缓冲区底层实际上就是一个该类型的数组。如在ByteBuffer中,有:

Java代码  收藏代码

  1. final byte[] hb;  

   在从通道读取时,所读取的数据将放被到底层的数组中;同理,向通道中写入时,将从底层数组中将数据写入通道。下面我们来具体介绍这三个变量的作用:


   a)    position 
   position变量跟踪了向缓冲区中写入了多少数据或者从缓冲区中读取了多少数据。
   更确切的说,当您从通道中读取数据到缓冲区中时,它指示了下一个数据将放到数组的哪一个元素中。比如,如果您从通道中读三个字节到缓冲区中,那么缓冲区的position将会设置为3,指向数组中第4个元素。反之,当您从缓冲区中获取数据进行写通道时,它指示了下一个数据来自数组的哪一个元素。比如,当您从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。


   b)    limit 
   limit变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
   position总是小于或者等于limit。

   c)    capacity 
   capacity变量表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小—或者至少是指定了准许我们使用的底层数组的容量。 
   limit总是小于或者等于capacity。

 

   d)    举例说明:

   下面我们就以数据从一个输入通道拷贝到一个输出通道为例,来详细分析每一个变量,并说明它们是如何协同工作的:


   初始变量: 
   我们首先观察一个新创建的缓冲区,以ByteBuffer为例,假设缓冲区的大小为8个字节,ByteBuffer初始状态如下:

NIO缓冲区内部实现机制
   回想一下 ,limit决不能大于capacity,此例中这两个值都被设置为8。我们通过将它们指向数组的尾部之后(第8个槽位)来说明这点。
NIO缓冲区内部实现机制
   我们再将position设置为0。表示如果我们读一些数据到缓冲区中,那么下一个读取的数据就进入 slot 0。如果我们从缓冲区写一些数据,从缓冲区读取的下一个字节就来自slot 0。position设置如下所示:
NIO缓冲区内部实现机制
   由于缓冲区的最大数据容量capacity不会改变,所以我们在下面的讨论中可以忽略它。

   第一次读取: 
   现在我们可以开始在新创建的缓冲区上进行读/写操作了。首先从输入通道中读一些数据到缓冲区中。第一次读取得到三个字节。它们被放到数组中从position开始的位置,这时position被设置为0。读完之后,position就增加到了3,如下所示,limit没有改变。
NIO缓冲区内部实现机制

   第二次读取: 
   在第二次读取时,我们从输入通道读取另外两个字节到缓冲区中。这两个字节储存在由position所指定的位置上, position因而增加2,limit没有改变。
NIO缓冲区内部实现机制

   flip: 
   现在我们要将数据写到输出通道中。在这之前,我们必须调用flip()方法。 其源代码如下:

Java代码  收藏代码

  1. public final Buffer flip() {  

  2.     limit = position;  

  3.     position = 0;  

  4.     mark = -1;  

  5.     return this;  

  6. }  

   这个方法做两件非常重要的事:
   i  它将limit设置为当前position。
   ii 它将position设置为0。

   上一个图显示了在flip之前缓冲区的情况。下面是在flip之后的缓冲区:

NIO缓冲区内部实现机制

   我们现在可以将数据从缓冲区写入通道了。position被设置为0,这意味着我们得到的下一个字节是第一个字节。limit已被设置为原来的position,这意味着它包括以前读到的所有字节,并且一个字节也不多。

   第一次写入: 
   在第一次写入时,我们从缓冲区中取四个字节并将它们 写入输出通道。这使得position增加到4,而limit不变,如下所示:

NIO缓冲区内部实现机制


   第二次写入: 
   我们只剩下一个字节可写了。limit在我们调用flip()时被设置为5,并且position不能超过limit。 所以最后一次写入操作从缓冲区取出一个字节并将它写入输出通道。这使得position增加到5,并保持limit不变,如下所示:
NIO缓冲区内部实现机制

   clear: 
   最后一步是调用缓冲区的clear()方法。这个方法重设缓冲区以便接收更多的字节。其源代码如下:

Java代码  收藏代码

  1. public final Buffer clear() {  

  2.     osition = 0;  

  3.     limit = capacity;  

  4.     mark = -1;  

  5.     return this;  

  6. }  

   clear做两种非常重要的事情: 
   i 它将limit设置为与capacity相同。 
   ii 它设置position为0。 
   下图显示了在调用clear()后缓冲区的状态, 此时缓冲区现在可以接收新的数据了。

NIO缓冲区内部实现机制

 

   2)    访问方法: 
   到目前为止,我们只是使用缓冲区将数据从一个通道转移到另一个通道。然而,程序经常需要直接处理数据。例如,您可能需要将用户数据保存到磁盘。在这种情况下,您必须将这些数据直接放入缓冲区,然后用通道将缓冲区写入磁盘。 或者,您可能想要从磁盘读取用户数据。在这种情况下,您要将数据从通道读到缓冲区中,然后检查缓冲区中的数据。 
   实际上,每一个基本类型的缓冲区都为我们提供了直接访问缓冲区中数据的方法,我们以ByteBuffer为例,分析如何使用其提供的get()和put()方法直接访问缓冲区中的数据。

 

   a)    get() 
   ByteBuffer类中有四个get()方法:

Java代码  收藏代码

  1. byte get();  

  2. ByteBuffer get( byte dst[] );  

  3. ByteBuffer get( byte dst[], int offset, int length );  

  4. byte get( int index );   

   第一个方法获取单个字节。第二和第三个方法将一组字节读到一个数组中。第四个方法从缓冲区中的特定位置获取字节。那些返回ByteBuffer的方法只是返回调用它们的缓冲区的this值。 
   此外,我们认为前三个get()方法是相对的,而最后一个方法是绝对的。“相对”意味着get()操作服从limit和position值,更明确地说,字节是从当前position读取的,而position在get之后会增加。另一方面,一个“绝对”方法会忽略limit和position值,也不会影响它们。事实上,它完全绕过了缓冲区的统计方法。 
   上面列出的方法对应于ByteBuffer类。其他类有等价的get()方法,这些方法除了不是处理字节外,其它方面是是完全一样的,它们处理的是与该缓冲区类相适应的类型。

   b)    put() 
   ByteBuffer类中有五个put()方法:

Java代码  收藏代码

  1. ByteBuffer put( byte b );  

  2. ByteBuffer put( byte src[] );  

  3. ByteBuffer put( byte src[], int offset, int length );  

  4. ByteBuffer put( ByteBuffer src );  

  5. ByteBuffer put( int index, byte b );   

   第一个方法 写入(put)单个字节。第二和第三个方法写入来自一个数组的一组字节。第四个方法将数据从一个给定的源ByteBuffer写入这个ByteBuffer。第五个方法将字节写入缓冲区中特定的 位置 。那些返回ByteBuffer的方法只是返回调用它们的缓冲区的this值。 
   与get()方法一样,我们将把put()方法划分为“相对”或者“绝对”的。前四个方法是相对的,而第五个方法是绝对的。 
   上面显示的方法对应于ByteBuffer类。其他类有等价的put()方法,这些方法除了不是处理字节之外,其它方面是完全一样的。它们处理的是与该缓冲区类相适应的类型。 

   c)    类型化的 get() 和 put() 方法 
   除了前些小节中描述的get()和put()方法, ByteBuffer还有用于读写不同类型的值的其他方法,如下所示: 
   getByte() 
   getChar() 
   getShort() 
   getInt() 
   getLong() 
   getFloat() 
   getDouble() 
   putByte() 
   putChar() 
   putShort() 
   putInt() 
   putLong() 
   putFloat() 
   putDouble() 
   事实上,这其中的每个方法都有两种类型:一种是相对的,另一种是绝对的。它们对于读取格式化的二进制数据(如图像文件的头部)很有用。

 

   3)    如何使用? 
   下面的内部循环概括了使用缓冲区将数据从输入通道拷贝到输出通道的过程。

Java代码  收藏代码

  1. while (true) {  

  2.      buffer.clear();  

  3.      int r = fcin.read( buffer );  

  4.   

  5.      if (r==-1) {  

  6.        break;  

  7.      }  

  8.   

  9.      buffer.flip();  

  10.      fcout.write( buffer );  

  11. }  

   read()和write()调用得到了极大的简化,因为许多工作细节都由缓冲区完成了。clear()和flip()方法用于让缓冲区在读和写之间切换。


本文转载自:http://zhangshixi.iteye.com/blog/681704

共有 人打赏支持
LittlerBeans
粉丝 10
博文 26
码字总数 14592
作品 0
杭州
高级程序员
NIO学习系列:核心概念及基本读写

引言 I/O流或者输入/输出流指的是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。新的输入/输出(NIO)库是在JDK 1.4中引入的。NIO弥补了原来的I/O的不足,它在标准Java代码中提...

LittlerBeans ⋅ 2015/02/15 ⋅ 1

Netty系列之Netty高性能之道

背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用。相比于传统基于J...

宇智波带土 ⋅ 2014/06/17 ⋅ 1

NIO入门教程

关于本教程 新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。通过定义包含数据的类,以及通过以块的形式处理...

磨砂轮 ⋅ 2016/09/22 ⋅ 0

Java NIO系列教程(十二) Java NIO与IO

原文地址:http://tutorials.jenkov.com/java-nio/nio-vs-io.html 作者:Jakob Jenkov 当学习了Java NIO和IO的API后,一个问题马上涌入脑海: 我应该何时使用IO,何时使用NIO呢?在本文中,我...

_夏天的风_ ⋅ 2013/07/11 ⋅ 0

Java NIO核心概念及基本读写

面向流的I/O系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理...

BravoZu ⋅ 2014/02/19 ⋅ 1

Netty系列之Netty高可靠性原理分析

背景 1.1. 宕机的代价 1.1.1. 电信行业 毕马威国际(KPMG International)在对46个国家的74家运营商进行调查后发现,全球通信行业每年的收益流失约为400亿美元,占总收入的1%-3%。导致收益流失...

tantexian ⋅ 2016/07/22 ⋅ 0

Netty高性能之道

背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用。相比于传统基于J...

HenrySun ⋅ 2016/04/19 ⋅ 0

Java NIO 学习:概述

Java 在 I/O 领域一直处于劣势,这种情况直到 J2SE SDK 发布了 1.4 版以后才有了改观。 Java 的劣势源于其最大的优势:一次编写,到处运行。 Java 需要运行于虚拟机(即 JVM)之上,为了保证...

HenrySun ⋅ 2016/08/04 ⋅ 0

Java NIO使用及原理分析 (一)

最近由于工作关系要做一些Java方面的开发,其中最重要的一块就是Java NIO(New I/O),尽管很早以前了解过一些,但并没有认真去看过它的实现原理,也没有机会在工作中使用,这次也好重新研究...

tantexian ⋅ 2016/07/07 ⋅ 0

Java 与 Netty 实现高性能高并发

1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程服务调用。相比于传统基于...

ljianbing ⋅ 2017/04/01 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vue-cli是什么?

vue-cli是什么? vue-cli 是vue.js的脚手架,用于自动生成vue.js+webpack的项目模板,分为vue init webpack-simple 项目名 和vue init webpack 项目名 两种。 当然首先你的安装vue,webpack...

韦姣敏 ⋅ 33分钟前 ⋅ 0

12c rman中输入sql命令

12c之前版本,要在rman中执行sql语句,必须使用sql "alter system switch logfile"; 而在12c版本中,可以支持大量的sql语句了: 比如: C:\Users\zhengquan>rman target / 恢复管理器: Release 1...

tututu_jiang ⋅ 39分钟前 ⋅ 0

java 线程池

概述 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(...

轨迹_ ⋅ 44分钟前 ⋅ 0

Nginx的https配置记录以及http强制跳转到https的方法梳理

Nginx的https配置记录以及http强制跳转到https的方法梳理 一、Nginx安装(略) 安装的时候需要注意加上 --with-httpsslmodule,因为httpsslmodule不属于Nginx的基本模块。 Nginx安装方法: ...

Yomut ⋅ 56分钟前 ⋅ 0

SpringCloud Feign 传递复杂参数对象需要注意的地方

1.传递复杂参数对象需要用Post,另外需要注意,Feign不支持使用GetMapping 和PostMapping @RequestMapping(value="user/save",method=RequestMethod.POST) 2.在传递的过程中,复杂对象使用...

@林文龙 ⋅ 57分钟前 ⋅ 0

如何显示 word 左侧目录大纲

打开word说明文档,如下图,我们发现左侧根本就没有目录,给我们带来很大的阅读障碍 2 在word文档的头部菜单栏中,切换到”视图“选项卡 3 然后勾选“导航窗格”选项 4 我们会惊奇的发现左侧...

二营长意大利炮 ⋅ 今天 ⋅ 0

智能合约编程语言Solidity之线上开发工具

工具地址:https://ethereum.github.io/browser-solidity/ 实例实验: 1.创建hello.sol文件 2.调试输出结果

硅谷课堂 ⋅ 今天 ⋅ 0

ffmpeg 视频格式转换

转 Mp4 格式 #> ffmpeg -i input.avi -c:v libx264 output.mp4#> ffmpeg -i input.avi -c:v libx264 -strict -2 output.mp4#> ffmpeg -i input.avi -c:v libx264 -strict -2 -s 1......

Contac ⋅ 今天 ⋅ 0

VCS仿真生成vpd文件(verilog)

VCS仿真生成vpd文件(verilog): https://www.cnblogs.com/OneFri/p/5987673.html SYNOPSYS VCS常用命令使用详解 https://blog.csdn.net/hemmingway/article/details/49382551 DVE是synopsys公......

whoisliang ⋅ 今天 ⋅ 0

Spring Boot启动配置原理

几个重要的事件回调机制 配置在META-INF/spring.factories ApplicationContextInitializer SpringApplicationRunListener 只需要放在ioc容器中 ApplicationRunner CommandLineRunner 启动流程......

小致dad ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部