IO流介绍

I:input 输入(读取)

O:output 输出 (写出)

场景:

  1. 读写配置文件、日志文件
  2. 客户端和服务端的通讯
  3. 文件上传和下载

IO流体系结构

分类 抽象类 子类
字节流 InputStream FileInputStream
OutputStream FileOutStream
字符流 Reader FileReader
Writer FileWriter

关流

流对象使用完毕后,记得调用close方法关闭。不及时关闭流对象,则会占用资源,且无法对文件进行删除、修改等操作

当流对象被关闭后,无法再调用读取或写入的方法,除非重新创建流对象

IO流异常处理方式

JDK7之前

1
2
3
4
5
6
7
8
try {
FileOutputStream fos1 = new FileOutputStream("Day04/A.txt");
fos1.write("Hello World!".getBytes());
System.out.println(10 / 0); // 假设在执行流对象操作时,遇到了其他问题
fos1.close(); // 关流这个操作就会被省略
} catch (IOException e) {
e.printStackTrace();
}

想要解决这个问题,就需要用到finally关键字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FileOutputStream fos1 = null;			// 先提升作用域,否则finally代码块中的fos1无法正确识别
try {
fos1 = new FileOutputStream("Day04/A.txt");
fos1.write("Hello World!".getBytes());
System.out.println(10 / 0);
} catch (IOException e) {
e.printStackTrace();
} finally { // 无论是否捕获到异常,都会执行finally代码块中的代码
try {
fos1.close();
} catch (IOException e) {
e.printStackTrace();
}
}

JDK7之后

格式:

1
2
3
4
5
try (需要调用close方法的流对象) {
IO流逻辑代码
} catch(异常类名 对象名) {
异常处理...
}

小括号里的流对象,会自动调用close方法,就算有异常也会调用close

示例:

1
2
3
4
5
try (FileOutputStream fos = new FileOutputStream("Day04/A.txt")) {
fos.write("AABBCC".getBytes());
} catch (IOException e) {
e.printStackTrace();
}

小括号里的对象,它的类必须要实现了AutoCloseable这个接口,才能正常使用,并且还会自动调用close()这个重写后的方法

FileOutStream

构造方法 说明
FileOutputStream(String name) 输出流关联文件,文件路径以字符串形式给出
FileOutputStream(String name, boolean append) 第二个参数表示追加模式
FileOutputStream(File file) 输出流关联文件,文件路径以File对象形式给出
FileOutputStream(File file, boolean append) 第二个参数表示追加模式

当关联的文件不存在时,会自动创建

不开启追加模式,关联文件如果存在,会清空现有的,再开始写入

成员方法 说明
public void write(int i) 写出一个字节
public void write(byte[] b) 写出一个字节数组
public void write(byte[] b, int off, int len) 写出字节数的一部分

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建字节输出流对象关联对象
FileOutputStream fos1 = new FileOutputStream("Day04/A.txt");
File file = new File("Day04/B.txt");
FileOutputStream fos2 = new FileOutputStream(file, true);

// 写出数据
fos1.write(97);
fos1.write("你好世界!!!".getBytes());
byte[] bytes = {97,98,99,100};
fos2.write(bytes);

// 关闭字节输出流
fos1.close();
fos2.close();

想要写出字符串时,可以调用字符串中的getBytes()方法,该方法会返回由字节组成的数组

FileInputStream

构造方法 说明
FileInputStream(String name) 输入流关联文件,文件路径以字符串形式给出
FileInputStream(File file) 输入流关联文件,文件路径以File对象形式给出

关联文件如果不存在,会抛出FileNotFoundException异常

文件夹的话会拒绝访问

成员方法 说明
int read() 读取一个字节并返回,如果达到文件结尾则返回-1
int read(byte[] b) 将读取到的字节,放到传入的数组
返回读取到的有效字节个数
如果达到文件结尾则返回-1
字节数组的长度尽量为1024的倍数

读取的文件中不能有中文,否则会出乱码中文

read()示例:

1
2
3
4
5
6
7
8
9
// int read()
try (FileInputStream fis = new FileInputStream("Day04/B.txt")) {
int i;
while ((i = fis.read()) != -1) {
System.out.println((char) i);
}
} catch (IOException e) {
e.printStackTrace();
}

