Java NIO 学习笔记一

缓冲区操做

进程执行I/O操做,归结起来就是向操做系统发出请求,它要么把缓存区例的数据排干(写),要么用数据把数据区填满(读)。进程使用这一机制处理全部数据进出操做。 m7IfQH.png 进程使用read()系统调用,要求其缓存区被填满。内核随即向磁盘控制器发出命令,要求其从磁盘读取数据。经过DMA技术直接将磁盘中的数据写入内核内存缓存区,一旦磁盘控制器把缓存区填满,内存当即把数据从内核空间的里你是缓冲区拷贝到进程执行read()调用时指定的缓冲区。java

发散/汇聚

根据发散/汇聚的概念,进程只须要一个系统调用,就能把一连串的缓冲区地址传递给操做系统。而后,内核就能顺序填充或排干多个缓冲区,读的时候将数据发散到多个用户空间缓冲区,写的时候再从多个缓冲区把数据汇聚起来。 m7oYnA.png数组

利用虚拟内存避免一些拷贝

前面提到设备控制器不能使用DMA直接存储到用户空间,须要从内核空间拷贝到用户空间。但在使用内存多重映射技术能够避免这种拷贝。 现代操做系统都使用虚拟内存,它有极大优势:缓存

  1. 多个虚拟地址能够映射到同一个物理地址。
  2. 虚拟内存空间可能大于实际可用的硬件内存。

<!--more-->post

m7T9ud.png 借助虚拟内存的特色,将内核空间中的缓冲区的虚拟地址和用户空间的缓冲区虚拟地址映射到一个物理地址(即内存多重映射技术)。 但这也是有前提的,内核与用户缓冲区必须使用相同的页对齐,缓冲区的大小还必须是磁盘控制块大小的倍数。ui

采用分页技术的操做系统执行IO操做

  1. 肯定请求的数据分布在文件系统的哪些页,这些页不必定都是连续的
  2. 在内核空间种分配足够的页,以容纳文件系统页
  3. 在内存页与磁盘上的文件系统页之间创建映射
  4. 为每一个页产生一个缺页异常
  5. 虚拟内存系统俘获缺页异常,调用相应的缺页处理程序,将文件系统页调入主存
  6. 页面调入成功后,文件系统队原始数据进行解析,获取文件内容和属性信息。

文件系统页也会和其它内存页同样被缓存在主存zthis

内存映射文件

m7sbJ1.png 内存映射IO使用文件系统创建用户空间直到可用文件系统页的虚拟内存映射。 当用户进行触碰到映射内存空间时,会自动产生页错误,从而将文件系统从磁盘读进主存。若是用户修改了映射内存空间时,相应的页会被标记为脏页,随后就会将更改持久化到磁盘。spa

其优势:操作系统

  1. 用户进程直接将文件数据看成内存。
  2. 自动产生页错误,将文件数据从磁盘读入主存
  3. 操做系统的虚拟内存子系统能够对这些页进行智能高速缓存
  4. 数据老是按页对齐的
  5. 大型文件使用映射能够节约内存。

文件锁定机制

文件锁定机制容许一个进程阻止其它仅从存取某文件,或限制其存取方式。 文件锁定的锁定区域能够是整个文件也可ui细致到单个字节。线程

共享锁和独占锁

多个共享锁能够同时对同一文件区域发生做用;独占锁要求相关区域不能有其它锁定在起做用。3d

共享锁和独占锁的经典应用 --- 控制读取共享文件的更新 某个进程要读取文件,就要先取得相关区域的共享锁。其它但愿读取相同文件区域的进程也会请求共享锁。多个进程得以并行读取,互不影响。若是在此时有其它进行想要更新文件,那么它须要请求独占锁,而后该进行会进入阻滞状态,直到既有锁定(共享的,独占的)所有解除它才能拿到独占锁。一旦该进程拿到了独占锁,其它全部的共享锁读取线程间进入阻滞状态,直到独占锁解除。

流I/O

并不是全部的I/O都是面向块的,也有流I/O,其原理模仿了通道。I/O流字节必须顺序读取。流的传输通常比块设备慢,进程用于间歇性输入。

Buffer类

一个Buffer对象是固定容量的数据的容器。在这里数据能够被存储并在以后用于检索。

Buffer类的层次图

m7bFER.png

属性

private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
  1. 容量(capacity)
  2. 上界(limit) 缓冲区第一个不能被读或写的元素。或者说是现存元素的计数。其指明了缓冲区中有效内容末端的位置。
  3. 标记(mark) 一个备忘的位置。使用mark()来设定mark=postion.调用reset()设定position=mark 这四个属性之间遵循的关系为: 0<= mark <= position <= limit <= capacity

重要方法

put()

put方法就是将元素加入缓冲区,值得注意的是,put方法只改变position,不会改变limit和capacity。 mHPgtU.png

flip()

public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

filp()方法将一个可以继续添加数据的填充状态的缓冲区翻转为一个准备读出元素的释放状态。根据代码filp()所做的工做不言而喻。

rewind()

rewind方法和flip方法很是相似,可是它不会影响上界属性,能够利用rewind方法来从新读已经被翻转的缓冲区中的数据。

hasRemaining()

//切换到读模式
        buffer.flip();
        int count=buffer.remaining();
        System.out.println("当前位置距离上界还有:"+count);
        
        while (buffer.hasRemaining()){
            System.out.print(buffer.get()+" ");
        }

hasRemaining()能够判断当前位置是否已经达到buffer的上界。remaining()能够获取当前位置与上界的距离。

compact()

当须要从buffer中释放一部分已经被读取过的数据时,可使用compact方法,他会将为读的数据元素下移动使得第一个元素的索引为0.该方法在复制数据的场景下,比使用get方法和put方法更加的高效。

关于标记的一些注意点

在未设置标记以前,mark是等于-1的。此时若是调用reset()会抛出InvalidMarkExceptioin异常。值得注意的是由许多方法都是会将mark重置为-1的。如:rewind(),flip(),clear()等。

缓冲区相等

两个缓冲区相等的条件:

  1. 对象类型相同
  2. 两个对象剩余一样的元素(剩余是指position到limit之间的元素)

被认为相等的两个缓冲区 mbGwtO.png

批量的移动

以CharBuffer为例子

public CharBuffer get(char[] dst)
//offset参数是填充dst的起点位置
public CharBuffer get(char[] dst,int offset,int lenth);

public final CharBuffer put(char[] src)
public CharBuffer put(char[] src,int offset,int length)
public CharBUffer put(CharBuffer src)

可使用如下方法高效的读取处理数据

buffer.flip();
    int[] arr=new int[10];
    while (buffer.hasRemaining()){
        int len=Math.min(arr.length,buffer.remaining());
        buffer.get(arr,0,len);
        //处理数据
        processData(arr,len);
    }

缓存区建立的两个关键方法

public static CharBuffer allocate(int capacity)

public static CharBuffer Wrap(char[] array)
//offset参数用来初始化position参数,length参数用来初始化limit参数
public static CharBuffer wrap(char[] array,int offset,int length)

建立新的缓存区有两种方式,分别是分配或包装操做。 allocate方法采用的分配的方式,他会分类一个数组来存储数据。而wrap方法是将一个数组包装为一个缓冲区。这意味着对这个数组的任何改动都会对这个缓冲区可见。

复制缓冲区

public abstract CharBuffer duplicate();
public abstract CharBuffer asReadOnlyBuffer();
public abstract CharBuffer slice();

使用duplicate方法能够建立一个和原缓冲区共享数据元素的副本缓冲区。它们共享数据元素可是拥有各自的位置,上界和标记属性。

使用asReadOnlyBuffer方法能够建立一个只读的副本缓冲区。它所建立的副本是不容许使用put方法的。

使用slice方法能够建立一个position到limit的元素的一个副本。

值得注意的是以上三种方法都不会在堆中从新分配空间用来存储数据。因此它们都是复制缓冲区的方法。

字节缓冲区

字节顺序

字节顺序分为大端存储和小端存储 大端: 高位存放在内存的低地址位 小端: 低位存放在内存的低地址位

使用ByteOrder order()方法能够得到该缓冲区的字节顺序;使用ByteBuffer order(ByteOrder bo) 方法能够修改缓冲区的字节顺序。

直接缓冲区

字节缓冲区的一大特色就是它能够是直接缓冲区。它能够成为通道所执行的I/O的源头或目标。

非直接缓冲区:非直接缓冲区将缓冲区创建在JVM内存在中。 mO5GXF.png

非直接缓冲区:直接将缓冲区创建在物理内存中,能够提升效率。 mO5N79.png

//分配直接缓冲区
        ByteBuffer bf=ByteBuffer.allocateDirect(10);
        //判断其是不是直接缓冲区,结果是true
        System.out.println(bf.isDirect());

视图缓冲区

ByteBuffer类容许建立视图来将byte型缓冲区字节数据映射位其它的原始数据类型。视图对象维护它本身的容量、位置、上界和标记,可是和原来的缓冲区共享数据元素。

ByteBuffer bf=ByteBuffer.allocate(10);
        IntBuffer intBuffer=bf.asIntBuffer();
相关文章
相关标签/搜索