进程执行I/O操做,归结起来就是向操做系统发出请求,它要么把缓存区例的数据排干(写),要么用数据把数据区填满(读)。进程使用这一机制处理全部数据进出操做。 进程使用read()系统调用,要求其缓存区被填满。内核随即向磁盘控制器发出命令,要求其从磁盘读取数据。经过DMA技术直接将磁盘中的数据写入内核内存缓存区,一旦磁盘控制器把缓存区填满,内存当即把数据从内核空间的里你是缓冲区拷贝到进程执行read()调用时指定的缓冲区。java
根据发散/汇聚的概念,进程只须要一个系统调用,就能把一连串的缓冲区地址传递给操做系统。而后,内核就能顺序填充或排干多个缓冲区,读的时候将数据发散到多个用户空间缓冲区,写的时候再从多个缓冲区把数据汇聚起来。 数组
前面提到设备控制器不能使用DMA直接存储到用户空间,须要从内核空间拷贝到用户空间。但在使用内存多重映射技术能够避免这种拷贝。 现代操做系统都使用虚拟内存,它有极大优势:缓存
<!--more-->post
借助虚拟内存的特色,将内核空间中的缓冲区的虚拟地址和用户空间的缓冲区虚拟地址映射到一个物理地址(即内存多重映射技术)。 但这也是有前提的,内核与用户缓冲区必须使用相同的页对齐,缓冲区的大小还必须是磁盘控制块大小的倍数。ui
文件系统页也会和其它内存页同样被缓存在主存zthis
内存映射IO使用文件系统创建用户空间直到可用文件系统页的虚拟内存映射。 当用户进行触碰到映射内存空间时,会自动产生页错误,从而将文件系统从磁盘读进主存。若是用户修改了映射内存空间时,相应的页会被标记为脏页,随后就会将更改持久化到磁盘。spa
其优势:操作系统
文件锁定机制容许一个进程阻止其它仅从存取某文件,或限制其存取方式。 文件锁定的锁定区域能够是整个文件也可ui细致到单个字节。线程
多个共享锁能够同时对同一文件区域发生做用;独占锁要求相关区域不能有其它锁定在起做用。3d
共享锁和独占锁的经典应用 --- 控制读取共享文件的更新 某个进程要读取文件,就要先取得相关区域的共享锁。其它但愿读取相同文件区域的进程也会请求共享锁。多个进程得以并行读取,互不影响。若是在此时有其它进行想要更新文件,那么它须要请求独占锁,而后该进行会进入阻滞状态,直到既有锁定(共享的,独占的)所有解除它才能拿到独占锁。一旦该进程拿到了独占锁,其它全部的共享锁读取线程间进入阻滞状态,直到独占锁解除。
并不是全部的I/O都是面向块的,也有流I/O,其原理模仿了通道。I/O流字节必须顺序读取。流的传输通常比块设备慢,进程用于间歇性输入。
一个Buffer对象是固定容量的数据的容器。在这里数据能够被存储并在以后用于检索。
private int mark = -1; private int position = 0; private int limit; private int capacity;
0<= mark <= position <= limit <= capacity
put方法就是将元素加入缓冲区,值得注意的是,put方法只改变position,不会改变limit和capacity。
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
filp()方法将一个可以继续添加数据的填充状态的缓冲区翻转为一个准备读出元素的释放状态。根据代码filp()所做的工做不言而喻。
rewind方法和flip方法很是相似,可是它不会影响上界属性,能够利用rewind方法来从新读已经被翻转的缓冲区中的数据。
//切换到读模式 buffer.flip(); int count=buffer.remaining(); System.out.println("当前位置距离上界还有:"+count); while (buffer.hasRemaining()){ System.out.print(buffer.get()+" "); }
hasRemaining()能够判断当前位置是否已经达到buffer的上界。remaining()能够获取当前位置与上界的距离。
当须要从buffer中释放一部分已经被读取过的数据时,可使用compact方法,他会将为读的数据元素下移动使得第一个元素的索引为0.该方法在复制数据的场景下,比使用get方法和put方法更加的高效。
在未设置标记以前,mark是等于-1的。此时若是调用reset()会抛出InvalidMarkExceptioin
异常。值得注意的是由许多方法都是会将mark重置为-1的。如:rewind(),flip(),clear()等。
两个缓冲区相等的条件:
被认为相等的两个缓冲区
以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内存在中。
非直接缓冲区:直接将缓冲区创建在物理内存中,能够提升效率。
//分配直接缓冲区 ByteBuffer bf=ByteBuffer.allocateDirect(10); //判断其是不是直接缓冲区,结果是true System.out.println(bf.isDirect());
ByteBuffer类容许建立视图来将byte型缓冲区字节数据映射位其它的原始数据类型。视图对象维护它本身的容量、位置、上界和标记,可是和原来的缓冲区共享数据元素。
ByteBuffer bf=ByteBuffer.allocate(10); IntBuffer intBuffer=bf.asIntBuffer();