Java IO操作
IO流介绍
I:input 输入(读取)
O:output 输出 (写出)
场景:
- 读写配置文件、日志文件
- 客户端和服务端的通讯
- 文件上传和下载
IO流体系结构
| 分类 | 抽象类 | 子类 |
|---|---|---|
| 字节流 | InputStream | FileInputStream |
| OutputStream | FileOutStream | |
| 字符流 | Reader | FileReader |
| Writer | FileWriter |
关流
流对象使用完毕后,记得调用close方法关闭。不及时关闭流对象,则会占用资源,且无法对文件进行删除、修改等操作
当流对象被关闭后,无法再调用读取或写入的方法,除非重新创建流对象
IO流异常处理方式
JDK7之前
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关键字:
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之后
格式:
try (需要调用close方法的流对象) {
IO流逻辑代码
} catch(异常类名 对象名) {
异常处理...
}
小括号里的流对象,会自动调用close方法,就算有异常也会调用close
示例:
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) | 写出字节数的一部分 |
示例:
// 创建字节输出流对象关联对象
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()示例:
// 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)示例:
// 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)优化以上代码:
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目录下
- 创建输入流对象读取文件
- 创建输出流对象关联数据目的
- 读写操作
- 关流释放资源
代码:
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读取中文文本时:
// 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读取中文文本时:
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();
}
或:
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) | 写出字符串的一部分 |
示例:
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 |
类似Map集合的keySet方法 |
示例:
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)方法的格式输出字符流 |
加载配置文件:
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}
输出配置文件:
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 |
按行读取内容到集合 |
| close(Closeable closeables) | 安全关闭流 |
| FileUtil类提供的部分方法 | 说明 |
|---|---|
| touch(filePath) | 创建文件(自动创建父目录) |
| mkdir(dirPath) | 创建目录(支持多级目录) |
| copy(srcPath, destPath, isOverride) | 复制文件或目录(可选覆盖) |
| move(srcFile, destDir, isOverride) | 移动文件或目录 |
引入Jar包:在项目下创建一个文件夹,把要用到的jar包放进去,对这个文件夹右键,点击Add as Library...
IOUtil示例:
// 文件拷贝
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:
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);
⚠️危险代码:⚠️
FileUtil.move(new File("E:\\FileUtil\\AAA"),new File("E:\\FileUtil\\AAA\\BBB"),true);// 循环创建嵌套空文件夹