java.nio.Buffer源码阅读
Java 自从 JDK1.4 起,对各种 I/O 操作使用了 Buffer 和 Channel 技术。这种更接近于操作系统的的底层操作使得 I/O 操作速度得到大幅度提升,下面引用一段《Java 编程思想》对于 Buffer(缓冲器)和 Channel 的形象化解释。
我们可以将它想象成一个煤矿,Channel(通道)是一个包含煤层(数据)的矿藏,而 Buffer(缓冲器)则是派送到矿藏的卡车。卡车满载煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和 Channel 交互;我们只是和 Buffer 交互,并把 Buffer 派送到 Channel。Channel 要么从 Buffer 获得数据,要么向 Buffer 发送数据。
本片博文简单的介绍一下 java.nio 包中的 Buffer 抽象类。
一、类的介绍
(一)概述
首先 Buffer 是一个能存储基本数据类型的容器(除了 Boolean 类型),从 java.nio 包的继承结构就能看出来。

但是它的子类没有 BooleanBuffer 这样一个类。
Buffer 是一个线性、有序的基本类型元素组成的序列。 除了其中存储的元素,Buffer 类还有 4 个重要的成员属性:
- capacity:它表示一个 Buffer 包含的元素数量,它是非负且恒定不变的。
- position:它是下一个要读或者写的元素的索引,它是非负的且不会超过 limit 的大小。
- limit:它是可以读或者写的最后一个元素的索引,它是非负的且不会超过 capacity 的大小。
- mark:当调用 reset() 方法被调用时,一个 Buffer 的 mark 值会被设定为当前的 position 值的大小。
综合上面,可以得出这样一个永远成立的公式:0 <= mark <= position <= limit <= capacity
通过这 4 个属性,间接的实现了 Buffer 中的读写保护机制。
一个新创建的 Buffer 具有以下几个性质:
- 它的 position 是 0;
- mark 没有被定义(实际上是 -1);
- 而 limit 值可能是 0,也可能是其他值,这取决于这个 Buffer 的类型;
- Buffer 中每一个元素值都被初始化为 0
(二)数据传输
Buffer 类的每一个子类都有两套 put 和 get 操作,以便向 Buffer 中写数据,或从 Buffer 中读数据。
- (Relative opetations)相对操作:这种操作会从当前的 position 位置开始读写一个或多个元素,position 会按照读写的元素个数值增加。
- (Absolute opetations)绝对操作:绝对操作要指明读写的元素索引,读写元素后,当前 Buffer 的 position 值不变。
上面说的是我们直接与 Buffer 打交道的方式。开篇就说过,Channel 也能操作 Buffer 中的数据,要注意的是这个 Channel 是与当前 Buffer 的 position 位置相关联的 Channel。
(三)一些重要的方法
在学习这些方法前先来看一张图:

