2、java文件系统io()

  本篇文章为你整理了2、java文件系统io()的详细内容,包含有 2、java文件系统io,希望能帮助你了解 2、java文件系统io。

  
 

  app是一个应用程序,应用程序有缓冲区,我们在写代码的时候,都用过Buffer。
 

  应用程序肯定要访问内核,内核其实也是一个程序,应用程序和内核通信的时候,会调用一个system call(系统调用)。
 

  system call 的实现是int 0x80,int是cpu的指令,0x80是16进制的,所以它的值是128,如果是二进制的话,就是10000000,它是一个值,这个值放在cpu的寄存器,然后和中断描述符(和中断控制器应该是一个意思)去匹配,终端描述符的128表示的就是call back方法,去调用的这个方法。

  所以应用程序调用某一个系统调用的时候,其实完成了一个int 0x80,这条指令一但被cpu读到以后,就开始调用内核里的call back方法,然后就开始保护现场,从用户态切换到内核态。

  cpu读应用程序去执行,也会去读内核里的指令去执行,中间不停的切换读取用户态(用户空间,应用程序)和内核态(内核空间),然后从哪开始读取指令呢?由这个call back方法告诉cpu,然后执行调度。

  比如cpu要调用kernel去读取硬盘上面的数据给读取到,调用的read,其实调用的是它的文件描述符(fd),读的时候比如数据很多,会分成很多个4k大小的pagecache,这是内核的缓冲区,pagecache会分成消耗内存去压榨内存,是不会主动释放的。

  数据从硬盘读取到内核缓冲区,硬盘里也有缓冲区,读取的时候有个东西叫协处理器(DMA),这是什么?
 

  如果没有协处理器,cpu读取数据的时候,从硬盘读取一个字节放在cpu自己的寄存器里,然后再交给内核,然后再拷贝到buffer,数据拷贝这么多次,这种做法是很缓慢的。
 

  协处理器可以直接把数据拷贝到内核里,这样的话cpu和协处理器可以交叉使用,都可以去读取硬盘的数据到内存里,并且这个时候cpu还可以去做其他事情,不至于说直接停在那里只在拷贝数据,这样会快很多。所以叫做协处理器。

  linux正常关机和断电

  linux虚拟机有2种关机方式,如果是图下这种方式,属于是正常关机,就等于是点了一个关机键。这个电源会产生一个中断,会告诉cpu,你的主人要关机了,然后开始调用关机的过程,会把脏数据,pagecache缓存,有一个回写磁盘的过程,会保存数据。
 

  如下图这个关机,等同于直接把断电了,这样的话来不及把内存的脏数据和pagecache缓存的数据给刷出去。
 

  验证:
 

  打开虚拟机,查看mysh这个文件内容
 

  
 

  这个文件是个脚本,内容如下:
 

  
 

  删除当前目录包含out的所有文件,并且用jdk1.8 javac编译 OSFileIO.java 这个文件,然后启动这个OSFileIO.java 这个类,传进入一个参数1

  然后查看一下这个OSFileIO.java 类的代码:
 

  源代码:

  

