12:IO框架 || 应该学会如何优雅的读写文件
IO框架
一、什么是流
概念:内存与存储设备之间传输数据的通道。
程序在内存,文件在硬盘,要建立通道,这个通道就是流,没有这个程序,内存中的程序无法交互硬盘中的文件。

水借助管道传输;数据借助流传输。
二、流的分类
-
按方向【重点】:
以内存为参照物
- 输入流:将存储设备中的内容读入到内存中。
- 输出流:将内存中的内容写入到存储设备中。
-
按单位:
- 字节流:以字节为单位,可以读写所有数据。
- 字符流:以字符为单位,只能读写文本数据。
-
按功能:
- 节点流(底层流):具有实际传输数据的读写功能。
- 过滤流:在节点流的基础上增强功能。
三、字节流
字节流的父类(抽象类)为流的读写提供基本操作:
- InputStream:字节输入流
- public int read(){}
- public int read(byte[] b){}
- public int read(byte[] b,int off, int len){}
- OutputStream:字节输出流
- public void write(int n){}
- public void write(byte[] b){}
- public void write(byte[] b,int off,int len){}
1.InputStream


2.OutputStream


1. 文件字节流
FileInputStream:
- public int read(byte[] b) //从流中读取多个字节,将读到内容存入b数组,返回实际读到的字节数;如果达到文件的尾部,则返回-1。
FileOutputStream:
- public void write(byte[] b) //一次写多个字节,将b数组中所有字节,写入输出流。
1.FileInputStream:


2.FileOutputStream:


2. FileInputStream 的使用
package xyz.qianfeng.io.input;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* FileInputStream的演示 文件字节输入流
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
//1. 创建FileInputStream
FileInputStream fileInputStream = new FileInputStream("e:\\aaa.txt");
//2. 读取文件
//单个字节读取 效率低
// fileInputStream.read();
// int data = 0;
// while ((data = fileInputStream.read()) != -1){
//// System.out.println(data);//这样读出来是int型
// System.out.println((char) data);
// }
//一次多个字节读取
byte[] buf = new byte[1024];
int count = 0;
while ((count = fileInputStream.read(buf)) != -1){//count的值为读到了几个字符
System.out.println(new String(buf,0,count));//从0开始读count个字节
}
//3. 关闭
fileInputStream.close();
System.out.println("打印完毕");
}
}