因为在创建 Buffer 的时候并不会区分创建的是读 Buffer 还是写 Buffer,所以就需要一种机制来区分当前 Buffer 正在被读还是正在被写,即所谓的读模式与写模式。
利用 position 和 limit 这两个属性就能很好的做出这种区分,从上面的图就能看出来,当 limit = capacity 的时候说明可以向当前 Buffer 写入数据;当二者不等时就可以从 Buffer 中读取数据。
每次读写模式的转换 position 都被重置为 0,下面的 3 个方法就是完成读写模式转换的 3 种途径。
- clear( ) :调用 clear( ) 方法后,我们就可以向 Buffer 里面 put 数据,或者 Channel 能读取 Buffer 里的数据,并且将 limit 设置为 capacity,将 position 设置为 0。
- flip( ) :调用 flip( ) 方法后,我们就可以向 Buffer 里面 get 数据,或者 Channel 能向 Buffer 里写数据,并且将 limit 设置为 position,将 position 设置为 0。
- rewind( ) :调用 flip( ) 方法后,使 Buffer 里的数据可以被重新读取(无论是我们从 Buffer 读,还是 Channel 从 Buffer 读),此时 limit 不变,将 position 设置为 0。
(四)Buffer 的读写性
Buffer 分为:① 只读 Buffer ② 读写 Buffer
每个 Buffer 都是可读的,但是不是每个 Buffer 都是可写的。
只读 Buffer 的内容是不能变化,但是 mark,limit,position 值是可变的。
(五)线程安全性
在多线程操作下 Buffer 是不安全的,所以在多线程操作同一个 Buffer 的时候要使用相应的线程同步操作。
(六)Buffer 的数组形式
@ TODO
二、类的源代码
public abstract class Buffer {
/**
* The characteristics of Spliterators that traverse and split elements
* maintained in Buffers.(这个属性我目前还没弄懂是干嘛用的)
*/
static final int SPLITERATOR_CHARACTERISTICS =
Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;
// 注意只有 mark 和 position 有初始值
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
// 这个属性只有在当前 Buffer 为 Direct Buffer 时才会使用
long address;
// 创建一个指定属性值的 Buffer,且参数的大小必须符合上面所讲到的大小顺序规范(会在构造方法里进行检查)
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
public final int capacity() {
return capacity;
}
public final int position() {
return position;
}
/** * 重新设置 Buffer 的 position,并检查其大小是否符合规范
*/
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
public final int limit() {
return limit;
}
/**
* 同 position
*/
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
/**
* 将 mark 设置为当前 position,并返回当前 Buffer*/
public final Buffer mark() {
mark = position;
return this;
}
/**
* 将当前的 position 重新指向 mark 所指的元素,并且 mark 值不变
*/
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
/**
* 这个方法的原理上面已经讲过,下面是它的使用方法
* buf.clear(); //
* in.read(buf); // in 可以是一个 Channel 及其子类的一个实例,表示从 Channel 中读取数据后写入 Buffer 中
* 注意这个方法并不会使 Buffer 中的数据清空,只是 Buffer 中的读写保护机制
* 使这个操作看起来像是将 Buffer 中的内容清空了,实际上只是覆盖了原有的值而已*/
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
/**
* 这个方法的原理上面也说过了
* 具体可以像下面这样使用
* buf.put(magic); // 内存向 Buffer 中存放数据(我们直接打交道的是 Buffer)
* in.read(buf); // Channel 也向 Buffer 中存放数据
* buf.flip(); // Flip buffer,让 Buffer 做好被读取数据的准备
* out.write(buf); // Channel 从 Buffer 中读取数据
*/
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
/**
* 具体使用如下:
* out.write(buf); // 将 Buffer 中的数据写入 Channel
* buf.rewind(); // Rewind buffer
* buf.get(array); //
*/
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
/**
* 返回当前 Buffer 还有多少数据可供读写*/
public final int remaining() {
return limit - position;
}
public final boolean hasRemaining() {
return position < limit;
}
public abstract boolean isReadOnly();
/**
* 判断 Buffer 是否是由可获取的数组构成
* 如果这个方法返回 true,那说明这个 Buffer 是可读写的
* 且 array() 和 arrayOffset() 方法可以安全的调用
* @since 1.6
*/
public abstract boolean hasArray();
/**
* 返回这个 Buffer 的数组形式(optional operation)
* 这个方法是为了让数组 Buffer 更有效的传递给 native code
* Buffer 的子类会实现这个方法,并返回子类 Buffer 里所保存的基本类型的数组
*
* 改变 Buffer 的内容也会造成对数组内容的改变,反之亦然
*
* 一般在调用这个方法之前会调用 hasArray() 方法,以确保能获取到数组内容
*
* @since 1.6
*/
public abstract Object array();
/**
* Returns the offset within this buffer's backing array of the first
* element of the buffer(optional operation).
*
* If this buffer is backed by an array then buffer position p
* corresponds to array index p + arrayOffset().
*
* <p> Invoke the {@link #hasArray hasArray} method before invoking this
* method in order to ensure that this buffer has an accessible backing
* array. </p>
*
* @return The offset within this buffer's array
* of the first element of the buffer
*
* @throws ReadOnlyBufferException
* If this buffer is backed by an array but is read-only
*
* @throws UnsupportedOperationException
* If this buffer is not backed by an accessible array
*
* @since 1.6
*/
public abstract int arrayOffset();
/** * 鉴别 Buffer 是否为 Direct Buffer
* @since 1.6
*/
public abstract boolean isDirect();
// -- 下面是几个包内的私有方法,了解一下即可. --
final int nextGetIndex() { // package-private
if (position >= limit)
throw new BufferUnderflowException();
return position++;
}
final int nextGetIndex(int nb) { // package-private
if (limit - position < nb)
throw new BufferUnderflowException();
int p = position;
position += nb;
return p;
}
final int nextPutIndex() { // package-private
if (position >= limit)
throw new BufferOverflowException();
return position++;
}
final int nextPutIndex(int nb) { // package-private
if (limit - position < nb)
throw new BufferOverflowException();
int p = position;
position += nb;
return p;
}
final int checkIndex(int i) { // package-private
if ((i < 0) || (i >= limit))
throw new IndexOutOfBoundsException();
return i;
}
final int checkIndex(int i, int nb) { // package-private
if ((i < 0) || (nb > limit - i))
throw new IndexOutOfBoundsException();
return i;
}
final int markValue() { // package-private
return mark;
}
final void truncate() { // package-private
mark = -1;
position = 0;
limit = 0;
capacity = 0;
}
final void discardMark() { // package-private
mark = -1;
}
static void checkBounds(int off, int len, int size) { // package-private
if ((off | len | (off + len) | (size - (off + len))) < 0)
throw new IndexOutOfBoundsException();
}
}
(完)
java.nio.Buffer源码阅读的更多相关文章
- Java多线程——ReentrantLock源码阅读
上一章<AQS源码阅读>讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲). ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解 ...
- Java多线程——ReentrantReadWriteLock源码阅读
之前讲了<AQS源码阅读>和<ReentrantLock源码阅读>,本次将延续阅读下ReentrantReadWriteLock,建议没看过之前两篇文章的,先大概了解下,有些内 ...
- java.util.HashSet, java.util.LinkedHashMap, java.util.IdentityHashMap 源码阅读 (JDK 1.8)
一.java.util.HashSet 1.1 HashSet集成结构 1.2 java.util.HashSet属性 private transient HashMap<E,Object> ...
- java.util.HashSet, java.util.LinkedHashMap, java.util.IdentityHashMap 源码阅读 (JDK 1.8.0_111)
一.java.util.HashSet 1.1 HashSet集成结构 1.2 java.util.HashSet属性 private transient HashMap<E,Object> ...
- 【JAVA】HashMap源码阅读
目录 1.关键的几个static参数 2.内部类定义Node节点 3.成员变量 4.静态方法 5.HashMap的四个构造方法 6.put方法 7.扩容resize方法 8.get方法 9.remov ...
- Java的Vector源码阅读
* The {@code Vector} class implements a growable array of * objects. Like an array, it contains comp ...
- Java多线程框架源码阅读之---ReentrantLock
ReentrantLock基于Sync内部类来完成锁.Sync有两个不同的子类NonfairSync和FairSync.Sync继承于AbstractQueuedSynchronizer. Reent ...
- Java NIO Buffer(netty源码死磕1.2)
[基础篇]netty源码死磕1.2: NIO Buffer 1. Java NIO Buffer Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区.Buffer缓冲区本质上是一块可 ...
- JDK源码阅读(1)_简介+ java.io
1.简介 针对这一个版块,主要做一个java8的源码阅读笔记.会对一些在javaWeb中应用比较广泛的java包进行精读,附上注释.对于容易混淆的知识点给出相应的对比分析. 精读的源码顺序主要如下: ...
随机推荐
- TCP/IP协议族(一)
TCP/IP协议族(一) HTTP简介.请求方法与响应状态码 接下来想系统的回顾一下TCP/IP协议族的相关东西,当然这些东西大部分是在大学的时候学过的,但是那句话,基础的东西还是要不时的回顾回顾的. ...
- MySQL - 常见的三种数据库存储引擎
原文:MySQL - 常见的三种数据库存储引擎 数据库存储引擎:是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询.更新和删除数据.不同的存储引擎提供不同的存储机制.索引技巧. ...
- 各个 C# 版本的主要特性、发布日期和发布方式(C# 1.0 - 7.3)
原文 各个 C# 版本的主要特性.发布日期和发布方式(C# 1.0 - 7.3) 本文收集各个 C# 版本的主要特性.发布日期和发布方式. C# 8.0 尚在预览版本 C# 7.3 2018 年 5 ...
- WPF 3D编程介绍
原文:WPF 3D编程介绍 上一篇文章简单的介绍了WPF编程的相关的内容,也推荐了本书.今天要来讲一下在WPF如何开展3D编程. 使用的xmal 和C#开发的时候:需要使用如下的关键要素: 1:摄像机 ...
- python之强大的日志模块
1.简单的将日志打印到屏幕 import logging logging.debug('This is debug message')logging.info('This is info mess ...
- python下载图片(3)
# -*- coding: utf-8 -*-"""some function by metaphy,2007-04-03,copyleftversion 0.2&quo ...
- React学习(3)——ref,key,PureComponent,bindActionCreator
ref 如果在html里设置ref那么它就指向这个真实的DOM节点. 如果在组件里设置ref,那么它就指向这个组件实例的引用,和组件里面的this互等. 我们经常在表单input,select里使用, ...
- VC++中的C运行时库浅析(控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程)
1.概论 运行时库是程序在运行时所需要的库文件,通常运行时库是以LIB或DLL形式提供的.C运行时库诞生于20世纪70年代,当时的程序世界还很单纯,应用程序都是单线程的,多任务或多线程机制在此时还属于 ...
- WPF - 本质:数据和行为
原文:WPF - 本质:数据和行为 如果自己来做一个UI框架,我们会首先关注哪些方面?我想UI框架主要处理的一定包括两个主要层次的内容,一个是数据展现,另一个就是数据操作,所以UI框架必须能够接收各种 ...
- layerui
引用layer.js,官网:http://layer.layui.com/常用属性:btn/icon/skin/time/content/yes(点击确认.提交) 常用窗体.alert layer.a ...