广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java IO流深入理解
  • 200
分享到

Java IO流深入理解

2024-04-02 19:04:59 200人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录阻塞(Block)和非阻塞(Non-Block)同步(Synchronization)和异步(Asynchronous)BIO与NIO对比面向流与面向缓冲阻塞与非阻塞选择器的问世

阻塞(Block)和非阻塞(Non-Block)

阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候。

**阻塞:**往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否者一直等待在那里

**非阻塞:**当我们进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。

同步(Synchronization)和异步(Asynchronous)

同步和异步都是基于应用程序和操作系统处理IO事件所采用的方式。比如同步:是应用程序要直接参与IO读写的操作。异步:所有的IO读写交给操作系统去处理,应用程序只需要等待通知。

同步方式在处理IO事件的时候,必须阻塞在某个方法上面等待我们的IO事件完成(阻塞IO事件或者通过轮询IO事件的方式),对于异步来说,所有的IO读写都交给了操作系统。这个时候,我们可以去做其他的事情,并不需要去完成真正的IO操作,当操作完成iO后,会给我们的应用程序一个通知。

**同步:**阻塞到IO事件,阻塞到read或者write。这个时候我们就完全不能做自己的事情。让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大。

BIO与NIO对比

IO模型 BIO NIO
通信 面向流(乡村公路) 面向缓冲(高速公路,多路复用技术)
处理 阻塞IO(多线程 非阻塞IO(反应堆Reactor)
触发 选择器 轮询机制

面向流与面向缓冲

java NIO和BIO之间第一个最大的区别是,BIO是面向流的,NIO是面向缓冲的。Java BIO面向流意味着每次从流中读一个或多个字节,直至读取所有的字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否缓冲区包含了所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

阻塞与非阻塞

Java BIO的各种流是阻塞的。这意味着,当一个线程调用read()和write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么多不会获取。而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。

非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道。

选择器的问世

java NIO的选择器(Selector)允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道,这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

Java NIO三件套

在NIO中有几个核心对象需要掌握:缓冲器(Buffer)选择器(Selector)通道(Channel)

缓冲区Buffer

缓冲区实际上是一个容器对象,更直接的说,其实就是一个数组,在NIO库中,所有数据都是用缓冲区出来的。在读取数据时,它是直接读到缓冲区的;在写入数据时,它也是写入到缓冲区的;任何时候访问NIO中的数据,都是将它放到缓冲区中。而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。

在NIO中,所有的缓冲区类型都继承于抽象类Buffer,最常用的就是ByteBuffer,对于java中的基本类型,基本都有一个具体Buffer类型与之相对于,他们之间的extend关系如下图所示。

在这里插入图片描述

eg:


  public static void main(String[] args) {
        //new NiOServerDemo(8080).listen();

        // 分配新的 int 缓冲区,参数为缓冲区容量
        // 新缓冲区的当前位置将为零,其界限(限制位置)将为其容量。它将具有一个底层实现数组,其数组偏移量将为零。
        IntBuffer buffer = IntBuffer.allocate(8);
        for (int i = 0; i < buffer.capacity(); ++i) {
            int j = 2 * (i + 1);
            // 将给定整数写入此缓冲区的当前位置,当前位置递增
            buffer.put(j);
        }
        // 重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为 0
        buffer.flip();
        // 查看在当前位置和限制位置之间是否有元素
        while (buffer.hasRemaining()) {
            // 读取此缓冲区当前位置的整数,然后当前位置递增
            int j = buffer.get();
            System.out.print(j + " ");
        }
    }

2 4 6 8 10 12 14 16 
Process finished with exit code 0

Buffer的基本的原理

在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。如果我们使用get()方法从缓冲区获取数据或者使用put()方法把数据写入缓冲区,都会引起缓冲区状态的变化。

在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部的状态的变化跟踪。

position: 指定下一个将要被写入或者读取的元素索引,它的值由get()/put()方法自动更新,在新创建一个Buffer对象时,position被初始化为0.

limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)

**capacity:**制定了可以存储在缓冲区中的最大数据容量,实际上,它制定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。

以上三个属性值之间有一些相对大小的关系: 0<=positon<=limit<=capacity。如果我们创建一个新的容量大小为10的ByteBuffer对象,在初始化的时候,positon设置为0,limit和capacity被设置为10,在以后使用ByteBuffer对象过程中,capacity的值不会再发生变化,而其他两个将会随着使用而变化。

