前言
本文为IO流的学习笔记,刚开始学习Java,有错误及不完善的地方还请大佬们指出~
1. 文件
1.1 概念
保存数据的地方,如word、txt、excel、pdf、jpg、mp4、avi等
1.1.1 文件流
文件在程序是以流的形式操作的
1.1.2 流
数据在数据源(文件)和程序(内存)之间的路径
1.1.3 输入流
文件→内存
1.1.4 输出流
内存→文件
1.1.5 序列化和反序列化
想将对象存入文件或从文件中读取对象,则需要将基本数据类型或对象进行序列化和反序列化操作
- 序列化:保存数据时,保存数据的值和数据类型
- 反序列化:恢复数据时,恢复数据的值和数据类型
而实现序列化机制:让类是可序列化的,即实现以下两个接口之一- Serializable
- Externalizable
1.2 常用操作
1.2.1 创建文件对象相关的构造器和方法
- new File(String pathname) //根据路径构建一个File对象
- new File(File parent, String child) //根据父目录文件+子路径构建
- new File(String parent, String child) //根据父目录+子路径构建
1.2.2 创建新文件
createNewFile();
练习
在D盘分别用三种方式从创建a.txt、b.txt和c.txt文件
- 方式一
File file = new File("d:/a.txt");
file.createNewFile(); //需要抛出异常
- 方式二
File parentFile = new File("d:/");
String fileName = "b.txt";
File file = new File(parentFile,fileName);
file.createNewFile();
- 方式三
String parentPath = "d:/";
String fileName = "c.txt";
File file = new File(parentPath,fileName);
file.createNewFile();
1.2.3 获取文件相关信息
- getName:获取文件名
- getAbsolutePath:获取文件绝对路径
- getParent:获取文件父级目录
- length:获取文件大小(字节)
- exists:判断文件是否存在
- isFile:判断是否为文件
- isDirectory:判断是否为目录
例:
//先创建文件对象
File file = new File("d:/a.txt");
//调用相应方法
sout(file.getName());
1.2.4 目录的操作和文件删除
- mkdir:创建一级目录,并返回boolean类型值
- mkdirs:创建多级目录,并返回boolean类型值
- delete:删除空目录或文件,并返回boolean类型值
例:
//判断"d:/a.txt"是否存在,存在则删除文件
File file = new File("d:/a.txt");
if (file.exists()) {
if (file.delete()) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}
} else {
System.out.println("文件不存在");
}
//判断"d:/demo/a/b/c"是否存在,存在则提示,否则创建
File file = new File("d:/demo/a/b/c");
if (file.exists()) {
System.out.println("目录存在");
} else {
if(file.mkdirs()) {
System.out.println("该目录创建成功");
} else {
System.out.println("创建失败");
}
}
2. IO流原理及流分类
2.1 IO流原理
- I/O是Input/Output的缩写,用于处理数据传输
- Java程序中对于数据的输入输出操作以“流(Stream)”的方式进行
- java.io包下提供了各种流类和接口,用于获取不同类型数据,并通过方法输入或输出数据
- 输入Input:读取外部输入到程序
- 输出Output:将程序输出到外部
2.2 流分类
- 按操作数据单位分:字节流(8bit),字符流
- 按数据流的流向分:输入流,输出流
- 按流的角色分:节点流,处理流/包装流
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
3. 节点流和处理流
3.1 节点流
可以从一个特定的数据源读写数据,如:FileReader、FileWriter
3.2 处理流
也叫包装流,是连接在已经存在的流(节点流或处理流)上,为程序提供更为强大的读写功能,如:BufferedReader、BufferedWriter
3.3 分类
分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 | 流类型 |
---|---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer | |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter | 节点流 |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter | 节点流 |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWrter | 节点流 |
访问字符串 | StringReader | StringWriter | 节点流 | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter | 处理流 |
转换流 | InputStreamReader | OutputStreamWriter | 处理流 | ||
对象流 | ObjectInputStream | ObjectOutputStream | 处理流 | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | 处理流 |
打印流 | PrintStream | PrintWriter | 处理流 | ||
推回输入流 | PushbackInputStream | PushbackReader | 处理流 | ||
特殊流 | DataInputStream | DataOutputStream | 处理流 |
3.4 区别和联系
- 区别:
- 节点流是底层流/低级流,直接跟数据源相接
- 处理流对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
- 联系:
- 处理流包装节点流,既可以消除不同节点流的现实差异,也可以提供更方便的方法来完成输入输出
4. 输入流
4.1 InputStream
InputStream抽象类是所有字节流的超类
4.1.1 FileInputStream
- 构造函数:
- FileInputStream(File file):通过文件对象(file)创建文件输入字节流
- FileInputStream(String name):通过实际文件的路径字符串创建输入流对象
- 方法:
- close:关闭文件输入流并释放资源
- finalize:清除与文件的链接
- read:按单个字节读取文件
- read(byte[] b):一次读取最多b.length个字节的数据,返回实际读取的字节数
- read(byte[] b,int start,int end):从b数组的指定位置读入字节数据
例:
//文件输入字节流读取文件,并输出在控制台
public void readFile01() {
String filePath = "d:/hello.txt";
FileInputStream fileInputStream = null;
try{
fileInputStream = new FileInputStream(filePath);//需要异常处理
int readData = 0;
while (readData != -1) {
readData = fileInputStream.read();//逐个字节读入,需要异常处理
System.out.println((char) readData);//读入的数据为字节,转换为字符输出
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();//关闭文件流释放资源,关闭需要异常处理
} catch (IOException e) {
e.printStackTrance();
}
}
}
//使用read(byte[] b)读取文件,提高效率
public void readFile02() {
String filePath = "d:/hello.txt";
byte[] buf = new byte[8];//一次读取8个字节
FileInputStream fileInputStream = null;
try{
fileInputStream = new FileInputStream(filePath);//需要异常处理
//如果读取正常,read(byte[] b)返回的是实际读取的字节数(最多读8个字节,不够8个字节有几个字节就读几个)
int readLen = 0;//用于存放实际读取到的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {//读数据,需要异常处理
System.out.println(new String(buf,0,readLen));//转换为字符串输出
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();//关闭文件流释放资源,关闭需要异常处理
} catch (IOException e) {
e.printStackTrance();
}
}
}
4.1.2 BufferedInputStream
- 构造函数:
- BufferedInputStream(InputStream in):将InputStream包装为BufferedInputStream
- BufferedInputStream(InputStream in, int size):将InputStream包装为BufferedInputStream,并指定缓冲区大小
- 方法:
- close:关闭输入流并释放与流相关的系统资源
- read:按单个字节读取文件
- read(byte[] b,int off,int len):从给定偏移开始,将字节输入流中的字节读入指定的字节数组
4.1.3 ObjectInputStream
提供反序列化,读取数据时需要与序列化时顺序一致
- 构造函数:
- ObjectInputStream(InputStream in):将InputStream包装为ObjectInputStream
- 方法:
- close:关闭流并释放资源
- read:按单个字节读取文件
- read(byte[] b,int off,int len):从给定偏移开始,将字节输入流中的字节读入指定的字节数组
- readInt:读取一个32位整型
- readByte:读取一个8位字节
- readShort:读取一个16位短整型
- readLong:读取一个64位长整型
- readChar:读取一个字符
- readDouble:读取一个64位小数
- readFloat:读取一个32位浮点型
- readBoolean:读取一个布尔类型
- readObject:读取一个对象
例:
/**
* @ClassName: ObjectInputStreamDemo
* @Description: 反序列化恢复data.dat文件
*/
public class ObjectInputStreamDemo {
public static void main(String[] args) {
//指定反序列化的文件
String filePath = "data.dat";
try {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filePath));
//读取,反序列化时需要和序列化顺序一致
System.out.println(objectInputStream.readInt());
System.out.println(objectInputStream.readBoolean());
System.out.println(objectInputStream.readChar());
//读取Dog对象,Dog实体类在ObjectOutputStreamDemo的内部类中,同包内可以调用
Object dog = objectInputStream.readObject(); //找不到实体类时会报异常ClassNotFoundException
System.out.println(dog);
//调用dog的方法需要向下转型(Dog) dog
System.out.println(((Dog) dog).getName());
//关闭流
objectInputStream.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
【tips】对象输入输出流注意事项和细节:
- 读写顺序要一致
- 要求实现序列化和反序列化对象,需要实现Serializable
- 序列化的类中建议添加SerialVersionUID以提高版本的兼容性
- 序列化对象时,默认将里面的所有属性都进行序列化,但除了static或transient修饰的成员
- 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性,即如果某类已经实现序列化,则它的所有子类也已经默认实现了序列化
4.2 Reader
4.2.1 FileReader
-
构造函数:
- FileReader(File file):通过文件对象创建文件字符输入流对象
- FileReader(String fileName):通过文件路径创建文件字符输入流对象
-
相关方法:
- read:读取字符
例:
//单个字符读取story.txt文件内容
public void readFile01() {
String filePath = "d/story.txt";
FileReader fileReader = null;
int data = 0;
try {
fileReader = new FileReader(filePath);
while ((data = fileReader.read()) != -1) {
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrance();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrance;
}
}
}
//字符数组读取文件
public void readFile02() {
String filePath = "d/story.txt";
FileReader fileReader = null;
int readLen = 0;
char[] buf = new char[8];
try {
fileReader = new FileReader(filePath);
//循环读入,返回的是实际读取到的字符数
while ((readLen = fileReader.read(buf)) != -1) {
System.out.println(new String(buf,0,readLen));
}
} catch (IOException e) {
e.printStackTrance();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrance;
}
}
}
4.2.2 BufferedReader
属于字符流,按照字符读取数据,关闭处理流时只需关闭外层流
- 构造函数:
- BufferedReader(Reader in):创建使用默认大小输入缓冲区的缓冲字符输入流
- BufferedReader(Reader in, int size):创建使用指定大小输入缓冲区的缓冲字符输入流
- 方法:
- close:关闭流并释放资源
- read:读入一个字符
- readLine:读入一行字符
例:
public class BufferedReaderDemo{
public static void main(String[] args) throws IOException {
String filePath = "src/FileWriter01.java";
//创建BufferedReader对象
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//按行读取,当返回值为空时说明文件读取完毕
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
//关闭流
bufferedReader.close();
}
}
4.2.3 InputStreamReader
将InputStream(字节流)包装成Reader(字符流)
- 构造函数:
- InputStreamReader(InputStream in):创建一个使用默认字符集的InputStreamReader
- InputStreamReader(InputStream in, Charset cs):创建一个指定字符集的InputStreamReader
- 方法:
- close:关闭流并释放资源
- read:读入一个字符
- read(char[], int off, int len):读入字符数组的指定部分
例:
public class InputStreamReaderDemo {
/**
* @Description: 将字节流FileInputStream转换为字符流InputStreamReader
* @Param: [args]
* @Return: [void]
*/
public static void main(String[] args) {
String filePath = "a.txt";
try {
//把FileInputStream转换为InputStreamReader,并指定编码格式为utf-8
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "utf-8");
//将InputStreamReader传入BufferedReader
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//读取
String str = bufferedReader.readLine();
System.out.println("读取内容:" + str);
//关闭流
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5. 输出流
5.1 OutputStream
5.1.1 FileOutputStream
- 构造函数:
- FileOutputStream(File file):通过文件对象创建文件输出字节流对象
- FileOutputStream(String name):通过文件路径创建输出流对象
- FileOutputStream(File file,boolean append):append为true则输出时追加到文件末尾,否则覆盖文件内容
- FileOutputStream(String name,boolean append):同上
- 方法:
- close:关闭输出流并释放资源
- finalize:清除与文件的链接
- write(int b):写入单个字节的数据
- write(byte[] b):将b.length个字节的数据从指定的字节数组输出到文件
- write(byte[] b,int start,int end):将字符串数组b中指定位置的字节数据输出到文件
例:
//在a.txt中写入hello,world 文件不存在时自动创建文件
pulic void writeFile() {
String filePath = "d:/a.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write('H');//写入单个字节,char会自动转换为int
String str = "hello,world";
fileOutputStream.write(str.getBytes());//getBytes方法可以把字符串转换为一个字节数组
fileOutputStream.write(str.getBytes(),0,str.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
}
}
}
5.1.2 BufferedOutputStream
- 构造函数:
- BufferedOutputStream(OutputStream os):创建一个缓冲输出流
- BufferedOutputStream(OutputStream os, int size):指定缓冲区大小的缓冲输出流
- 方法:
- write(int b):将指定字节写入输出流
- write(byte[], int off, int len):将数组指定段落写入输出流
5.1.3 ObjectOutputStream
提供序列化,实现了Serializable接口
-
构造函数:
- ObjectOutputStream(OutputStream os):将OutputStream包装为ObjectOutputStream
-
方法:
- close:关闭流并释放资源
- write:按单个字节输出
- writeInt:输出一个32位整型
- writeByte:输出一个8位字节
- writeShort:输出一个16位短整型
- writeLong:输出一个64位长整型
- writeChar:输出一个字符
- writeDouble:输出一个64位小数
- writeFloat:输出一个32位浮点型
- writeBoolean:输出一个布尔类型
- writeObject:输出一个对象
例:
/**
* @ClassName: ObjectOutputStreamDemo
* @Description: 使用ObjectOutputStream序列化基本数据类型和一个Dog对象(name, age),并保存到data.dat文件中
*/
public class ObjectOutputStreamDemo {
public static void main(String[] args) {
//文件路径:相对路径,即工程文件目录下data.dat文件,也可以使用.txt文件保存
String filePath = "data.dat";
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到文件中
objectOutputStream.writeInt(100); //自动装箱成Integer,实现了Serializable
objectOutputStream.writeBoolean(true); //自动装箱成Boolean,实现了Serializable
objectOutputStream.writeChar('a'); //自动装箱成Character,实现了Serializable
//保存Dog对象
objectOutputStream.writeObject(new Dog("旺财",5)); //需要在实体类实现Serializable接口
//关闭流
objectOutputStream.close();
System.out.println("数据保存完毕(序列化形式)");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* @ClassName: Dog
* @Description: Dog实体类(内部类),需要实现Serializable接口
*/
class Dog implements Serializable {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
5.2 Writer
5.2.1 FileWriter
-
构造函数:
- FileWriter(File file):通过文件对象创建文件输出字符流对象
- FileWriter(String str):通过字符串创建文件输出字符流对象
- FileWriter(File file, boolean append):append为true则输出时追加到文件末尾,否则覆盖文件内容
- FileWriter(String str, boolean append): 同上
-
方法:
- write(int):写入单个字符
- write(char[]):写入指定数组
- write(char[], off, len):写入指定数组的指定部分
- write(String):写入整个字符串
- write(String, off, len):写入字符串指定部分
【tips】FileWriter使用后必须关闭(close)或刷新(flush)
例:
public class FileWriterDemo {
public static void main(String[] args) {
String filePath = "d:/note.txt";
//创建FileWriter对象
FileWriter fileWriter = null;
char[] chars = {'a','b','c'};
try {
fileWriter = new FileWriter(filePath);
// write(int):写入单个字符
fileWriter.write("H");
// write(char[]):写入指定数组
fileWriter.write(chars);
// write(char[], off, len):写入指定数组的指定部分
fileWriter.write(chars, 0, 2);
// write(String):写入整个字符串
fileWriter.write("213");
// write(String, off, len):写入字符串指定部分
fileWriter.write("4dfk3",2,2);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
assert fileWriter != null;
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("程序结束");
}
}
5.2.2 BufferedWriter
-
构造函数:
- BufferedWriter(Writer out):创建缓冲字符输出流
- BufferedWriter(Writer out, int size):创建指定缓冲区大小的缓冲字符输出流
-
方法:
- close:关闭流并释放资源
- newLine:输出并插入一个和系统相关的换行符
- write:输出一个字符
- write(char[], int off, int len):输出数组指定部分
- write(String s, int off, int len):输出字符串指定部分
例:
public class BufferedWriterDemo {
public static void main(String[] args) {
String filePath = ("BufferedWriterTest.txt");
//创建bufferedWriter对象
try {
//BufferedWriter本身没有追加,需要追加时在节点流后加true,即:FileWriter(filePath,true)
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath));
bufferedWriter.write("Hello World");
//插入一个和系统相关的换行符
bufferedWriter.newLine();
//关闭流
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.2.3 OutputStreamWriter
将OutputStream(字节流)包装成Writer(字符流)
-
构造函数:
- OutputStreamWriter(OutputStream os):默认字符集的OutputStreamWriter
- OutputStreamWriter(OutputStream os, Charset cs):指定字符集的OutputStreamWriter
-
方法:
- close:关闭流并释放资源
- write:输出一个字符
例:
public class OutputStreamWriterDemo {
/**
* @Description: 将字节流FileOutputStream包装为字符流OutputStreamWriter,并指定保存文件的编码格式为gbk
* @Param: [args]
* @Return: [void]
*/
public static void main(String[] args) {
String filePath = "OutputStreamWriterDemo.txt";
try {
//创建流对象
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(filePath),"gbk");
//写入
outputStreamWriter.write("测试中文编码");
//关闭流
outputStreamWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
6. Properties类
用于处理.properties文件
- 方法:
- load:加载配置文件的键值对到Properties对象
- list:将数据显示到指定的设备/流对象
- getProperty(key):根据键获取值
- setProperty(key, value):设置键值对到Properties对象
- store:将Properties中的键值对存储到配置文件
例:
public class PropertiesDemo {
public static void main(String[] args) {
//创建Properties对象
Properties properties = new Properties();
try {
//加载配置文件
properties.load(new FileReader("demo.properties"));
//键值对打印到控制台
properties.list(System.out);
//根据key获取对应value
String userName = properties.getProperty("userName");
String password = properties.getProperty("password");
System.out.println("用户名:" + userName + " 密码:" + password);
} catch (IOException e) {
e.printStackTrace();
}
}
}
demo.properties
ip=192.168.100.100
userName=root
password=root