IO 流
文件流
文件在程序中是以流的形式来操作的
- 流:数据在数据源(文件)和程序(内存)之间经历的路径
- 输入流:数据从数据源到程序的路径
- 输出流:数据从程序到数据源的路径
流的分类
按操作数据单位分为:
- 字符流
- 字节流
按流向不同分为:
- 输入流
- 输出流
按流的角色分为:
- 节点流
- 处理流
它们有:
字节流 | 字符流 | |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
其中,上述类都是抽象类
- Java 的 IO 流涉及 40 多个类,实际上非常规则,都是从如上 4 个基类派生的
- 它们的派生类都以它们的名字作为后缀
InputStream
FileInputStream
public void fun1() {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(filePath);
while ((readData = fileInputStream.read()) != -1) {
System.out.print((char)readData);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void fun2() {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest.txt";
int readLen = 0;
// 字节数组
byte[] buf = new byte[8];
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(filePath);
// 从输入流读取 buf 数组长度的内容到数组中
// 如果读取正常,返回实际读到的字符数,否则返回 -1
while ((readLen = fileInputStream.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileReader
public static void main(String[] args) {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest666.txt";
FileReader fileReader = null;
int data = ' ';
try {
fileReader = new FileReader(filePath);
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void readFile() {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest666.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.print(new String(buf, 0, readLen));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStream
FileOutputStream
public void writeFile01() {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write('6');
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void writeFile02() {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath);
String str = "YASUO^&^&^*&^^&*&*^*&^\n893374987324\n328974";
// 写入字符串,需要转换成 byte 数组
fileOutputStream.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void writeFile03() {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath);
String str = "YASUO^&^&^*&^^&*&*^*&^\n893374987324\n328974";
// 写入范围内的字符
fileOutputStream.write(str.getBytes(), 0, str.length() - 8);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void writeFile04() {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath, true); // true 表示追加
String str = "YASUO^&^&^*&^^&*&*^*&^\n893374987324\n328974";
// 写入范围内的字符
fileOutputStream.write(str.getBytes(), 0, str.length() - 8);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileWriter
FileWriter 使用后必须要关闭或者刷新才能真正写入到文件中
public void writeFile01() {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest666.txt";
FileWriter fileWriter = null;
char[] chars = {'a', 'b'};
try {
fileWriter = new FileWriter(filePath);
fileWriter.write('T');
fileWriter.write(chars);
fileWriter.write("杰瑞狗4132421".toCharArray(), 0, 3);
fileWriter.write("string21321");
fileWriter.write("STRING", 0, 2);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 必须关闭,才能真正写入到文件
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
节点流和处理流
节点流
节点流可以从一个特定的数据源读写数据,如 FileReader、FileWriter
处理流
处理流,也叫处理流。是“连接”在已存在的流(节点流或者处理流)之上,为程序提供更为强大的读写功能,也更加灵活,如 BufferedReader\BufferedWriter
比如,在 BufferedReader 类中,有一个 Reader 属性,即可以封装一个节点流;BufferedWriter 同理,BufferedWriter 最好不要处理二进制文件,如mp3、视频之类,可能出错
如果使用了处理流的包装类包装节点流,关闭的时候只需要关闭最外层的处理流
两者关系
- 节点流是底层流 / 低级流,直接给数据源相连
- 处理流(包装流)包装节点流,既可以消除不同节点流之间的实现差异,也可以提供更方便的方法完成输入输出
- 处理流对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连
处理流的功能主要体现在:
- 性能提高:以增加缓冲的方式提高输入输出的效率
- 操作便捷:可能会提供一系列便捷方法
对象流
当需要对 基本数据类型 或者 对象 进行序列化或者反序列化时,会需要用到对象流 ObjectInputStream、ObjectOutputStream
简单地说,序列化就是保存数据的时候同时保存数据类型和值,反序列化就是恢复数据的时候恢复数据类型和值
而要让某个对象支持序列化机制,则类本身必须是可序列化的,那么该类需要实现如下两个接口之一:
- Serializable // 标记接口,没有方法,推荐使用
- Externalizable // 该接口有方法需要实现
注意,对象流属于处理流,它有参数为 InputStream(OutputStream)的构造器
例子:
public class ObjectOutputStreamTest {
public static void main(String[] args) throws IOException {
// 序列化后保存的文件格式不是存文本,而是按照序列化自身的格式来保存
String filePath = "C:\\Users\\acer\\Desktop\\objectTest.txt";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.write(100);
objectOutputStream.writeBoolean(true);
objectOutputStream.writeChar('a');
objectOutputStream.writeDouble(3.6);
objectOutputStream.writeUTF("压缩213123");
objectOutputStream.writeObject(new Dog("jerry", 18));
objectOutputStream.close();
}
}
class Dog implements Serializable {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
执行后文件内容:
然后使用 ObjectInputStream 读取文件内容:
public static void main(String[] args) throws IOException, ClassNotFoundException {
String filePath = "C:\\Users\\acer\\Desktop\\objectTest.txt";
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
// 读取顺序一定要和写入的顺序是一致的
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
Object o = ois.readObject();
System.out.println("运行类型:" + o.getClass());
System.out.println("内容:" + o);
}
注意:
- 读写顺序要一致
- 类需要实现 Serializable 接口
- 建议添加 serialVersionID ,为了提高版本兼容性
- 序列化对象时,默认将所有属性进行序列化,除了 static 或者 transient 修饰的成员
- 序列化对象时,要求里面属性的类型也需要实现序列化接口
- 序列化具备可继承性
标准输入输出流
类型 | 默认设备 | |
---|---|---|
System.in 标准输入 | InputStream | 键盘 |
System.out 标准输出 | PrintStream | 显示器 |
public static void main(String[] args) {
InputStream in = System.in;
// System.in 运行类型 BufferedInputStream
// System.in 编译类型 InputStream
// 标准输入 键盘
System.out.println(in.getClass());
PrintStream out = System.out;
// System.out 运行类型 PrintStream
// System.out 编译类型 PrintStream
// 标准输出 显示器
System.out.println(out.getClass());
// 使用 out 对象,将数据显示到显示器
System.out.println("HELLO!!");
// Scanner 从标准输入 键盘 接收数据
Scanner scanner = new Scanner(in);
System.out.println("输入数据" + scanner.next());
}
转换流
实现字节流和字符流之间的转换
InputStreamReader 和 OutputStreamWriter
- InputStreamReader 是 Reader 的子类,可以将 InputStream 包装成 Reader
- OutputStreamWriter 是 Writer 的子类,可以将 OutputStreamWriter 包装成 Writer
- 可以在使用时指定编码方式
public static void main(String[] args) throws IOException {
String filePath = "C:\\Users\\acer\\Desktop\\fileTest666.txt";
// 把 FileInputStream 转成 InputStreamReader,并指定编码
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "gbk");
// 把 InputStreamReader 转成 BufferedReader
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = bufferedReader.readLine();
System.out.println("读取内容:" + str);
bufferedReader.close();
}
打印流
PrintStream 和 PrintWriter
当然,打印流只有输出流
Properties 类
是 Hashtable 的子类,是专门用于读写配置文件的集合类。
配置文件的格式:
- 键=值
注意,键值对不需要有空格,值不需要用引号包起来,默认类型是 String
常见方法:
- load 从文件中加载键值对
- list 将数据显示到指定设备
- getPorperty(key)
- setPorperty(key)
- store 将键值对存储到文件中,在 IDEA 中,如果有中文,将会存储为 unicode
例子:
操作前的文件内容
代码:
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
// 加载文件的键值对
properties.load(new FileReader("src\\main\\java\\mysql.properties"));
// 把键值对显示出来
properties.list(System.out);
// 读取
String user = properties.getProperty("user");
System.out.println("用户名:" + user);
// 修改
properties.setProperty("user", "jerry");
System.out.println("用户名:" + properties.getProperty("user"));
// 创建
properties.setProperty("captcha", "1234");
System.out.println("验证码:" + properties.getProperty("captcha"));
// 存储
properties.store(new FileWriter("src\\main\\java\\mysql2.properties"), null);
}
操作后的新文件: