Java BIO

Wesley13
• 阅读 626

同步与异步,阻塞与非阻塞

同步:当前线程发起了一个调用或请求,然后当前线程需要等待该调用结束返回结果才能继续往下进行其他操作。

异步:当前线程发起了一个调用或请求,然后当前线程不需等待调用的执行结果就可以继续往下执行(请求交由另一个线程去执行),之后可以通过被调用者的状态改变或者被调用者主动发出通知来获得执行结果。

阻塞:也可以说是等待,调用者发起请求如果不能立即拿到返回结果就一直等,等到结果完成。

非阻塞:调用者发起请求之后立即返回,没有等待的过程,但是要自己不断去检查是否已有结果。

同步异步讲的是消息通信机制,关注点侧重于线程与线程之间的协作。而阻塞、非阻塞更侧重于调用者在等待结果时的状态。

Java中的同步IO:线程A发起了一个IO请求,那么就由java程序去执行io操作,并且是由当前线程去做。

Java中的异步IO就是当前线程把IO请求委托给了另外一个线程去做。

Java中的阻塞IO就是线程A发起了IO请求,如果此时所需IO资源被占用,那么线程A就要被挂起,等待其他线程释放IO资源分配给了A,A才能执行IO操作。

Java中的非阻塞IO是指线程A发起了一个IO请求,同样IO资源被占用,但是A不被挂起,而是立刻返回,之后每隔一段时间再去看看IO资源是否空闲。

BIO

BIO是java最早期的IO,位于java.io包下,它主要面向的对象是流。

分类

IO流是用来处理设备之间的数据传输,是有起点和终点的字节结合。按数据传输方向可划分为输入流、输出流——相对于内存设备而言,将外设中的数据读取到当前程序中为输入,将程序中的数据写出到外设中为输出。

按是否阻塞划分为阻塞流、非阻塞流

按操作的数据对象可以划分为字节流、字符流——字符流实际上是字节流+编码表,即字节流读取文字字节数据后不直接操作而是先查对应的编码表,获取对应的文字,再对这个文字进行操作,因此可以说传统的IO都是基于字节Byte的,简称BIO。

四个顶级抽象父类

  • 字节输入流:InputStream
  • 字节输出流:OutputStream
  • 字符输入流:Reader
  • 字符输出流:Writer

IO体系

Java BIO

分类

字节输入流

字节输出流

字符输入流

字符输出流

抽象基类

InputStream

OutputStream

Reader

Writer

访问文件

FileInputStream

FileOutputStream

FileReader

FileWriter

访问字符串

StringReader

StringWriter

缓冲流

Buffered
InputStream

Buffered
OutputStream

BufferedReader

BuffferedWriter

转换流

InputStreamReader

OutputStreamWriter

对象流

ObjectInputStream

ObjectOutputStream

打印流

PrintStream

PrintWriter

推回输入流

PushbackInputStream

PushBackReader

特殊流

DataInputStream

DataOutputStream

访问数组

ByteArray
InputStream

ByteArray
OutputStream

CharArray
Reader

CharArray
Writer

访问管道流

Piped
InputStream

Piped
OutputStream

Piped
Reader

Peped
Writer

其他常用类

File:File类是对文件系统中的文件和文件夹进行抽象的对象,通过File类可以访问文件或文件夹的各种数据信息和操作,比如文件名、文件长度、最后修改时间、是否可读、获取路径、判断是否存在、创建和删除文件、目录。

RandomAccessFile:RandomAccessFile封装了字节流,同时还封装了一个字符数组缓冲区,通过内部的指针去操作字符数组中的数据。

java.io 范围

java.io 包主要涉及标准输入输出、错误输出(System.in,System.out,System.error)、文件、管道、网络数据流、内存缓存等的输入输出。 主要学习各个类的设计目的和使用场景。

流的概念

