同步与异步,阻塞与非阻塞
同步:当前线程发起了一个调用或请求,然后当前线程需要等待该调用结束返回结果才能继续往下进行其他操作。
异步:当前线程发起了一个调用或请求,然后当前线程不需等待调用的执行结果就可以继续往下执行(请求交由另一个线程去执行),之后可以通过被调用者的状态改变或者被调用者主动发出通知来获得执行结果。
阻塞:也可以说是等待,调用者发起请求如果不能立即拿到返回结果就一直等,等到结果完成。
非阻塞:调用者发起请求之后立即返回,没有等待的过程,但是要自己不断去检查是否已有结果。
同步异步讲的是消息通信机制,关注点侧重于线程与线程之间的协作。而阻塞、非阻塞更侧重于调用者在等待结果时的状态。
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体系

分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问字符串 | | | StringReader | StringWriter |
缓冲流 | Buffered<br/>InputStream | Buffered<br/>OutputStream | BufferedReader | BuffferedWriter |
转换流 | | | InputStreamReader | OutputStreamWriter |
对象流 | ObjectInputStream | ObjectOutputStream | | |
打印流 | | PrintStream | | PrintWriter |
推回输入流 | PushbackInputStream | | PushBackReader | |
特殊流 | DataInputStream | DataOutputStream | | |
访问数组 | ByteArray<br/>InputStream | ByteArray<br/>OutputStream | CharArray<br/>Reader | CharArray<br/>Writer |
访问管道流 | Piped<br/>InputStream | Piped<br/>OutputStream | Piped<br/>Reader | Peped<br/>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!"); } }