eg:


package com.evan.Netty.nio.demo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;


public class BufferDemo {
    public static void main(String[] args) throws IOException {

        FileInputStream fin = new FileInputStream("D://evan.txt");
        FileChannel fc = fin.getChannel();
        //先分配一个10大小的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(10);
        outPut("初始化",buffer);

        fc.read(buffer);
        outPut("调用read()方法",buffer);

        buffer.flip();
        outPut("调用flip()",buffer);

        //判断有没有可读数据
        while (buffer.remaining()>0){
            byte b = buffer.get();
        }

        outPut("调用get()",buffer);

        //可以理解为解
        buffer.clear();
        outPut("调用clear()",buffer);

        fin.close();
    }

    
    public static void outPut(String step, Buffer buffer){
        System.out.println(step+":");
        System.out.println("capacity: "+buffer.capacity()+",");
        System.out.println("position: "+buffer.position()+",");
        System.out.println("limit: "+buffer.limit());
        System.out.println();
    }
}

文件中的数据

在这里插入图片描述

输出结果:

在这里插入图片描述

运行结果我们已经可以知道,四个属性值分别如图所示:

在这里插入图片描述

我们可以从管道中读取一些数据到缓冲区,注意从通道读取数据,相当于往缓冲区写入数据。如果读取4个自己的数据,则此时position的值为4,即下一个将要被下入的字节索引是4,而limit仍然是10,如下图所示:

在这里插入图片描述

下一步把读取的数据写入到输出管道中,相当于从缓冲区中读取数据,在此之前,必须调用flip()方法,该方法将会完成两件事:

1,把limit设置为当前的positon值

2,把position设置为0

由于position被设置为0,所以可以保证在下一步输出时读取到的是缓冲区中的第一个字节,而limit被设置为当前的position,可以保证读取的数据正好是之前写入到缓冲区的数据,如下图所示。

在这里插入图片描述

现在调用get()方法从缓冲区读取数据写入到输出通道,这会导致position的增加而limit保持不变,单position不会超过limit的值,所以在读取我们之前写入到缓冲区中的4个自己之后,position和limit的值都为4.如下图所示。

在这里插入图片描述

在从缓冲区读取数据完毕后,limit的值仍然保持在我们调用flip()方法时的值,调用clean()方法能够把所有的状态设置为初始值。

缓冲区分配

在前面的几个例子中,我们已经看过了,在创建一个缓冲对象时,会调用静态方法allocate()来指定缓冲区的容量,其实调用allocate()相当于创建一个指定大小的数组,并把它包装为缓冲区对象。或者我们也可以直接将一个现有的数组,包装为缓冲区对对象

选择器Selector

传统的Server/Client 模式会基于TPR(Thread per Request),服务器会为每个客户端请求建立一个线程,由该线程单独负责处理一个客户请求。这种模式带来的一个问题就是线程数量的剧增,大量的线程会增大服务器的开销。大多数的实现为了避免这个问题,都采用了线程池模型,并设置线程池模型的最大数量,这又带来了新的问题,如果线程池中有200个线程,而有200个用户都在进行大文件下载,会导致第201个用户的请求无法及时处理,即便第201个用户只想请求一个几kb大小的页面。传统的Server/Client模式如下图所示。

在这里插入图片描述

NIO 中非阻塞I/O采用了基于Reactor模式的工作模式,I/O调用不会被阻塞,相反是注册感兴趣的特定I/O事件,如可读数据到达,新的套接字连接等等,在发生特定事件时,系统在通知我们。NIO中实现非阻塞I/O的核心对象就是Selector,Selector就是注册各种I/O事件地方,而且当那些事件发生时,就是这个对象告诉我们所发生的事件。如下图所示。

在这里插入图片描述

从图中可以看出,当有读或写等任何注册的时间发生时,可以从Selector中获得相应的SelectionKey,同时从SelectionKey中可以找到发生的事件和该事件所发生的具体SelectableChannel,以获得客户端发送过来的数据。

使用NIO中非阻塞I/O编写服务器处理程序,大体上可以分为下面三个步骤:

1,向Selector对象注册感兴趣的事件。

2,从Selector中获取感兴趣的事件。

3,根据不同的事件进行相应的处理。

通道Channel

通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓存区。同样不会直接从通道中读取字节,而是将数据从通道读入缓存区,再从缓冲区获取这个字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

在NIO中,提供了多种通道对象,而所有的通道对象都实现了Channel接口。它们之间的继承关系如下图所示:

在这里插入图片描述

使用NIO读取数据

在前面我们说过,任何时候读取数据,都不是直接从通道读取,而是从通道读取到缓冲区。所以使用NIO读取数据可以分为下面三个步骤:

1,从FileInputStream获取Channel

2,创建Buffer

3,将数据从Channel读取到Buffer中


package com.evan.netty.nio.demo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;


public class FileInputDemo {
    public static void main(String[] args) throws IOException {
        FileInputStream fin=new FileInputStream("D://evan.txt");
        FileChannel channel = fin.getChannel();

        ByteBuffer allocate = ByteBuffer.allocate(1024);
        //读取数据到缓冲区
        channel.read(allocate);

        allocate.flip();
        while (allocate.remaining()>0){
            byte b = allocate.get();
            System.out.println(b);
        }
        fin.close();
    }
}

使用NIO写入数据

使用NIO写入数据与读取数据的过程类似,同样数据不是直接写入通道,而是写入缓冲区,可以分为下面三个步骤:

1,从FileputStream获取channel。

2,创建Buffer

3,将数据从Channel写入到Buffer中,


package com.evan.netty.nio.demo;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;


public class FileOutPutDemo {
    static private final byte message[] ={83, 111, 109, 101, 32, 98, 121, 116, 101, 115, 46 };
    public static void main(String[] args) throws IOException {
        FileOutputStream fout=new FileOutputStream("D://evan.txt");

        FileChannel channel = fout.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        for (int i = 0; i < message.length; i++) {
            buffer.put(message[i]);
        }
        buffer.flip();
        channel.write(buffer);
        fout.close();
    }
}

在这里插入图片描述

IO多路复用

我们试想一下这样的现实场景。

100桌客人到店点菜

方法A:

服务员都把仅有的一份菜单递给其中一桌客人,然后站在这个客人身旁等待客人完成点菜过程。。。。。

方法B:

老板马上新雇佣99名服务员,同时印制99本新的菜单。没人服务一桌客人。

在这里插入图片描述

方法C:

改进点菜的方式,当客人到店后,自己申请一本菜单。想好自己要点的菜,然后呼叫服务员。服务员站在自己身边记录客人点的菜的内容。

在这里插入图片描述

  • 到店情况 :并发
    • 到店情况不理想时,一个服务员一本菜单,就足够了
  • 客人:服务端请求
  • 点餐内容:客服端发送的实际数据
  • 老板:操作系统
  • 人力成本:系统资源
  • 菜单:文件状态描述符(FD)。操作系统对于一个进程能够同时持有的文件状态描述符的个数是有限制的,在linux系统中,$Ulimit -n 查看这个限制值,当然也是可以(并且应该)进行内核参数调整的
  • 服务员:操作系统内核用于IO操作的线程(内核线程)
  • 厨师:应用程序线程(当然厨房就是应用程序进程)
    • 方法A:同步IO
    • 方法B:同步IO
    • 方法C:多路复用IO

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注编程网的更多内容!

--结束END--

本文标题: Java IO流深入理解

