IO流和装饰者设计模式
IO流的概述和分类
解决的问题?
- 把程序中存储在内存中的数据,写入到文件中
- 把磁盘文件中存储的数据,读取到内存中
概述:
- I流:把磁盘文件中存储的数据,读取到内存中(读)
- O流:把内存中的数据,写入到磁盘文件中(写)
字节输出流
- 以字节为单位,把内存中数据写入到磁盘文件中
步骤:
- 创建
- 注意事项:如果文件不存在,就创建。如果文件存在就清空
- 操作(写或读)
- 注意事项:写出的整数,实际写出的是整数在码表上对应的字符
- 关闭
- 注意事项:每次使用完流必须要释放资源
字节流写数据的3种方式
方法名 | 说明 |
---|---|
void write(int b) | 一次写一个字节数据 |
void write(byte[] b) | 一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 一次写一个字节数组的部分数据 |
向文件中追加写入数据:(创建字节输出流对象时,不会清空文件中的原有内容)
FileOutputStream fos = new FileOutputStream("关联文件", true)
第二个参数true表示追加写入模式
字节输入流
- 以字节为单位,把磁盘中数据读取到内存中
步骤:
- 创建
- 注意:如果文件不存在,直接报错
- 读数据
- 注意:读出来的是文件中数据的码表值
- 释放资源
- 注意:每次使用完流必须要释放资源
案例:复制文件
import java.io.*;
public class Demo1 {
public static void main(String[] args){
String from = "from/a.jpg";
String to = "to/a.jpg";
try(
InputStream is = new FileInputStream(from);
OutputStream os = new FileOutputStream(to);
)
{
int data = 0;
while((data = is.read()) != -1){
os.write(data);
}
System.out.println("从" + from + "-->" + to + ":Done");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Properties
在开发中,通常会使用到配置文件,而配置文件格式通常分为:
- XML文件
- properties文件
- …
properties类的作用:
- 读取开发中使用到的.properties配置文件
- properties配置文件中的数据是以key/value形式体现
- 把properties配置文件中的key,存储到Properties类中的key元素下
- 把properties配置文件中的value,存储到Properties类中的value元素下
概述:
- 是一个Map体系的集合类
- Properties中有限IO相关的方法
- 不需要加泛型,默认存储的是Object类型,但是工作中只存字符串
Properties和IO流结合的方法:
方法名 | 说明 |
---|---|
void load(InputStream inStream) | 以字节流形式,把文件中的键值对,读取到集合中 |
void load(Reader reader) | 以字符流形式,把文件中的键值对,读取到集合中 |
void store(OutputStream out, String comments) | 把集合中的键值对以字节流形式写入到文件中,参数二为注释 |
void store(Writer writer, String comments) | 把集合中的键值对以字符流形式写入文件中,参数二为注释 |
Properties作为集合的特有方法:
方法名 | 说明 |
---|---|
Object setProperty(String key, String value) | 设置结婚的键和值,都是String类型,相当于put方法 |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性,相当于get方法 |
Set< String > stringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串,相当于keyset方法 |
案例:
需求:在properties文件中手动写上姓名和年龄,读取到集合中,将该数据封装成javabean对象
// 配置文件
name=zhangsan
age=18
// Test类
public class Test {
private String name;
private int age;
public Test() {
}
public Test(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Test{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 测试类
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/*
在properties文件中手动写上姓名和年龄,读取到集合中,将该数据封装成javabean对象
*/
public class Demo3 {
public static void main(String[] args) throws IOException {
String path = "file/test.properties";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
Properties p = new Properties();
p.load(bis);
String name = p.getProperty("name");
int age = Integer.parseInt(p.getProperty("age"));
Test t = new Test(name, age);
System.out.println(t.toString());
bis.close();
}
}
ResourceBundle工具类
API介绍
- java.util.ResourceBundle是一个抽象类
- 我们可以使用它的子类PropertyResourceBundle来读取以.properties结尾的配置文件
通过静态方法直接获取对象
-
static getBundle(String baseName) 直接获取默认语言环境下的属性资源
-
参数注意:
- 属性集名称不含扩展名
- 属性集文件是在src目录中的
-
ResourceBundle中常用方法:
方法名 说明 String getString(String key) 通过建获取对应的值
字符流
字符流写数据的5种方式
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流,一旦关闭,就不能再写数据 |
字符流读数据的2种方式
方法名 | 说明 |
---|---|
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
缓冲区流
字符缓冲流
- BufferedWriter:可以将数据高效的写出
- BufferedReader:可以将数据高效的读入到内存
- 注意:字符缓冲流不具备读写功能,只提供缓冲区,真正读写还是需要依赖与构造接受的基本的字符流
构造方法:
- BufferedWriter(Writer out)
- BufferedReader(Reader in)
练习:
-
需求:使用字符缓冲流复制纯文本文件
import java.io.*; public class Demo4 { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new FileReader("file/小说.txt")); BufferedWriter bw = new BufferedWriter(new FileWriter("file/copy.txt")); int len = -1; while ((len = br.read()) != -1){ bw.write(len); } br.close(); bw.close(); } }
字符缓冲流特有功能
BufferedWriter:
- void newLine():写一个分隔符,会根据操作系统的不同,写入不同的行分隔符
BufferedReader:
- public String readLine():读取文件一行数据,不包含换行符号,读到文件的末尾返回null
案例:
读取文件中的数据排序后再次写到本地
- 需求:读取文件中的数据,33 22 11 55 44
- 排序后:11 22 33 44 55 再次写到本地文件
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/*
需求:读取文件中的数据:33 22 11 55 44
排序后进行写入到本地文件
*/
public class Demo2 {
public static void main(String[] args) {
try{
BufferedReader br = new BufferedReader(new FileReader("from/helloworld.txt"));
String s = br.readLine();
String[] strArr = s.split(" ");
List<Integer> list = Arrays.stream(strArr).map(str -> Integer.parseInt(str)).sorted().collect(Collectors.toList());
String s1 = list.toString();
s1 = s1.substring(1, s1.length() - 1).replace(",", " ");
System.out.println(s1);
BufferedWriter bw = new BufferedWriter(new FileWriter("from/helloworld.txt"));
bw.write(s1);
br.close();
bw.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
转换流
转换流的作用:读写特定编码表的文件
- FileReader类默认是UTF-8编码表(无法读取GBK编码表的文件)
输入流:InputStreamReader
- 自身没有读数据的能力,需要依赖字节输入流
输出流:OutputStreamWriter
- 自身没有写数据的能力,需要依赖字节输出流
练习
需求1:使用转换流,把数据按照GBK的编码写入文件,在使用GBK的编码读取数据
import java.io.*;
public class Demo5 {
public static void main(String[] args) throws IOException {
String path = "file/GBK.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(path), "gbk");
osw.write("我是gbk编码的文件");
osw.close();
InputStreamReader isr = new InputStreamReader(new FileInputStream(path), "gbk");
int len = 0;
while ((len = isr.read()) != -1){
System.out.print((char)len);
}
isr.close();
}
}
转换流就是来进行字节流和字符流之间转换的
需求2:将模块根目录中GBK编码的文本文件,转换为UTF-8编码的文本文件
import java.io.*;
public class Demo6 {
public static void main(String[] args) throws IOException {
String path = "file/GBK.txt";
String target = "file/utf8.txt";
InputStreamReader isr = new InputStreamReader(new FileInputStream(path), "gbk");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(target), "utf8");
int len = -1;
while ((len = isr.read()) != -1){
osw.write(len);
}
isr.close();
osw.close();
}
}
对象操作流(序列化流)
对象操作流的特点:可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中。
对象操作流的应用:
- 把程序中创建的对象,先写入到文件中(持久化存储)
- 即使重启计算机后,通过程序读取文件中存储的对象,可以重新把对象加载到内存中
对象操作输入流(ObjectInputStream)(反序列化):从文件中读取存储的对象
对象操作输出流(ObjectOutputStream)(序列化):把内存中创建的对象,写入到文件中或在网络中传输
注意:如果一个类对象想要被序列化,那么此类需要实现Serializable接口
Serializable接口的含义:
- 是一个标记型接口,里面没有任何抽象方法
- 只要一个类实现了此接口,表示此类的对象可以被序列化
练习:使用对象操作输出流,把一个User对象写入文件中,在使用对象操作输入流读取对象数据
// 将对象写入文件中
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Test1 {
public static void main(String[] args) throws IOException {
String path = "file/user.txt";
User u = new User("张三", "zc123");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
oos.writeObject(u);
oos.close();
}
}
// 将文件反序列化,读入到内存
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Test2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file/user.txt"));
Object user = ois.readObject();
User u = (User)user;
System.out.println(u.getName() + " " + u.getPassword());
ois.close();
}
}
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的Javabean类,读取数据会抛出InvalidClassException异常
解决方法:
- 给对象所属的类加一个serialVersionUID
- private static final long serialVersionUID = 42L
- 如果一个对象中的某个成员变量的值不想被序列化
- 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
打印流
-
作用:
-
在写入数据后可以实现自动换行
-
通常用于日志记录
-
-
类:PrintStream
public PrintStream(String filePath)
-
常用方法:
- public void println(数据) // 打印后换行
- public void print(数据) // 打印不换行
装饰者设计模式
概述:
- 装饰模式指的是在不改变原类,不使用继承的基础上,动态的扩展一个对象的功能
- 不使用继承技术扩展功能,可以降低耦合
使用原则:
- 装饰类和被装饰类需要有共同的父类型
- 在之前学习过的BufferedWriter和FileWriter就是装饰设计模式
- BufferedWriter的父类为Writer
- FileWriter的父类也是Writer
- 我们把FileWriter的对象传递到BufferedWriter的构造中,那么可以理解为BufferedWriter是装饰类,FileWriter是被装饰类
- BufferedWriter对FileWriter的功能做了增强
- 装饰类的构造要接收被装饰类的对象
- FileWriter fw = new FileWriter(“路径”);
- BufferedWriter bw = new BufferedWriter(fw);
- 在装饰类中把要增强扩展的功能进行扩展
- BufferedWriter和FileWriter的功能一样,都具备Writer中写数据的功能
- 但是BufferedWriter提供了缓冲区,相当于对FileWriter功能做了扩展
- 对于不要增强的功能直接调用
- 不需要增强的功能直接继承父类的即可