流从概念上来说是一个连续的数据流,不能通过索引读写数据,只能顺序访问流中的数据。流又可以根据读写单位粒度不同分为字节流和字符流,BIO是通过从流中读取数据,往流中写入数据从而达到数据源与目的媒介的关联。

java.io不常用字节、字符流

管道流

管道:Java中的管道是为了让运行在同一个JVM中的两个线程之间能进行通信的一种手段,通过管道通信的双方应该是运行在同一个进程中的不同线程,而在Unix/Linux中的管道概念是能让运行在不同地址空间的进程通过管道通信。

管道流示例 要点:输入流要关联输出流;相关联的流要在不同线程使用。

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class PipeStreamDemo {

    PipedOutputStream out;
    PipedInputStream in;

    /**
     * 管道流示例: Java中的管道流只能用于同一个进程中的不同线程通信, 一个管道输入流必须关联一个管道输出流,
     * 一个线程往管道输出流中写数据,另一个线程往管道输入流中取数据。
     * 
     * @throws IOException
     */
    public void PipeDemo() throws IOException {
        out = new PipedOutputStream();
        in = new PipedInputStream(out);// 一个输入流要跟一个输出流关联
        // 相关联的管道输入流和输出流必须在不同线程使用,
        // 因为read和write方法会导致流阻塞,在同一个线程会导致死锁
        // 以下两个线程的启动顺序无关
        new Thread(() -> {
            try {
                out.write("this is a pipe stream demo".getBytes());
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                byte[] result = new byte[1024];
                int b = in.read(result);
                while (b != -1) {
                    System.out.println(new String(result));
                    b = in.read(result);
                }
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }

    public static void main(String[] args) throws IOException {
        PipeStreamDemo demo = new PipeStreamDemo();
        demo.PipeDemo();
    }
}

标准流

System.in、System.out、System.err是三个常见的标准流,使用得最多的是System.out在控制台程序里将输出打印到控制台。这三个对象是在JVM启动时就完成了初始化,程序中可以直接使用。

System.out与System.err最主要的区别是System.out是可以被重定向的系统流,而System.err不能被重定向,使用System.setIn()和System.setOut()可以重定向System.in和System.out。

并发问题

在同一时刻不能有多个线程同时从InputStream或者Reader中读取数据,也不能同时往OutputStream或者Writer里写数据。因为无法保证每个线程读取多少数据,以及多个线程写数据时的顺序。如果能保证操作的顺序,那么使用同一个stream、reader、writer则是可行的。

RandomAccessFile

RandomAccessFile可以来回读写文件,也可以替换文件中的某些部分。通过RandomAccessFile实例的seek(int) 方法能将文件指针移动到指定位置,从而在此处开始进行读写。

字节数组流

ByteArrayInputStream和ByteArrayOutputStream是用来读写字节数组的流对象。

推回输入流 PushbackInputStream

PushbackInputStream用于解析InputStream中的数据,有时候我们需要提前知道接下来将要读取到的字节内容,才能判断用何种方式进行数据解析,PushbackInputStream允许将读取到的字节重新推回到InputStream中,以便再次通过read()读取。示例代码如下:

PushbackInputStream in = new PushbackInputStream(new FileInputStream(file));
int data = in.read();
//do something
in.unread(data); //将上一次读取到的data个字节推回流中

顺序输入流SequenceInputStream

SequenceInputStream把一个或者多个InputStream整合起来,形成一个逻辑连贯的输入流。当读取SequenceInputStream时,会先从第一个输入流中读取,完成之后再从第二个输入流读取,依次推类。

InputStream input1 = new FileInputStream(file);
InputStream input2 = new FileInputStream(file2);
InputStream combined = new SequenceInputStream(input1, input2);

LineNumberReader

LineNumberReader是记录了已读取数据行号的BufferedReader。默认情况下,行号从0开始,当LineNumberReader读取到行终止符时,行号会递增。 LinuNumberReader的setLineNumber()仅仅改变LineNumberReader内的记录行号的变量值,不会改变当前流的读取位置。流的读取依然是顺序进行,意味着你不能通过setLineNumber()实现流的跳跃读取。

分词流 StreamTokenizer

StreamTokenizer可以把InputStream和Reader分解成一系列符号,比如将英语句子分解成一个个单词。

StringReader sr = new StringReader("How old are you? I am 20.");
StreamTokenizer tokenizer = new StreamTokenizer(sr);

while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) { //流末尾
    if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
        System.out.println(tokenizer.sval); //字符串类型
    } else if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
        System.out.println(tokenizer.nval); //数字类型
    } else if (tokenizer.ttype == StreamTokenizer.TT_EOL) { //行末尾
        System.out.println("exit!");
    } 
}
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
IO模型(BIO,NIO,AIO)及其区别
BIO:同步阻塞IONIO:同步非阻塞IOAIO:异步非阻塞IO先弄清楚同步、异步,阻塞、非阻塞概念。io操作分为两部分,发起io请求,和io数据读写。阻塞、非阻塞主要是针对线程发起io请求后,是否立即返回来定义的,立即返回称为非阻塞io,否则称为阻塞io。同步、异步主要针对io数据读写来定义的,读写数据过程中不阻塞线程称为异步io
Wesley13 Wesley13
3年前
JAVA中的BIO、NIO和AIO
Java中的IO方式主要分为3种:BIO(同步阻塞)、NIO(同步非阻塞)和AIO(异步非阻塞)。BIO同步阻塞模式。在JDK1.4以前,使用Java建立网络连接时,只能采用BIO方式,在服务器端启动一个ServerSocket,然后使用accept等待客户端请求,对于每一个请求,使用一个线程来进行处理用户请求。线程的大部分时间都在等待请求的
Wesley13 Wesley13
3年前
BIO、NIO、AIO 介绍和适用场景分析
IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。一、同步阻塞的BIO在JDK1.4之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个serverSocket,然后在客户端启动socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否
Stella981 Stella981
3年前
Linux网络IO模型
同步和异步,阻塞和非阻塞_同步和异步_关注的是结果消息的通信机制同步:同步的意思就是调用方需要主动等待结果的返回异步:异步的意思就是不需要主动等待结果的返回,而是通过其他手段比如,状态通知,回调函数等。_阻塞和非阻塞_主要关注的是等待结果返回调用方的状态阻塞:是指
Easter79 Easter79
3年前
Spring注解@Scheduled 多线程异步执行
一、前言:Spring定时任务@Schedule的使用方式,默认是单线程同步执行的,启动过程是一个单线程同步启动过程,一旦中途被阻塞,会导致整个启动过程阻塞,其余的定时任务都不会启动。二、@Schedule注解多线程的实现:多个定时任务的执行,通过使用@Async注解来实现多线程异步调用。@Scheduled(
Wesley13 Wesley13
3年前
Java CyclicBarrier介绍
CyclicBarrier(周期障碍)类可以帮助同步,它允许一组线程等待整个线程组到达公共屏障点。CyclicBarrier是使用整型变量构造的,其确定组中的线程数。当一个线程到达屏障时(通过调用CyclicBarrier.await()),它会被阻塞,直到所有线程都到达屏障,然后在该点允许所有线程继续执行。与CountDownLatch不同的
Stella981 Stella981
3年前
Linux的五种IO模型?
IO的同步、异步、阻塞、非阻塞同步、异步同步(synchronous):A调用B,B立刻处理A的请求(即使C紧接着调用B),并把最终结果返回给A。异步(asynchronous):A调用B,B立刻反馈A,仅是状态,并非最终结果。B处
Wesley13 Wesley13
3年前
NIO
一、什么是阻塞和非阻塞?传统的IO流都是阻塞式的。也就是说,当一个线程调用read()或write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行IO操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端