IO流简述
IO流是一种顺序读写数据的模式,它的特点是单向流动。数据类似自来水一样在水管中流动,所以我们把它称为IO流。
I
是指输入Input
, O
是指输出Output
- Input指从外部读入数据到内存,例如,把文件从磁盘读取到内存,从网络读取数据到内存等。
- Output指把数据从内存输出到外部,例如,把数据从内存写入到文件,把数据从内存输出到网络等。
在读取或写入IO流的过程中,可能会发生错误。例如,文件不存在导致无法读取,没有写权限导致写入失败等。这些底层错误由Java虚拟机自动封装成IOException
异常并抛出。
因此,所有与IO操作相关的代码都必须正确处理
IOException
。
IO流体系
按流的方向分类
输入流:读,数据从数据源读入程序。
输出流:写,数据从程序写出到目的地。
按处理的数据单元分类
字节流:以字节(1 Byte = 8 bit) 为单位进行读取或者写出数据。
字符流:以字符为单位进行读取或者写出数据。
使用场景
场景 | 流 |
---|---|
拷贝任意类型文件 | 字节流 |
读、写纯文本文件 | 字符流 |
字节流
1 字节流简述
该流是以byte
(字节)为最小单位,因此也称为字节流。
在Java中,InputStream
代表输入字节流,OuputStream
代表输出字节流,这是最基本的两种IO流。
继承体系
2 字节输出流
2.1 OutputStream
OutputStream
是Java标准库提供的最基本的输出流。
和InputStream
类似,OutputStream
也是抽象类,它是所有输出流的超类。
导包: java.io.OutputStream
在计算机中,类似文件、网络端口这些资源,都是由操作系统统一管理的。应用程序在运行的过程中,如果打开了一个文件进行读写,完成后要及时地关闭,以便让操作系统把资源释放掉,否则,应用程序占用的资源会越来越多,不但白白占用内存,还会影响其他应用程序的运行。
InputStream
和OutputStream
都是通过close()
方法来关闭流。关闭流就会释放对应的底层资源。
2.2 FileOutputStream
FileOutputStream
是 OutputStream
的一个实现类。它可以将若干个字节写入文件流:
所有与IO操作相关的代码都必须正确处理
IOException
。
2.2.1 构造方法
方法 | 说明 |
---|---|
public FileOutputStream(File file) | 根据File对象创建输出流对象。 |
public FileOutputStream(File file, boolean append) | 根据File对象创建输出流对象,续写。 |
public FileOutputStream(String pathname) | 根据路径创建输出流对象。 |
public FileOutputStream(String pathname, boolean append) | 根据路径创建输出流对象,续写。 |
注意:
- 关联的路径如果不存在,构造方法会创建一个
- 关联的路径的父级路径如果不存在,会报错
- 如果使用一个参数的构造方法,每次写入底层都会先清空文件
- 如果想续写,可以传递第二个参数为true
2.2.2 常用方法
方法 | 说明 |
---|---|
void write(int b) | 写入一个字节的数据 |
void write(byte[] b) | 写入整个字节数组的数据 |
void write(byte[] b, int off, int len) | 写入字节数组中从off开始共len个数据 |
void close() | 关闭流 |
2.2.3 写
import java.io.FileOutputStream;
import java.io.IOException;
public class OutputDemo1 {
public static void main(String[] args) throws IOException { //抛出IOException
//1.创建对象
FileOutputStream fos = new FileOutputStream("src\\do26IO\\a.txt");
//2.写入数据
fos.write(97); // a
fos.write(99); // c
fos.write(100); // d
//3.关闭文件
fos.close();
}
}
每次写入一个字节非常麻烦,更常见的方法是一次性写入若干字节。这时,可以用OutputStream
提供的重载方法void write(byte[])
来实现:
public static void main(String[] args) throws IOException {
OutputStream fos = new FileOutputStream("src\\do26IO\\a.txt");
String str = "HelloWorld";
byte[] bytes = str.getBytes();
fos.write(bytes); // HelloWorld
fos.close();
}
2.2.4 换行
换行的思想就是在需要换行的地方写入一个换行符。
在不同的操作系统中,换行符是不一样的。如Windows是 \r\n
,Linux是 \n
, Mac是 \r
。
在Windows系统中,Java对换行进行了优化,无论是输入 \r\n
还是单独输入 \r
或 \n
都可以实现换行,Java底层会自动补全。
import java.io.FileOutputStream;
import java.io.IOException;
public class OutputDemo2 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("src\\do26IO\\a.txt");
fos.write("HelloWorld".getBytes()); // HelloWorld
fos.write("\r\n".getBytes()); // 换行
fos.write("Java".getBytes()); // Java
fos.close();
}
}
3 字节输入流
3.1 InputStream
InputStream
是Java标准库提供的最基本的输入流。
InputStream
并不是一个接口,而是一个抽象类,它是所有输入流的超类。
导包: java.io.InputStream
3.2 FileInputStream
3.2.1 构造方法
方法 | 说明 |
---|---|
public FileInputStream(File file) | 根据File对象创建输入流对象。 |
public FileInputStream(String name) | 根据路径创建输入流对象。 |
注意:
- 关联的路径如果不存在,会报错
3.2.2 常用方法
方法 | 说明 |
---|---|
int read() | 读出一个字节的数据。返回值表示读取到的数据,如果没有数据返回-1 |
int read(byte b[]) | 读出一个字节数组的数据,大小与创建的数组长度有关。返回值表示读取了多少字节数据,如果没有数据返回-1 |
void close() | 关闭流 |
注意:
- read方法每调用一次,记录读取位置的指针就会后移一次。
- read方法读不到数据会返回-1
- 读的数组大小最好是1024的整数倍
3.2.3 读
import java.io.FileInputStream;
import java.io.IOException;
public class InputDemo1 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("src\\do26IO\\a.txt");//已有数据 HelloWorld
//定义第三方变量,记录每次读取到的数据
int b;
while ((b = fis.read()) != -1) {
System.out.print((char) b); // HelloWorld
}
fis.close();
}
}
4 应用
4.1 拷贝文件
拷贝的思想是边读边写。
public static void copy(File source, File obj) throws IOException {
FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(obj);
//记录本次读取到了多少字节数据
int len;
//一次读5mb数据
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
//写入字节数组中从0开始共len个数据
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
}
4.2 拷贝文件夹
/**
* 用于文件夹拷贝,(包含子文件拷贝)
* @param source 源文件
* @param obj 目的地
* @return 是否拷贝成功
* @throws IOException
*/
public static boolean copy(File source, File obj) throws IOException {
//如果不存在目的文件夹就创建,存在就略过,不需要判断
obj.mkdirs();
File[] files = source.listFiles();
//判断目的文件夹是否带有权限
if (files == null) {
return false;
}
for (File file : files) {
//如果是文件夹,直接拷贝
if (file.isFile()) {
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(obj, file.getName()));
byte[] bytes = new byte[1024 * 1024 * 5];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
} else {
copy(file, new File(obj, file.getName()));
}
}
return true;
}
4.3 加密解密
原理:异或运算的自反性。
A XOR B XOR B = A
异或是一种基于二进制的位运算,用符号XOR或者 ^
表示,其运算法则是对运算符两侧数的每一个二进制位,同值取0,异值取1。
/**
* 该方法用于加密和解密
* @param source 要加密、解密的文件
* @param obj 加密、解密后的文件
* @throws IOException
*/
public static void encryption(File source, File obj) throws IOException {
FileInputStream fis = new FileInputStream(source);
FileOutputStream fos = new FileOutputStream(obj);
int b;
while ((b = fis.read()) != -1) {
fos.write(b ^ 10);
}
fos.close();
fis.close();
}
IO流捕获异常处理
上述代码中的InputStream
和OutputStream
没有考虑到在发生异常的情况下如何正确地关闭资源。写入和读出过程会经常发生IO错误,例如,磁盘已满,无权限写入等。
我们需要用try(resource)
来保证InputStream
和OutputStream
在无论是否发生IO错误的时候都能够正确地关闭。
捕获异常处理过于繁琐,了解即可。一般都是选择使用抛出处理。
5.1 基本操作
语法:
try {
可能出现异常的代码;
} catch (异常类名 对象名) {
遇到异常后的处理代码;
} finally {
释放资源代码;
}
finally里的代码一定会被执行,除非虚拟机停止
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IODemo1 {
public static void main(String[] args) {
//1.创建对象
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//2.拷贝
fis = new FileInputStream("D:\\test\\aaa.txt");
fos = new FileOutputStream("D:\\test\\copy.txt");
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//3.释放资源
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
5.2 JDK7优化
语法:
try (创建流对象1; 创建流对象2; ...) {
可能出现异常的代码;
} catch (异常类名 对象名) {
遇到异常后的处理代码;
}
凡是实现接口
AutoCloseable
的类,在特定情况下都可以自动释放资源。只有实现接口
AutoCloseable
的类,才能在try后的括号里创建对象。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IODemo2 {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("D:\\test\\aaa.txt");
FileOutputStream fos = new FileOutputStream("D:\\test\\copy.txt")) {
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.3 JDK9优化
将所有的创建流的代码写在try后的括号里,代码阅读性不高。JDK9可以将创建对象写在try外面
语法:
创建流对象1;
创建流对象2;
...
try (流对象1; 流对象2; ...) {
可能出现异常的代码;
} catch (异常类名 对象名) {
遇到异常后的处理代码;
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class IODemo3 {
public static void main(String[] args) throws FileNotFoundException {
FileInputStream fis = new FileInputStream("D:\\test\\aaa.txt");
FileOutputStream fos = new FileOutputStream("D:\\test\\copy.txt");
try (fis; fos) {
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}