本文链接: https://www.lsjlt.com/news/131391.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java IO流深入理解
    目录阻塞(Block)和非阻塞(Non-Block)同步(Synchronization)和异步(Asynchronous)BIO与NIO对比面向流与面向缓冲阻塞与非阻塞选择器的问世...
    99+
    2022-11-12
  • 深入浅析Java中IO流的字节流
    这期内容当中小编将会给大家带来有关深入浅析Java中IO流的字节流,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java中IO流 字节流实例详解IO流(输入流、输出流),又分为字节流、字符流。  流是磁盘...
    99+
    2023-05-31
    java io流 字节流
  • 深入浅析Java 中的IO流字符流
    这期内容当中小编将会给大家带来有关深入浅析Java 中的IO流字符流,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java—IO流 字符流  java的文本(char)是16位无符号整数,是字符的unic...
    99+
    2023-05-31
    java io流 字符流
  • 深入浅析Java中IO流的 RandomAccessFile类
    这篇文章将为大家详细讲解有关深入浅析Java中IO流的 RandomAccessFile类,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。RandomAccessFilejava提供的对文件内...
    99+
    2023-05-31
    java randomaccessfile io流
  • 深入浅析Java中 IO流的继承结构
    深入浅析Java中 IO流的继承结构?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Java IO体系结构看似庞大复杂,其实有规律可循,要弄清楚其结构,需要明白两点: 其对称性质...
    99+
    2023-05-31
    java io流 继承结构
  • 深入了解Android IO的底层原理
    目录前言一、应用层1. IO的分类1.1 缓冲和直接1.2 阻塞和异步2. IO流程二、sysCall系统调用三、虚拟文件系统1. VFS结构2. VFS中的缓存3. IO流程四、文...
    99+
    2022-11-13
  • Java io流 详解
    文章目录 前言一、IO流的分类二、流的原理及流的数量三、Java IO流对象1. 输入字节流InputStream2. 输出字节流OutputStream 前言 "IO流"(Inpu...
    99+
    2023-09-17
    java 开发语言
  • JAVA—IO流详解
    1. 流概述 1.1. 什么是IO IO:Input/Output即输入&输出,也称之为流(河流,水流),指的是数据从一个地点到另一个地点过程;对于计算机来说文件的拷贝过程,文件的编写保存,显示功能都是使用到IO;将数据的传输过程看做一个输...
    99+
    2023-08-16
    java jvm 网络
  • 深入nodejs中流(stream)的理解
    nodejs的fs模块并没有提供一个copy的方法,但我们可以很容易的实现一个,比如: var source = fs.readFileSync('/path/to/source', {encoding...
    99+
    2022-06-04
    中流 nodejs stream
  • java基础入门之IO流
    目录io学习框架:文件:Io流的原理:节点流和处理流:BufferWriter:处理字节的处理流:标准输入和输出:转换流:打印流:Properties类:总结io学习框架: 文件:...
    99+
    2022-11-12
  • Java深入讲解AWT实现事件处理流程
    目录AWT的事件处理AWT中的事件继承图事件适配器小结AWT的事件处理 事件处理主要是为了响应用户的操作 事件对象(Event):封装了GUI组件上发生的特定事件(通常就是用户的一次...
    99+
    2022-11-13
  • java String的深入理解
    java String的深入理解一、Java内存模型 按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。     JVM主要管理两种类型内存...
    99+
    2023-05-31
    java string ava
  • JavaScript深入理解节流与防抖
    目录一、js防抖和节流二、为什么滚动scroll、窗口resize等事件需要优化三、滚动和页面渲染前端性能优化的关系四、防抖Debounce1 防抖Debounce情景2 防抖原理3...
    99+
    2022-11-13
  • Java异常处理深入理解
    目录图片解析:异常的处理:处理机制一:try-catch-finallyfinally的再说明:处理机制二:throws + 异常类型开发中应该如何选择两种处理方式?如何自定义异常类...
    99+
    2022-11-12
  • 深入理解Java中的HashMap
    目录一、HashMap的结构图示二、HashMap的成员变量以及含义2.1、hash方法说明2.2、tableSizeFor方法说明三、HashMap的构造方法四、HashMap元素...
    99+
    2022-11-12
  • 深入理解java泛型Generic
    目录一、背景二、泛型概念三、泛型类3.1 定义与调用3.2 注意3.3 使用3.4 泛型类的继承3.4.1 子类也是泛型类3.4.2 子类不是泛型类四、泛型接口4.1 定义4.2 使...
    99+
    2022-11-12
  • Java多线程深入理解
    目录线程Thread类Runnable接口创建线程Thread和Runnable的区别匿名内部类方式实现线程的创建线程安全线程安全线程同步同步方法Lock锁线程状态等待唤醒机制线程间...
    99+
    2022-11-12
  • Java反射之深入理解
    目录一、Java反射机制概述二、理解Class类并获取Class实例关于java.lang.Class类的理解三、通过反射创建对应的运行时类的对象(反射的应用1)四、获取运行时类的完...
    99+
    2022-11-12
  • 深入浅析Java NIO中的IO模型
    这期内容当中小编将会给大家带来有关深入浅析Java NIO中的IO模型,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。一.什么是同步?什么是异步  同步和异步的概念出来已经很久了,网上有关同步和异步的说法也...
    99+
    2023-05-31
    java io模型 nio
  • SpringBoot错误处理流程深入详解
    目录一、错误处理二、底层相关组件三、异常处理流程四、定制错误处理逻辑1、自定义错误页面2、使用注解或者默认的异常处理3、自定义异常处理解析器一、错误处理 默认情况下,Spring B...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作