import org.junit.Test;

 

  import java.io.BufferedOutputStream;

  import java.io.File;

  import java.io.FileOutputStream;

  import java.io.RandomAccessFile;

  import java.nio.ByteBuffer;

  import java.nio.MappedByteBuffer;

  import java.nio.channels.FileChannel;

  public class OSFileIO {

   static byte[] data = "123456789\n".getBytes();

   static String path = "/root/testfileio/out.txt";

   public static void main(String[] args) throws Exception {

   switch ( args[0]) {

   case "0" :

   testBasicFileIO();

   break;

   case "1":

   testBufferedFileIO();

   break;

   case "2" :

   testRandomAccessFileWrite();

   case "3":

  // whatByteBuffer();

   default:

  
public static void testBufferedFileIO() throws Exception {

   File file = new File(path);

   BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));

   while(true){

   Thread.sleep(10);

   out.write(data);

   //测试文件NIO

   public static void testRandomAccessFileWrite() throws Exception {

  
//mmap 堆外 和文件映射的 byte not objtect

   MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);

  
map.put("@@@".getBytes()); //不是系统调用 但是数据会到达 内核的pagecache

   //曾经我们是需要out.write() 这样的系统调用,才能让程序的data 进入内核的pagecache

   //曾经必须有用户态内核态切换

   //mmap的内存映射,依然是内核的pagecache体系所约束的!!!

   //换言之,丢数据

   //你可以去github上找一些 其他C程序员写的jni扩展库,使用linux内核的Direct IO

   //直接IO是忽略linux的pagecache

   //是把pagecache 交给了程序自己开辟一个字节数组当作pagecache,动用代码逻辑来维护一致性/dirty。。。一系列复杂问题

   System.out.println("map--put--------");

   System.in.read();

  // map.force(); // flush

  
ByteBuffer buffer = ByteBuffer.allocate(8192);

  // ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

   int read = rafchannel.read(buffer); //buffer.put()

   System.out.println(buffer);

   buffer.flip();

   System.out.println(buffer);

   for (int i = 0; i buffer.limit(); i++) {

   Thread.sleep(200);

   System.out.print(((char)buffer.get(i)));

  
// ByteBuffer buffer = ByteBuffer.allocate(1024);

   ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

  
System.out.println("postition: " + buffer.position());

   System.out.println("limit: " + buffer.limit());

   System.out.println("capacity: " + buffer.capacity());

   System.out.println("mark: " + buffer);

   buffer.put("123".getBytes());

   System.out.println("-------------put:123......");

   System.out.println("mark: " + buffer);

   buffer.flip(); //读写交替

   System.out.println("-------------flip......");

   System.out.println("mark: " + buffer);

   buffer.get();

   System.out.println("-------------get......");

   System.out.println("mark: " + buffer);

   buffer.compact();

   System.out.println("-------------compact......");

   System.out.println("mark: " + buffer);

   buffer.clear();

   System.out.println("-------------clear......");

   System.out.println("mark: " + buffer);

  

 

  编辑一下
 

  
 

  
 

  定义了data 和 path 两个属性,然后main方法有个switch,如果参数为0,就调用testBasicFileIO()方法,
 

  testBasicFileIO()方法中写的是,通过new File() 获取一个文件,然后往这个文件输出data的数据,data是在上面定义好了的,在while死循环中不停的写入数据。

  查看完代码以后,调用这个mysh脚本,传入参数0,就可以调用testBasicFileIO()方法了
 

  启动后,不停的使用ll 命令查看out.txt文件,
 

  上图可以看见内容大小在不断的变大,在这个时候直接点击这个关机,等于蒿掉电源,直接断电了
 

  关机以后在重启启动这个虚拟机,查看out.txt文件可以看到,这个文件大小为0
 

  所以就验证了,数据在内存中pagecache不停的修改写入数据,在它还没有来得及把数据刷出去保存到磁盘上面的时候,直接给他断电,数据就丢失了。

  配置pagecache的数据写入磁盘

  sysctl -a grep dirty:通过管道查询dirty的配置项
 

  这些配置项都是阈值 对应着字节数

  接下来给这个配置项做一个修改,这个配置项在/etc/sysctl.conf
 

  vm.dirty_background_radio:90 表示90%,内核内存比如有10G,当pagecache占用了9G时候,内核会有里另一个线程把数据同步到磁盘,剩下的10%内存会接着给用户程序使用。但如果写入的数据很快,可能会报错,因为内核还没来得及把数据写入磁盘。

  vm.dirty_ratio:这个90表示pagecache的容量达到了内存90%,直接阻塞,用户程序不允许继续往内存写数据,会先把数据同步到磁盘,然后继续允许用户写数据。

  vm.dirty_writeback_centisecs:单位是1%秒,表示多久写入一次数据到磁盘,5000就是50秒

  vm.dirty_expire_centisecs:单位是1%秒,数据在pagecache存放时间,3000就是30秒

  改完sysctl.conf 文件以后,sysctl -p执行一下,不然不生效
 

  想象一下redis的日志保存和mysql的binLog保存信息,它们有3种方式保存数据:每秒钟保存一次、每操作一次保存一次、根据内核来保存。
 

  为什么要有这3种方式?因为数据写入在pagecache,不会立马写入磁盘,发生意外有可能会丢失很多数据!

  使用直接io和buffer的区别

  使用ll -h pcstat out.txt命令,查看这个out.txt 文件大小,和在内核中的分页情况
 

  
 

  最好的100.000 表示百分比是多少(文件总页和内核中分配的页数量占比)

  查看mysh 文件内容:
 

  然后执行这个mysh 执行文件
 

  执行后一直不停的使用ll -h pcstat out.txt命令查看文件大小情况
 

  
 

  这个就等于有一个字节就立马写入服务器的内存,所以会一直不断的切换核态,pagecache使用情况也一直都是100%。

  如果这个时候给它断电,数据是整个都丢失了,不会保存到磁盘。
 

  然后开机,继续查看那个out.txt文件,发现刚刚写的好几M大小的文件变成了0大小,数据丢失了
 

  使用buffer

  接下来调用这个testBufferedFileIO()方法
 

  使用mysh这个执行文件,参数1表示调用这个testBufferedFileIO()方法
 

  然后不停的在另一个窗口查看out.txt文件大小信息
 

  这个等于使用buffer,可以很明显的发现这个out.txt 文件大小增长的非常快(这也说明了为什么java一定要用buffer的io),它使用了buffer,每攒够10个字节的数据就调用一次操作系统内核,所以相比较使用直接io的话,减少了调用内核的次数,避免了不停的切换核态。

  
 

  刚开始发现pagecache使用率也是100%,但是等到写入数据的大小超过内存的3G大小以后,pagecache使用率慢慢的变小,因为这个时候说明已经在往磁盘写入数据了。

  如果把这个文件改名为OOXX.txt 文件,可以查看到pagecache里面还是有和这个文件的关联数据。
 

  如果这个时候再重新启动这个buffer的io,那它肯定会重新写入一个新的out.txt文件,然后疯狂的给内核缓冲区分配页缓存,肯定会占用那个OOXX.txt 文件之前的页缓存

  可以看到这个ooxx.txt 文件现在的页缓存比例,说明还是有缓存很多东西在内核缓冲区的
 

  然后再次启动mysh执行文件,重新向一个新的out.txt文件写入
 

  然后使用pcstat out.txt pcstat ooxx.txt命令,同时查看这两个文件的页缓存情况
 

  不停的查看
 

  但这个时候可以发现之前这个OOXX.txt 文件的pagecache使用率慢慢减少,被淘汰掉了(淘汰掉之前会把数据同步到磁盘),从而保证这个新的out.txt文件有相对应的文件页缓存

  通过buffer的io写入数据虽然很快,但是如果断电的话,数据会丢失很多。
 

  虽然看到的这个out.txt文件已经变得很大了,但实际它查询的是虚拟文件系统,并没有实际写入磁盘。

  所以pagecache的优点就是提升io性能,缺点就是丢失数据。

  nio就理解是new io,意思是新的io体系。
 

  新的io体系有两个概念:channel、byteBuffer
 

  nio也是基于buffer的,channel等于是把输入输出整合到了一起。

  ByteBuffer

  ByteBuffer就理解成字节数组,有一些对应的api和概念。

  allocate() 和 allocateDirect() 不影响最终结果,只是分配方式不一样。
 

  测试代码:

  

 @Test

 

   public void whatByteBuffer(){

  // ByteBuffer buffer = ByteBuffer.allocate(1024);

   ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

  
System.out.println("postition: " + buffer.position());

   System.out.println("limit: " + buffer.limit());

   System.out.println("capacity: " + buffer.capacity());

   System.out.println("mark: " + buffer);

   buffer.put("123".getBytes());

   System.out.println("-------------put:123......");

   System.out.println("mark: " + buffer);

   buffer.flip(); //读写交替

   System.out.println("-------------flip......");

   System.out.println("mark: " + buffer);

   buffer.get();

   System.out.println("-------------get......");

   System.out.println("mark: " + buffer);

   buffer.compact();

   System.out.println("-------------compact......");

   System.out.println("mark: " + buffer);

   buffer.clear();

   System.out.println("-------------clear......");

   System.out.println("mark: " + buffer);

  

 

  执行结果:
 

  postition: 表示写入时候的指针位置

  limit:写入和读取时候指针截止位置(写入和读取时会切换位置)

  capacity:表示buffer剩余空间,总大小。如果前面有已经读取过的字节,不能算在内。所以写入的时候需要capacity一下

  postition 和 limit 两个指针位置在读或写的时候联动发生变化

  抽象逻辑图:
 

  刚开始:
 

  put了123,3个字节后
 

  flip() 翻转,翻转的目的是读写交替时候
 

  compact()后,得到的buffer剩余的空间
 

  基于文件的NIO

  测试代码:

  

//测试文件NIO

 

   public static void testRandomAccessFileWrite() throws Exception {

   // 使用RandomAccessFile(); 给出文件路径,给出权限,r表示可读,w表示可写

   RandomAccessFile raf = new RandomAccessFile(path, "rw");

   // write()表示普通的写,

   raf.write("hello mashibing\n".getBytes());

   raf.write("hello seanzhou\n".getBytes());

   System.out.println("write------------");

   System.in.read();

   // seek()指定字符下标指针偏移位置,写的内容会覆盖掉之前的字符。

   // 因为向文件写入数据的时候只能一直向后写,如果想要postition指针向前,是不行的,所以调用seek()方法可以修改postition指针指定位置。

   raf.seek(4);

   raf.write("ooxx".getBytes());

   System.out.println("seek---------");

   System.in.read();

   // getChannel()获取io通道,

   FileChannel rafchannel = raf.getChannel();

   //mmap 堆外 和文件映射的 byte not objtect

   MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);

   // 只有文件系统有map()方法,只有map()方法返回MappedByteBuffer(堆外的,并且和文件映射的)

   // 如果是ByteBuffer上面获取,只能获取堆上的和堆外的内存空间

   map.put("@@@".getBytes()); //不是系统调用 但是数据会到达 内核的pagecache

   //曾经我们是需要out.write() 这样的系统调用,才能让程序的data 进入内核的pagecache

   //曾经必须有用户态内核态切换

   //mmap的内存映射,依然是内核的pagecache体系所约束的!!!

   //换言之,pagecache体系会丢数据

   //你可以去github上找一些 其他C程序员写的jni扩展库,使用linux内核的Direct IO(直接io)

   //直接IO是忽略linux的pagecache

   //是把pagecache 交给了程序自己开辟一个字节数组当作pagecache,动用代码逻辑来维护一致性/dirty(脏数据)。。。一系列复杂问题

   System.out.println("map--put--------");

   System.in.read();

   // map.force(); // 等于flush,刷写数据到磁盘

  
ByteBuffer buffer = ByteBuffer.allocate(8192);

   // ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

   int read = rafchannel.read(buffer); //buffer.put()

   System.out.println(buffer);

   buffer.flip();

   System.out.println(buffer);

   for (int i = 0; i buffer.limit(); i++) {

   Thread.sleep(200);

   System.out.print(((char)buffer.get(i)));

  
抽象原理逻辑图:
 

  在整个操作系统中,有txt(编写的程序),data数据,堆内存(操作系统的)
 

  txt自己编写的程序会向操作系统的堆内存分配一块空间,放在了jvm的堆里面
 

  如果使用ByteBuffer使用allocate,相当于分配到了jvm的堆上(on heap),如果使用allocateDirect相当于分配到了java进程的堆空间里面(off heap)

  FileChannel对象会有一个map方法,得到一个MappedByteBuffer,
 

  MappedByteBuffer对象通过put方法映射到内核的pagecache,不会有系统调用。如果有脏数据,最终是写入磁盘的

  但如果是通过channel的read或者write方法,都是需要系统调用的,虽然都是最终到达内核的pagecache,都会有数据丢失的可能,这个目前没法避免。

  从逻辑上看allocateDirect 确实要比allocate 性能要高一些,
 

  所以当一个对象可以自己控制的时候,就尽量使用allocateDirect方法,当这个对象自己没法得到一个具体的时候,只能使用allocate

  性能对比:on heap off heap mapped(只限于文件系统)

  既然pagecache没法确保数据不丢失,为什么还要使用?
 

  **OS操作系统 没有觉得数据可靠性为什么设计pagecache: **
 

  减少硬件 IO 调用,这样可以提速,优先使用内存。
 

  即便你想要可靠性,调成最慢的方式,但是单点问题会让你的性能损耗,一毛钱收益都没有。
 

  所以要主从复制,主备HA,kafka/ES 都有副本概念(通过socket io) (副本又分 同步/异步)。。。

  以上就是2、java文件系统io()的详细内容,想要了解更多 2、java文件系统io的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: