用户进程发起请求,内核接收到请求后,从I/O设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数据后再响应客户端。
数据输入到buffer需要时间,从buffer复制数据至进程也需要时间,根据在这两段时间内等待方式不同,I/O动作可分为五种模式
- 阻塞I/O(Blocking I/O)
- 非阻塞I/O(Non-Blocking I/O)
- I/O复用(I/O Multiplexing)
- 信号驱动I/O
- 异步I/O
java的I/O操作在类的java.io包中
基于字节操作的I/O接口: InputStream和OutputStream
基于字符操作的I/O接口: Writer和Reader
基于磁盘操作的I/O接口: File
基于网络操作的I/O接口: Socket(网络编程,不在io包中)
普通IO
字节流对应原生的二进制数据
字符流对应字符数据,会自动处理与本地字符集之间的转换
缓冲流可以提高性能,通过减少底层API的调用次数来优化IO
字节流
读取文件是输入流,写文件是输出流(输入输出是对于程序而言的)
输入流
继承关系
字节输入流都继承自InputStream,InputStream表示从不同数据源产生输入的类,这些数据源包括
- 字节数组
- String对象
- 文件
- 管道,从一端输入,从另一端输出
- 一个由其他种类的流组成的序列,然后把它们汇聚为一个流
- 其他数据源,如Internet连接
类 | 功能 | 构造器 | 如何使用 |
---|---|---|---|
ByteArrayInputStream | 允许将内存的缓冲区当做InputStream使用 | 缓冲区,字节将从中取出 | 将其与FilterInputStream对象相连以提供有用接口 |
StringBufferInputStream(已废除) | 将String转换为InputStream | 字符串。底层实现实际使用StringBuffer | 将其与FilterInputStream对象相连以提供有用接口 |
FileInputStream | 用于从文件中读取信息 | 字符串,表示文件名、文件或FileDescriptor对象 | |
PipedInputStream | 产生用于写入相关PipedOutputStream的数据。实现管道化概念 | PipedOutputStream | 作为多线程中的数据源 |
SequenceInputStream | 将两个或多个InputStream对象转换成一个InputStream | 两个InputStream对象或一个容纳InputStream对象的容器Enumeration | |
FilterInputStream | 抽象类,作为装饰器的接口,为其他的InputStream类提供有用的功能 |
FilterInputStream类型子类包括以下几种
类 | 功能 | 构造器 | 如何使用 |
---|---|---|---|
DataInputStream | 与DataOutputStream搭配使用,按照移植方式从流读取基本数据类型 | InputStream | 包含用于读取基本数据类型的全部接口 |
BufferedInputStream | 使用它可以防止每次读取时都得进行实际写操作,提供了缓冲区的操作,提高IO的性能 | InputStream | 本质上不提供接口,只是向进程添加缓冲功能 |
LineNumberInputStream(已废除) | 跟踪输入流的行号,可调用getLineNumber()和setLineNumber(int) | InputStream,可以指定缓冲区大小 | 仅增加了行号 |
PushbackInputStream | 具有能弹出一个字节的缓冲区,因此可以将读到的最后一个字符回退 | InputStream | 通常作为编译器的扫描器 |
方法介绍
读取数据
1 | // 每次读取一个字节 |
关闭流
使用close方法来关闭流,在1.7及之后建议使用try-with-resources语句来使用流,这样可以避免显示的调用close()方法,减少一些重复代码
1 | public void close() throws IOException |
跳过指定字节
可以使用skip方法来跳过指定数目的字节,相当于把流中当前读到的位置向后移动若干个字节。
1 | public long skip(long n) throws IOException |
注意:该方法可能在向后移动的时候,没有达到指定字节就到达了流的末尾,所以并不一定会跳过指定的字节,该方法返回值为实际跳过的字节数
标记与重置
标记与重置方法一般联合使用,以实现流中部分内容可重复读取
1 | // mark方法在当前读取位置进行标记 |
可读字节数
当read方法被调用时,如果当前流中没有可用的数据,该操作就会被阻塞,available()方法就是返回当前流中还有多少字节可以读取
1 | public int available() throws IOException |
输出流
字节输入流都继承自OutputStream,该类决定了输出所要去的目标,字节数组、文件或管道
类 | 功能 | 构造器 | 如何使用 |
---|---|---|---|
ByteArrayOutputStream | 在内存中创建缓冲区。所有送往流的数据都要放置在此缓冲区 | 缓冲区初始大小 | 用于指定数据的目的地 |
FileOutputStream | 用于将信息写入文件 | 字符串,表示文件名、文件或FileDescriptor对象 | |
PipedOutputStream | 任何写入其中的信息都会自动作为相关PipedInputStream的输出,实现管道化概念 | PipedInputStream | 指定用于多线程的数据的目的地 |
FilterOutputStream | 抽象类,作为装饰器的接口,为其他OutputStream提供有用的功能 |
FilterOutputStream类型子类包括
类 | 功能 | 构造器 | 如何使用 |
---|---|---|---|
DataOutputStream | 与DataInputStream搭配使用,可以按照移植方式向流中写入基本数据类型 | OutputStream | 包含用于写入基本数据类型的全部接口 |
PrintStream | 用于产生改格式化输出。其中DataOutputStream处理数据的存储,PrintStream处理显示 | OutputStream,可以用boolean值指示是否每次换行时清空缓冲区 | 应该是对OutputStream对象的final封装 |
BufferedOutputStream | 使用它以避免每次发送数据时都进行实际的写操作,可以调用flush()清空缓冲区 | OutputStream,可以指定缓冲区大小 | 只是向进程添加缓冲功能 |
方法介绍
写入数据
1 | // 每次写入一个字节 |
关闭流
使用close方法来关闭流,在1.7及之后建议使用try-with-resources语句来使用流,这样可以避免显示的调用close()方法,减少一些重复代码
1 | public void close() throws IOException |
刷新
flush()方法用来强制要求OutputStream对象将暂存于内部缓冲区的数据立即进行实际的写入(一般情况下不需要手动的调用该方法,在内部缓冲区填充满了之后,会自动执行实际的写入操作,在调用close()方法时也会自动调用flush()方法)
有些OutputStream类中维护内部缓冲区是为了减少实际的写入操作次数,来提升性能
1 | public void flush() throws IOException |
字符流
InputStream和OutputStream是面向字节I/O的,而Reader和Writer则提供兼容Unicode和面向字符I/O的功能,InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer
RandomAccessFile类
适用于由大小已知的记录组成的文件,可以使用seek()将文件指针从一条记录移动到另一条记录,然后对记录进行读取和修改。文件中记录的大小不一定相同,只要我们能确定那些记录有多大以及它们在文件中的位置即可。
RandomAccessFile类不是InputStream或者OutputStream的子类,只是实现了DataInput和DataOutput接口,没有使用InputStream和OutputStream的任何功能,所有方法都是独立的,大部分为native方法
既可以读文件,又可以写文件,可以进行文件的随机读写
1 | // 有两种模式,"rw"读写 和 "r"只读 |
常用的IO操作
缓冲输入文件
想要打开一个文件进行字符输入,可以使用FileReader对象,然后传入一个String或者File对象作为文件名。为了提高速度,对文件进行缓冲,可以将所产生的引用传递给一个BufferedReader构造器。BufferedReader提供了lines()方法,会产生一个Stream<String>
对象
1 | public static void read(String file) { |
读取字符
1 | public static void read() throws IOException { |
StringReader的read方法是以int形式返回的下一个字节,所以打印的时候类型必须转为char
格式化数据
要读取格式化数据,可以使用DataInputStream,是面向字节的,所以要使用InputStream类
文件输出
FileWrite对象用于向文件写入数据,通常使用BufferedWriter将其包装起来增加缓冲的功能,
1 | try(BufferedReader in = new BufferedReader(new StringReader("1111111111111111\n2222222222222\n3333333333")); |
标准IO
标准输入流System.in、标准输出流System.out、标准错误流System.err。
System.out和System.err是预先包装成了PrintStream对象,但是System.in没有进行包装,属于原生的InputStream,所以在读取时需要对其进行包装
标准输入
通常一行一行地读取输入,将System.in包装成BufferedReader
1 | public static void main(String[] args) { |
重定向标准I/O
System类提供了简单的静态方法调用,重定向标准输入流、标准输出流和标准错误流
setIn(InputStream)
setOut(PrintStream)
setErr(PrintStream)
1 | public static void main(String[] args) { |
序列化流和反序列化流
对象的序列化就是将对象转化为byte序列,反之叫做反序列化,ObjectOutputStream是序列化流,ObjectInputStream是反序列化流
序列化接口
对象必须实现序列化接口Serializable,才可以进行序列化,该接口是一个标准,如果不想某个字段进行序列化,可以使用transient来修饰字段,使得该字段不进行序列化,可以通过重写writeObject和readObject方法来进行手动序列化
静态变量也不能进行序列化,因为所有的对象都共享同一份静态变量值
ObjectOutputStream序列化流
主要的方法是writeObject,存储对象的类、类的签名以及这个类及其父类中所有非静态和非瞬时的字段的值
1 | public final void writeObject(Object obj) throws IOException { |
ObjectInputStream反序列化流
主要的方法是readObject,读回对象的类、类的签名以及这个类及其父类中所有非静态和非瞬时的字段的值
1 | public final Object readObject() |
在反序列化生成对象时,构造函数并不会执行,因为反序列化是要得到存储时的状态,如果调用构造函数就会生成新的