用户进程发起请求,内核接收到请求后,从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 | 通常作为编译器的扫描器 |
方法介绍
读取数据
// 每次读取一个字节
public abstract int read() throws IOException
// 使用字节数组作为缓冲区,将读到的字节填充至缓冲区中
public int read(byte b[]) throws IOException
// 将读到的字节填充至字节数组的指定位置上
// off 起始位置
// len 长度
public int read(byte b[], int off, int len) throws IOException
关闭流
使用close方法来关闭流,在1.7及之后建议使用try-with-resources语句来使用流,这样可以避免显示的调用close()方法,减少一些重复代码
public void close() throws IOException
跳过指定字节
可以使用skip方法来跳过指定数目的字节,相当于把流中当前读到的位置向后移动若干个字节。
public long skip(long n) throws IOException
注意:该方法可能在向后移动的时候,没有达到指定字节就到达了流的末尾,所以并不一定会跳过指定的字节,该方法返回值为实际跳过的字节数
标记与重置
标记与重置方法一般联合使用,以实现流中部分内容可重复读取
// mark方法在当前读取位置进行标记
// readlimit表示允许重复读取的字节,只能从标记的位置再次重复向后读取所指定的字节
public synchronized void mark(int readlimit)
// reset方法将流的当前读取位置移动到上次标记的位置
public synchronized void reset() throws IOException
// 不是所有的流都支持标记功能,需要使用该方法来判断当前流是否支持标记功能
public boolean markSupported()
可读字节数
当read方法被调用时,如果当前流中没有可用的数据,该操作就会被阻塞,available()方法就是返回当前流中还有多少字节可以读取
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,可以指定缓冲区大小 | 只是向进程添加缓冲功能 |
方法介绍
写入数据
// 每次写入一个字节
public abstract void write(int b) throws IOException
// 使用字节数组作为缓冲区,写入整个字节数组的内容
public void write(byte b[]) throws IOException
// 写入字节数组的部分内容
// off 起始位置
// len 长度
public void write(byte b[], int off, int len) throws IOException
关闭流
使用close方法来关闭流,在1.7及之后建议使用try-with-resources语句来使用流,这样可以避免显示的调用close()方法,减少一些重复代码
public void close() throws IOException
刷新
flush()方法用来强制要求OutputStream对象将暂存于内部缓冲区的数据立即进行实际的写入(一般情况下不需要手动的调用该方法,在内部缓冲区填充满了之后,会自动执行实际的写入操作,在调用close()方法时也会自动调用flush()方法)
有些OutputStream类中维护内部缓冲区是为了减少实际的写入操作次数,来提升性能
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方法
既可以读文件,又可以写文件,可以进行文件的随机读写
// 有两种模式,"rw"读写 和 "r"只读
RandomAccessFile faf = new RandomAccessFile(file,"rw");
// 文件指针,打开文件时指针在开头pointer=0
// 写操作,只写一个字节,同时指针指向下一个位置,准备再次写入
raf.write(int)
// 读文件时要把指针移到头部
raf.seek(0);
// 读操作,读一个字节
int b = raf.read()
常用的IO操作
缓冲输入文件
想要打开一个文件进行字符输入,可以使用FileReader对象,然后传入一个String或者File对象作为文件名。为了提高速度,对文件进行缓冲,可以将所产生的引用传递给一个BufferedReader构造器。BufferedReader提供了lines()方法,会产生一个Stream 对象
public static void read(String file) {
try(BufferedReader bufferedReader = new BufferedReader(new FileReader(file))){
String line = null;
while((line = bufferedReader.readLine()) != null){
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException("读取失败",e);
}
}
读取字符
public static void read() throws IOException {
StringReader stringReader = new StringReader("qaw试试");
int c;
while ((c = stringReader.read()) != -1) {
System.out.println((char)c);
}
}
StringReader的read方法是以int形式返回的下一个字节,所以打印的时候类型必须转为char
格式化数据
要读取格式化数据,可以使用DataInputStream,是面向字节的,所以要使用InputStream类
文件输出
FileWrite对象用于向文件写入数据,通常使用BufferedWriter将其包装起来增加缓冲的功能,
try(BufferedReader in = new BufferedReader(new StringReader("1111111111111111\n2222222222222\n3333333333"));
PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter("test.txt")))
){
in.lines().forEach(printWriter :: println);
} catch (IOException e) {
e.printStackTrace();
}
标准IO
标准输入流Systom.in、标准输出流System.out、标准错误流Sytem.err。
System.out和System.err是预先包装成了PrintStream对象,但是System.in没有进行包装,属于原生的InputStream,所以在读取时需要对其进行包装
标准输入
通常一行一行地读取输入,将System.in包装成BufferedReader
public static void main(String[] args) {
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.equals("end")) {
break;
}
System.out.println(line);
}
} catch (IOException e) {
}
}
重定向标准I/O
System类提供了简单的静态方法调用,重定向标准输入流、标准输出流和标准错误流
-
setIn(InputStream)
-
setOut(PrintStream)
-
setErr(PrintStream)
public static void main(String[] args) {
try(BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("C:\\Users\\sinosoft\\Desktop\\剩余工作.txt"));
PrintStream printStream = new PrintStream(new BufferedOutputStream(new FileOutputStream("C:\\Users\\sinosoft\\Desktop\\剩余工作副本.txt")))
){
System.setIn(bufferedInputStream);
System.setOut(printStream);
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.equals("end")) {
break;
}
System.out.println(line);
}
} catch (IOException e1) {
}
}catch (IOException e){
}
}
序列化流和反序列化流
对象的序列化就是将对象转化为byte序列,反之叫做反序列化,ObjectOutputStream是序列化流,ObjectInputStream是反序列化流
序列化接口
对象必须实现序列化接口Serializable,才可以进行序列化,该接口是一个标准,如果不想某个字段进行序列化,可以使用transient来修饰字段,使得该字段不进行序列化,可以通过重写writeObject和readObject方法来进行手动序列化
静态变量也不能进行序列化,因为所有的对象都共享同一份静态变量值
ObjectOutputStream序列化流
主要的方法是writeObject,存储对象的类、类的签名以及这个类及其父类中所有非静态和非瞬时的字段的值
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
ObjectInputStream反序列化流
主要的方法是readObject,读回对象的类、类的签名以及这个类及其父类中所有非静态和非瞬时的字段的值
public final Object readObject()
throws IOException, ClassNotFoundException {
return readObject(Object.class);
}
在反序列化生成对象时,构造函数并不会执行,因为反序列化是要得到存储时的状态,如果调用构造函数就会生成新的