read(byte[] b)示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// int read(byte[] b)
try (FileInputStream fis = new FileInputStream("Day04/B.txt")) {
byte[] bytes = new byte[2];

int len1 = fis.read(bytes);
System.out.println(Arrays.toString(bytes)); // [97, 98]
System.out.println(len1); // 有效字节个数:2

int len2 = fis.read(bytes);
System.out.println(Arrays.toString(bytes)); // [99, 100]
System.out.println(len2); // 有效字节个数:2

int len3 = fis.read(bytes);
System.out.println(Arrays.toString(bytes)); // [101, 100] 100 残余数据
System.out.println(len3); // 有效字节个数:1

int len4 = fis.read(bytes);
System.out.println(Arrays.toString(bytes)); // [101, 100] 101, 100 残余数据
System.out.println(len4); // 有效字节个数:-1
} catch (IOException e) {
e.printStackTrace();
}

字节输入流读取参数为数组时

可以利用String的构造方法public String(byte[] bytes,int offset,int length)优化以上代码:

1
2
3
4
5
6
7
8
9
10
11
try (FileInputStream fis = new FileInputStream("Day04/B.txt")) {
byte[] bytes = new byte[2];
int len;

while ((len = fis.read(bytes)) != -1) {
String s = new String(bytes,0,len); // len表示有效字节的个数,所以可以防止读取到无效数据
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}

注意,字节数组的长度尽量设置为1024的倍数,保证性能的同时也可以降低额外的内存占用

文件拷贝案例

假设E:\src目录下有一个img_001.png的文件,需要将其拷贝到E:\dest目录下

  1. 创建输入流对象读取文件
  2. 创建输出流对象关联数据目的
  3. 读写操作
  4. 关流释放资源

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String srcFilePath = "E:\\src\\img_001.png";
String destFilePath = "E:\\dest\\img_001.png";
try (
FileInputStream fis = new FileInputStream(srcFilePath);
FileOutputStream fos = new FileOutputStream(destFilePath,true);
) {
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}

FileReader

  • 用于读取纯文本文件,解决中文乱码问题
构造方法 说明
FileReader(String fileName) 字符输入流关联文件,路径以字符串形式给出
FileReader(File file) 字符输入流关联文件,路径以File对象形式给出
成员方法 说明
public int read() 读取单个字符
public int read(char[] cbuf) 读取一个字符数组,返回读取到的有效字符个数

当使用FileInputStream读取中文文本时:

1
2
3
4
5
6
7
8
9
// E:\FileReaderTest\A.txt,文本内容为:你好呀!
try (FileInputStream fis = new FileInputStream("E:\\FileReaderTest\\A.txt")) {
int i;
while ((i = fis.read()) != -1) {
System.out.print((char) i + " "); // ä ½   å ¥ ½ å ï ¼
}
} catch (IOException e) {
e.printStackTrace();
}

使用FileReader读取中文文本时:

1
2
3
4
5
6
7
8
try (FileReader fr = new FileReader("E:\\FileReaderTest\\A.txt")) {
int i;
while ((i = fr.read()) != -1) {
System.out.print((char) i + " "); // 你 好 呀 !
}
} catch (IOException e) {
e.printStackTrace();
}

或:

1
2
3
4
5
6
7
8
9
10
try (FileReader fr = new FileReader("E:\\FileReaderTest\\A.txt")) {
int len;
char[] c = new char[1024];
while ((len = fr.read(c)) != -1) {
String s = new String(c,0,len);
System.out.println(s); // 你好呀!
}
} catch (IOException e) {
e.printStackTrace();
}

FileWriter

构造方法 说明
FileWriter(String fileName) 字符输出流关联文件,路径以字符串形式给出
FileWriter(String fileName, boolean append) 参数2:追加写入模式开关
FileWriter(File file) 字符输出流关联文件,路径以File对象形式给出
FileWriter(File file) 参数2:追加写入模式开关
成员方法 说明
public void write(int c) 写出单个字符
public void write(char[] cbuf) 写出一个字符数组
public void write(char[] cbuf, int offset, int len) 写出字符数组的一部分
public void write(String str) 写出字符串
public void write(String str, int offset, int len) 写出字符串的一部分

示例:

1
2
3
4
5
6
7
8
9
10
11
try (FileWriter fw = new FileWriter("Day04/中文文本.txt",true)) {
char[] chs = {'你','好', '吗'};

fw.write(chs);
fw.write(chs,0,1);
fw.write("我很好,你呢?");
fw.write("我也很好,下次见",0,4);

} catch (IOException e) {
e.printStackTrace();
}

注意事项:字符输出流写出数据时,需要调用flush或close方法,数据才会写出

flush():刷出数据,刷出后可以继续写出

close():关闭流释放资源,同时刷出数据,关闭后不可以继续写出

这是因为底层将写出的字符存放在缓冲区中,只有调用对应方法才能将缓冲池的数据彻底写出(缓冲区大小为1024字符数组长度)

Properties介绍

  • Properties类表示一组持久的属性。Properties可以保存到流中或从流中加载。属性列表中的每个键及其对应的值都是一个字符串
  • 本质就是Map集合
  • 内部存在着两个方法,可以很方便的将集合的键值写入文件,也可以方便的从文件中读取
    • 将来加载配置文件的时候很方便
方法名 说明
Object setProperty(String key, String value) 类似Map集合的put方法
String getProperty(String key) 类似Map集合的get方法
Set<String> stringPropertyNames() 类似Map集合的keySet方法

示例:

1
2
3
4
5
6
7
8
9
10
Properties properties = new Properties();
properties.setProperty("username","admin");
properties.setProperty("password","12345");

System.out.println(properties); // {password=12345, username=admin}
System.out.println(properties.getProperty("username")); // admin
System.out.println(properties.getProperty("password")); // 12345

Set<String> strings = properties.stringPropertyNames();
strings.stream().forEach(System.out::print); // password username

Properties和IO相关的方法

方法 说明
void load(InputStream inStream) 从输入字节流读取属性列表(键和元素对)
void load(Reader reader) 从输入字符流读取属性列表(键和元素对)
void store(OutputStream out, String comments) 将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(InputStream)方法的格式写入输出字节流
void store(Writer writer, String comments) 将此属性列表(键和元素对)写入此Properties表中,以适合使用load(Reader)方法的格式输出字符流

加载配置文件:

1
2
3
4
5
6
7
8
9
Properties prop = new Properties();

try(FileInputStream fis = new FileInputStream("Day04\\config.properties")) {
prop.load(fis);
} catch (IOException e) {
e.printStackTrace();
}

System.out.println(prop); // {password=12345, username=admin}

输出配置文件:

1
2
3
4
5
6
7
8
9
10
Properties prop = new Properties();
try (FileOutputStream fos = new FileOutputStream("Day04\\config.properties",true)) {
prop.setProperty("age","18");
prop.setProperty("score","100");
prop.setProperty("gender","男");

prop.store(fos,null); // 第二参数是写出配置信息时加上的注释,可以填null
} catch (IOException e) {
e.printStackTrace();
}

HuTool

  • HuTool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率
  • 官网地址:Hutool
IOUtil类提供的部分方法 说明
copy(InputStream in, OutputStream out, int bufferSize) 字节流拷贝
copy(Reader reader, Writer writer) 字符流拷贝
readLines(Reader reader, Collection<String> collection) 按行读取内容到集合
close(Closeable closeables) 安全关闭流
FileUtil类提供的部分方法 说明
touch(filePath) 创建文件(自动创建父目录)
mkdir(dirPath) 创建目录(支持多级目录)
copy(srcPath, destPath, isOverride) 复制文件或目录(可选覆盖)
move(srcFile, destDir, isOverride) 移动文件或目录

引入Jar包:在项目下创建一个文件夹,把要用到的jar包放进去,对这个文件夹右键,点击Add as Library...

IOUtil示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 文件拷贝
FileInputStream fis = new FileInputStream("E:\\src\\img_002.png");
FileOutputStream fos = new FileOutputStream("E:\\dest\\img_002.png");

IoUtil.copy(fis,fos,4096);

IoUtil.close(fis);
IoUtil.close(fos);

// 文件读取
FileReader fr = new FileReader("Day04/出师表.txt");
ArrayList<String> list = new ArrayList<>();

IoUtil.readLines(fr,list);

list.stream().forEach(System.out::println);

IoUtil.close(fr);

FileUtil:

1
2
3
4
5
6
FileUtil.touch("E:\\FileUtil\\testA.txt");
FileUtil.touch("E:\\FileUtil\\testB.txt");
FileUtil.touch("E:\\FileUtil\\testC.txt");
FileUtil.mkdir("E:\\FileUtil\\AAA\\BBB\\CCC");
FileUtil.copy("E:\\Program Files (x86)","E:\\FileUtil\\AAA",true);
FileUtil.move(new File("E:\\FileUtil\\AAA\\BBB"),new File("E:\\FileUtil\\AAA"),true);

⚠️危险代码:⚠️

1
FileUtil.move(new File("E:\\FileUtil\\AAA"),new File("E:\\FileUtil\\AAA\\BBB"),true);	// 循环创建嵌套空文件夹