缓冲区操作

进程执行I/O操作,归结起来就是向操作系统发出请求,它要么把缓存区例的数据排干(写),要么用数据把数据区填满(读)。进程使用这一机制处理所有数据进出操作。



进程使用read()系统调用,要求其缓存区被填满。内核随即向磁盘控制器发出命令,要求其从磁盘读取数据。通过DMA技术直接将磁盘中的数据写入内核内存缓存区,一旦磁盘控制器把缓存区填满,内存立即把数据从内核空间的里你是缓冲区拷贝到进程执行read()调用时指定的缓冲区。

发散/汇聚

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

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

前面提到设备控制器不能使用DMA直接存储到用户空间,需要从内核空间拷贝到用户空间。但在使用内存多重映射技术可以避免这种拷贝。

现代操作系统都使用虚拟内存,它有极大优点:

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



借助虚拟内存的特点,将内核空间中的缓冲区的虚拟地址和用户空间的缓冲区虚拟地址映射到一个物理地址(即内存多重映射技术)。

但这也是有前提的,内核与用户缓冲区必须使用相同的页对齐,缓冲区的大小还必须是磁盘控制块大小的倍数。

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

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

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

内存映射文件



内存映射IO使用文件系统建立用户空间直到可用文件系统页的虚拟内存映射。

当用户进行触碰到映射内存空间时,会自动产生页错误,从而将文件系统从磁盘读进主存。如果用户修改了映射内存空间时,相应的页会被标记为脏页,随后就会将更改持久化到磁盘。

其优点:

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

文件锁定机制

文件锁定机制允许一个进程阻止其它仅从存取某文件,或限制其存取方式。

文件锁定的锁定区域可以是整个文件也可ui细致到单个字节。

共享锁和独占锁

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

共享锁和独占锁的经典应用 --- 控制读取共享文件的更新

某个进程要读取文件,就要先取得相关区域的共享锁。其它希望读取相同文件区域的进程也会请求共享锁。多个进程得以并行读取,互不影响。如果在此时有其它进行想要更新文件,那么它需要请求独占锁,然后该进行会进入阻滞状态,直到既有锁定(共享的,独占的)全部解除它才能拿到独占锁。一旦该进程拿到了独占锁,其它所有的共享锁读取线程间进入阻滞状态,直到独占锁解除。

流I/O

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

Buffer类

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

Buffer类的层次图

属性

    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。

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之间的元素)

被认为相等的两个缓冲区

批量的移动

以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();

Java NIO 学习笔记一的更多相关文章

  1. Java NIO学习笔记

    Java NIO学习笔记 一 基本概念 IO 是主存和外部设备 ( 硬盘.终端和网络等 ) 拷贝数据的过程. IO 是操作系统的底层功能实现,底层通过 I/O 指令进行完成. 所有语言运行时系统提供执 ...

  2. 零拷贝详解 Java NIO学习笔记四(零拷贝详解)

    转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...

  3. Java NIO 学习笔记(七)----NIO/IO 的对比和总结

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  4. Java NIO 学习笔记(六)----异步文件通道 AsynchronousFileChannel

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  5. Java NIO 学习笔记(五)----路径、文件和管道 Path/Files/Pipe

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  6. Java NIO 学习笔记(四)----文件通道和网络通道

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  7. Java NIO 学习笔记(三)----Selector

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  8. Java NIO 学习笔记(二)----聚集和分散,通道到通道

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  9. Java NIO 学习笔记(一)----概述,Channel/Buffer

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  10. JAVA NIO学习笔记1 - 架构简介

    最近项目中遇到不少NIO相关知识,之前对这块接触得较少,算是我的一个盲区,打算花点时间学习,简单做一点个人学习总结. 简介 NIO(New IO)是JDK1.4以后推出的全新IO API,相比传统IO ...

随机推荐

  1. 【AMAD】sorl-thumbnail -- Django缩略图

    动机 简介 个人评分 动机 生成缩略图是一个烦人的工作. 简介 sorl-thumbnail1的特性包括: 支持不同的storage 实现缩略图的引擎是可以切换的:Pillow, ImageMagic ...

  2. 【VS开发】#pragma预处理命令

    #pragma预处理命令 #pragma可以说是C++中最复杂的预处理指令了,下面是最常用的几个#pragma指令: #pragma comment(lib,"XXX.lib") ...

  3. 【Matlab开发】函数bsxfun的使用

    [Matlab开发]函数bsxfun的使用 标签:[Matlab开发] 版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/lg1259156776/. 说明:当我们 ...

  4. OpenCV2.源码_编译&调试

    1.VS 调试第三方库源码 - writeeee的专栏 - CSDN博客.html(https://blog.csdn.net/writeeee/article/details/82692770) Z ...

  5. npm run build 报错 Error: No PostCSS Config found in...

    module.exports = { plugins: [ require('autoprefixer')//自动添加css前缀 ] }; 在项目根目录新建postcss.config.js文件,添加 ...

  6. 在vue项目中获取当前城市

    在vue项目中使用百度地图获取当前城市:https://www.jianshu.com/p/0819cfd46712 Vue2 :百度地图bmap:https://www.jianshu.com/p/ ...

  7. jstack使用

    top -p 22072 -H  -p:查看某个进程 -H列出所有的线程 printf '%x' 22398 (16进制线程号) sudo -u tomcat jstack 22072 | grep ...

  8. VMware Windows Server 2008 R2 X64 虚拟机安装教程

    首先进入VMware Workstation中,点击创建新的虚拟机 然后按如下步骤操作 然后进去选择拷贝的Windows 2008的映像文件 下面这个网址里面提供了各种映像文件的下载http://is ...

  9. 在C#中简单使用gRPC

    一.引言 本文采用gRPC官方提供的一个教程例子,通过这个例子可以学习到在.proto文件中定义服务.使用protocol buffer编译器生成服务器和客户端代码.使用C#gRPC API为您的服务 ...

  10. js函数(2)

    8.3函数的形参和实参 js中的函数并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型的检查. 8.3.1函数的形参和实参 当调用函数时传入的实参比函数声明时指定的形参个数要少,剩下的参数都 ...