3. FileOutputStream 的使用
package xyz.qianfeng.io.output;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
/**
* 演示文件输出流
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
//1. 创建文件字节输出流对象
FileOutputStream fou = new FileOutputStream("e:\\bbb.txt",true);//true的话就append 不会覆盖
//2. 写入文件
// fou.write(97);
// fou.write('b');
// fou.write('c');
String string = "hello world";
fou.write(string.getBytes());
//3. 关闭
fou.close();
System.out.println("写入成功");
}
}
4. 字节流复制文件
package xyz.qianfeng.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* 使用文件字节流实现文件的复制
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
FileInputStream fin = new FileInputStream("e:\\li.jpg");
FileOutputStream fou = new FileOutputStream("e:\\li3.jpg");
//一边读一边写
byte[] buf = new byte[1024];//每次读1024byte
int count = 0;
while ((count = fin.read(buf)) != -1 ){
fou.write(buf,0,count);
}
//关闭 没关闭流之前不会覆盖
fin.close();
fou.close();
System.out.println("复制完毕");
}
}
5. 字节缓冲流
缓冲流:BufferedInputStream/BufferedOutputStream
-
提高IO效率,减少访问磁盘的次数
-
数据存储在缓冲区,flush是将缓存区的内容写入文件中,也可以直接close
-
读取
package xyz.qianfeng.io; import java.io.BufferedInputStream; import java.io.FileInputStream; /** * 使用字节缓冲流读取 */ public class Demo02 { public static void main(String[] args) throws Exception { FileInputStream fin = new FileInputStream("e:\\aaa.txt"); BufferedInputStream bis = new BufferedInputStream(fin); //2.读取 // int data = 0; // while ((data = bis.read())!= -1){ // System.out.print((char) data); // } byte[] bytes = new byte[1024]; int count = 0; while ((count = bis.read(bytes)) != -1){ System.out.println(new String(bytes,0,count)); } //3.关闭 bis.close();//关闭这个会自动关闭fin } } -
写入
package xyz.qianfeng.io; import java.io.BufferedOutputStream; import java.io.FileOutputStream; /**\ * 使用字节缓冲流写入 */ public class Demo03 { public static void main(String[] args) throws Exception { FileOutputStream fou = new FileOutputStream("e:buffer.txt"); BufferedOutputStream bout = new BufferedOutputStream(fou); //写入文件 for (int i = 0; i < 10; i++) { bout.write("hello world\r\n".getBytes());//先写入缓冲区 bout.flush();//刷新到硬盘 } bout.close();//关闭内部也会调用flush方法,将之前的数据一次性刷新,但可能会数据丢失 } }
6. 对象流
对象流:ObjectOutputStream/ObjectInputSream
-
增强了缓冲区功能
-
增强了读写8种基本数据类型和字符串功能
-
增强了读写对象的功能:
- readObject() 从流中读取一个对象
- writeObject(Object obj) 向流中写入一个对象
使用流传输对象的过程称为序列化、反序列化。
序列化:将内存中对象写入硬盘
反序列化:从硬盘中读取对象到内存中
(1)实现对象的序列化
需要在类里实现接口 Serializable : 标记接口,说明这个类可以实例化其实里面什么方法都没有
package xyz.qianfeng.io.object;
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = 100L; //序列化版本号ID,作用是保证序列化的类和反序列化的类是同一个类
private String name;
private transient int age;
public final String cou;
public String getCou() {
return cou;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Student(String name, int age, String cou) {
this.name = name;
this.age = age;
this.cou = cou;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package xyz.qianfeng.io.object;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
/**
* 使用ObjectOutputStream 实现对象的序列化
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("e:\\stu.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//序列化(写入操作)
Student student = new Student("liu",20, "cn");
Student student1 = new Student("jie",20, "cn");
oos.writeObject(student);//会报错,说不能序列化,需要在类里实现接口 Serializable : 标记接口,说明这个类可以实例化其实里面什么方法都没有
oos.writeObject(student1);
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(student);
arrayList.add(student1);
oos.writeObject(arrayList);
oos.close();
System.out.println("序列化完毕");
}
}
(2)实现对象的反序列化
package xyz.qianfeng.io.object;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
/**
* 实现反序列化
*/
public class Demo02 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("e:\\stu.bin");
ObjectInputStream ois = new ObjectInputStream(fis);
//反序列化
Student student = (Student) ois.readObject();
Student student1 = (Student) ois.readObject();
ArrayList<Student> arrayList = (ArrayList<Student>) ois.readObject();
ois.close();
System.out.println(student.toString());
System.out.println(student1.toString());
System.out.println(arrayList.toString());
}
}
(3) 注意事项
- 序列化类必须要实现Serializable接口
- 序列化类中的对象属性也要求实现Serializable接口,比如学生类里面有一个地址类,这个地址类也要求实现该接口
- 改接口会有一个警告,private static final long serialVersionUID = 100L ; 序列化版本号ID,作用是保证序列化的类和反序列化的类是同一个类
- 要是不想序列化某一个属性,使用transient修饰属性,这个属性就不能序列化了。transient(瞬间的)
- 静态属性也不能序列化。
- 序列化多个对象,可以多写几次,多读几次,也可以使用集合实现。
四、字符流
1. 字符编码
ISO-8859-1 收录除ASCII外,还包括西欧、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。
UTF-8 针对Unicode码表的可变长度字符编码
GB2312 简体中文
GBK 简体中文、扩充
BIG5 台湾,繁体中文
ANSI在不同的国家代表不同编码,比如在中国,就代表GBK
2. 字符流
package xyz.qianfeng.io;
import java.io.FileInputStream;
public class Demo04 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("e:\\hello.txt");
int data = 0;
while ((data = fis.read()) != -1){//12字节 4个汉字
System.out.println((char) data);//1个字节读一次//读出来乱码
}
fis.close();
}
}

因此,我们不能用字节流读取,要用字符流。
字符流的父类(抽象类):
- Reader:字符输入流
- public int read(){}
- public int read(char[] c){}
- public int read(char[] b,int off,int len){}
- Writer:字符输出流
- public void write(int n){}
- public void write(String str){}
- public void write(char[] c){}
3. 文件字符流
FileReader:
- public int read(char[] c) //从流中读取多个字符,将读到内容存入c数组,返回实际读到的字符数;如果达到文件的尾部,则返回-1.
FileWriter:
- public void write(String str) //一次写多个字符,将b数组中所有字符,写入输出流。
4. FileReader 的使用
package xyz.qianfeng.io;
import java.io.FileReader;
/**
* 使用FileReader读取文件
*/
public class Demo05 {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("e:\\hello.txt");
//读取
//单个字符读取
// int data = 0;
// while ((data = fr.read()) != -1){
// System.out.println((char) data);
// }
char[] buf = new char[1024];
int count = 0;
while ((count = fr.read(buf)) != -1){
System.out.println(new String(buf,0,count));
}
fr.close();
}
}

5. FileWriter 的使用
package xyz.qianfeng.io;
import java.io.FileWriter;
/**
* FileWriter的使用
*/
public class Demo06 {
public static void main(String[] args) throws Exception {
FileWriter fw = new FileWriter("e:\\write.txt");
//写入
for (int i = 0; i < 10; i++) {
fw.write("Java是世界上最好的语言\r\n");
fw.flush();
}
fw.close();
}
}
6. 字符流复制文件
注意:只能复制文本文件,不能复制图片等没有字符编码的其他二进制文件,。
图片是010101...二进制文件,而字符流读的时候要把二进制转换为字符,但是图片没有编码格式,所以读到的是乱码,等写到硬盘复制过去后,还是乱码,所以复制会错误。
使用字节流可以复制任意文件。
package xyz.qianfeng.io;
import java.io.FileReader;
import java.io.FileWriter;
/**
* 字符流复制文件
*/
public class Demo07 {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("e:\\write.txt");
FileWriter fw = new FileWriter("e:\\write2.txt");
int data = 0;
while ((data = fr.read()) != -1 ){
fw.write(data);
fw.flush();
}
fr.close();
fw.close();
}
}
7. 字符缓冲流
缓冲流:BufferedReader/BufferedWriter
- 高效读写
- 支持输入换行符
- 可一次写一行、读一行
(1)使用字符缓冲流读取文件
package xyz.qianfeng.io;
import java.io.BufferedReader;
import java.io.FileReader;
/**
* 使用字符缓冲流读取文件
*/
public class Demo08 {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("e:\\write.txt");
BufferedReader br = new BufferedReader(fr);
//读取 方式一
// char[] chars = new char[1024];
// int count = 0;
// while ((count = br.read(chars)) != -1){
// System.out.println(new String(chars,0,count));
// }
//读取方式二 一行行
String line = null;
while ((line = br.readLine())!= null){
System.out.println(line);
}
br.close();
}
}
(2)使用字符缓冲流写入文件
package xyz.qianfeng.io;
import java.io.BufferedWriter;
import java.io.FileWriter;
/**
* 使用字符缓冲流写入文件
*/
public class Demo09 {
public static void main(String[] args) throws Exception {
FileWriter fr = new FileWriter("e:\\bufferwrite.txt");
BufferedWriter bw = new BufferedWriter(fr);
for (int i = 0; i < 10; i++) {
bw.write("好好学习,天天向上!");
bw.newLine();//写入一个换行符 兼容性更好,在不同操作系统下换行操作不一样
bw.flush();
}
bw.close();
}
}
五、打印流
PrintWriter:
- 封装了print() / println() 方法,支持写入后换行。
- 支持数据原样打印
package xyz.qianfeng.io;
import java.io.PrintWriter;
/**
* 演示PrintWriter的使用
*/
public class Demo10 {
public static void main(String[] args) throws Exception {
//创建打印流
PrintWriter pw = new PrintWriter("e:\\print.txt");
pw.println(97);
pw.println(3.14);
pw.println(true);
pw.println("a");
pw.close();
}
}
六、转换流
桥转换流:InputStreamReader / OutputStreamWriter
- 可将字节流转换为字符流
- 可设置字符的编码格式
1. InputStreamReader 的使用
可以指定使用的编码
package xyz.qianfeng.io.zhuanhuan;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* InputStreamReader 的使用
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("e:\\write.txt");
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//文件本来是UTF-8,使用其他编码格式会乱码
//读取文件
int data = 0;
while ((data = isr.read()) != -1){
System.out.print((char) data);
}
isr.close();
}
}
2. OutputStreamWriter 的使用
package xyz.qianfeng.io.zhuanhuan;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
/**
* 使用转换流写入文件,使用指定编码
*/
public class Demo02 {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("e:\\zhuanhuan.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos,"GBK");
for (int i = 0; i < 10; i++) {
osw.write("Java爱程序员!\r\n");
osw.flush();
}
osw.close();
System.out.println("执行成功");
}
}
七、File 类
概念:代表物理盘符中的一个文件或者文件夹。
方法:
1. 文件操作
package xyz.qianfeng.io.file;
import java.io.File;
import java.io.IOException;
import java.util.Date;
/**
* File类的使用
* 1. 分隔符
* 2. 文件操作
* 3. 文件夹操作
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
separator();
fileOpe();
}
//1. 分隔符
public static void separator(){
System.out.println("路径分隔符" + File.pathSeparator);
System.out.println("名称分隔符" + File.separator);
}
//2. 文件操作
public static void fileOpe() throws Exception {
// (1)创建文件
File file = new File("e:\\file.txt");//在硬盘上并没有,只是创建了文件对象,如果不写盘符,会创建在项目下
//判断文件是否存在,存在就不创建文件,不存在就创建
if (!file.exists()){//判断文件是否存在
boolean b = file.createNewFile();//创建文件
System.out.println("创建成功:"+b);
}
//(2)删除文件
//直接删除
// System.out.println("删除结果:" + file.delete());
//使用JVM退出时删除
// file.deleteOnExit();
// Thread.sleep(5000);//休眠五秒钟
//(3)获取文件信息
//获取绝对路径
System.out.println("获取文件的绝对路径:" + file.getAbsolutePath());
System.out.println("获取路径:" + file.getPath());//写的什么路径就获取到什么路径
System.out.println("获取文件名称:" + file.getName());
System.out.println("获取父目录:" + file.getParent());
System.out.println("获取文件长度:" + file.length());
System.out.println("获取文件创建时间:" + new Date(file.lastModified()).toLocaleString());
//(4)判断
System.out.println("是否可写:" + file.canWrite());//Windows不支持是否可读,是否可执行;如果在文件属性里修改为只读,这里返回false
System.out.println("是否是文件:" + file.isFile());
System.out.println("是否隐藏:" + file.isHidden());
}
}

2. 文件夹操作
package xyz.qianfeng.io.file;
import java.io.File;
import java.io.IOException;
import java.util.Date;
/**
* File类的使用
* 1. 分隔符
* 2. 文件操作
* 3. 文件夹操作
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
// separator();
// fileOpe();
directoryOpe();
}
//3. 文件夹操作
public static void directoryOpe() throws InterruptedException {
//(1) 创建文件夹
File dir = new File("e:\\aa\\bb\\cc");
if (!dir.exists()){
dir.mkdir();//只能创建单级目录,用这个会创建失败
dir.mkdirs();//可以创建多级目录,返回值是创建结果
}
//(2) 删除
//直接删除
dir.delete();//返回值是删除结果,,这个删除方法只删除最里层的目录,而且这个最里层的目录必须是空目录,否则删除为false。
// 若真的想删掉,可用递归删除,后面会讲
//JVM删除
dir.deleteOnExit();
Thread.sleep(5000);
//(3)获取文件夹信息
System.out.println("获取绝对路径:"+dir.getAbsolutePath());
System.out.println("获取路径:"+dir.getPath());
System.out.println("获取文件夹名称:"+dir.getName());
System.out.println("获取父目录:"+dir.getParent());
System.out.println("获取创建时间:"+new Date(dir.lastModified()).toLocaleString());
//(4)判断
System.out.println("是否是文件夹:"+dir.isDirectory());
System.out.println("是否是隐藏:"+dir.isHidden());
//(5)遍历文件夹
File dir2 = new File("e:");
String[] files = dir2.list();//listFiles() 也可以,返回file数组
System.out.println("========");
for (String file : files) {
System.out.println(file);
}
}
}

3. FileFilter 接口
public interface FileFilter
- boolean accept(File pathname)
当调用 File 类中的 listFiles() 方法时,支持传入 FileFilter 接口接口实现类,对获取文件进行过滤,只有满足条件的文件的才可出现在 listFiles() 的返回值中。
package xyz.qianfeng.io.file;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Date;
/**
* File类的使用
* 1. 分隔符
* 2. 文件操作
* 3. 文件夹操作
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
// separator();
// fileOpe();
directoryOpe();
}
//3. 文件夹操作
public static void directoryOpe() throws InterruptedException {
//(1) 创建文件夹
File dir = new File("e:\\aa\\bb\\cc");
if (!dir.exists()){
dir.mkdir();//只能创建单级目录,用这个会创建失败
dir.mkdirs();//可以创建多级目录,返回值是创建结果
}
//(2) 删除
//直接删除
dir.delete();//返回值是删除结果,,这个删除方法只删除最里层的目录,而且这个最里层的目录必须是空目录,否则删除为false。
// 若真的想删掉,可用递归删除,后面会讲
//JVM删除
dir.deleteOnExit();
Thread.sleep(5000);
//(3)获取文件夹信息
System.out.println("获取绝对路径:"+dir.getAbsolutePath());
System.out.println("获取路径:"+dir.getPath());
System.out.println("获取文件夹名称:"+dir.getName());
System.out.println("获取父目录:"+dir.getParent());
System.out.println("获取创建时间:"+new Date(dir.lastModified()).toLocaleString());
//(4)判断
System.out.println("是否是文件夹:"+dir.isDirectory());
System.out.println("是否是隐藏:"+dir.isHidden());
//(5)遍历文件夹
File dir2 = new File("e:");
String[] files = dir2.list();//listFiles() 也可以,返回file数组
// System.out.println("========");
// for (String file : files) {
// System.out.println(file);
// }
System.out.println("====FileFilter接口的使用======");
File[] files1 = dir2.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if (pathname.getName().endsWith(".jpg")){
return true;
}
return false;//true 返回所有文件,false 所有文件都不返回
}
});
for (File file : files1) {
System.out.println(file.getName());
}
}
}

八、递归遍历和递归删除
1. 递归遍历文件夹
package xyz.qianfeng.io.file;
import java.io.File;
/**
* 递归遍历文件夹
*/
public class Demo02 {
public static void main(String[] args) {
listDir(new File("e:\\MyFiles"));
}
public static void listDir(File dir){
File[] files = dir.listFiles();
if (files != null && files.length > 0){
for (File file : files) {
if (file.isDirectory()){
listDir(file);//递归
}else{
System.out.println(file.getPath());
}
}
}
}
}

2. 递归删除文件夹
package xyz.qianfeng.io.file;
import java.io.File;
/**
* 递归遍历文件夹
*/
public class Demo02 {
public static void main(String[] args) {
deleteFile(new File("e:\\MyFiles"));
}
//递归删除文件夹
public static void deleteFile(File dir){
System.out.println("======递归删除========");
File[] files = dir.listFiles();
if (files!=null && files.length>0){
for (File file : files) {
if (file.isDirectory()){
deleteFile(file);
}else {
System.out.println(file.getAbsoluteFile()+"删除"+file.delete());
}
}
}
System.out.println(dir.getAbsoluteFile()+"删除"+dir.delete());
}
}
九、Properties 补充
Properties:属性集合(Hashtable的子类),线程安全
特点:
- 存储属性名和属性值
- 属性名和属性值都是字符串类型
- 没有泛型
- 和流有关
Properties集合的使用:
package xyz.qianfeng.io.file;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Properties;
import java.util.Set;
/**
* Properties集合的使用
*/
public class Demo03 {
public static void main(String[] args) throws Exception {
//1.创建集合
Properties properties = new Properties();
//2. 添加数据
properties.setProperty("username","张三");
properties.setProperty("age","20");
System.out.println(properties.toString());
//3. 遍历
//keySet、entrySet
//stringPropertyNames
Set<String> set = properties.stringPropertyNames();
for (String s : set) {
System.out.println(s+"======"+properties.getProperty(s));
}
//4. 和流有关的方法
//=======list方法:打印====
// PrintWriter pw = new PrintWriter("e:\\print.txt");
// properties.list(pw);
// pw.close();
//=====store方法:保存===
// FileOutputStream fos = new FileOutputStream("e:\\store.properties");
// properties.store(fos,"注释");
// fos.close();
//======load方法:加载
Properties properties1 = new Properties();
FileInputStream fis = new FileInputStream("e:\\store.properties");
properties1.load(fis);
fis.close();
System.out.println(properties1.toString());
}
}
十、总结
流的概念:
- 内存与存储设备之间传输数据的通道。
流的分类:
- 输入流、输出流;字节流、字符流;节点流、过滤流;
序列化和反序列化:
- 将对象通过流写入到文件,或将对象通过流读取到内存,必须实现Serializable接口。
File对象:
- 代表物理盘符中的一个文件或者文件夹。


浙公网安备 33010602011771号