13-IO流3-转换流&对象操作流&Properties(jdk11前指定编码读写文字、序列化和反序列化加密存储、)
原创
©著作权归作者所有:来自51CTO博客作者武大保安的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
1.转换流
(字节流复制 字符流读写 缓冲流提高效率)
字符流: FileWriter、FileReader
字节流:FileOutputStream、FileInputStream
转换流将字符流与字节流进行了一个转换(eg:字符流读写中文时创建的是字符流,真正工作(读写)的是字节流)
那么转换流自然也有专门的两个用于读写的转换流类了(取名很高明,前一半字节流+后一半字符流)
1.1字符流中和编码解码问题相关的两个类【理解】(新学习两个类 )
API中精华提取如下:
- InputStreamReader:是从字节流到字符流的桥梁,父类是Reader
它读取字节,并使用指定的编码将其解码为字符
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集 - OutputStreamWriter:是从字符流到字节流的桥梁,父类是Writer
是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
查看源码:FileReader会创建字节流FileInputStream,并传给父类,而FileReader的父类就是转换流InputStreamReader。说明:读字符流底层就是通过转换流将字节流进行转换,再读数据
1.2转换流读写数据【应用】
方法名
| 说明
|
InputStreamReader(InputStream in)
| 使用默认字符编码创建InputStreamReader对象
|
InputStreamReader(InputStream in,String chatset)
| 使用指定的字符编码创建InputStreamReader对象
|
OutputStreamWriter(OutputStream out)
| 使用默认字符编码创建OutputStreamWriter对象
|
OutputStreamWriter(OutputStream out,String charset)
| 使用指定的字符编码创建OutputStreamWriter对象
|
函数倒着看
public static void main(String[] args) throws IOException {
Method_Reader1();
//jdk11之前 转换流可以指定编码读写
Method_Reader2();
Method_Writer1_u8();
//jdk11之后 再也没有必要使用转换流了 字符流直接就可以实现了(字符流也能直接指定编码读写了)
Method_Reader3_jdk11();
return;
}
//jdk11才能执行 jdk11之后再也没有必要使用转换流了
private static void Method_Reader3_jdk11() throws IOException {
//JDK11之后,字符流新推出了一个构造,也可以指定编码表 //Charset.forName("gbk")封装码表对象
FileReader fr=new FileReader("D:\\Users\\hanzhuan\\Desktop\\a.txt", Charset.forName("gbk"));//jdk11才有
int ch;
while ((ch=fr.read())!=-1){
System.out.print((char)ch);//"春眠不觉晓"
//当桌面a.txt是ANSI编码(另存为可查看编码)时,默认uft-8读取肯定乱码
}
System.out.println();
fr.close();
}
private static void Method_Writer1_u8() throws IOException {
//转换流来写
FileOutputStream fos = new FileOutputStream("D:\\Users\\hanzhuan\\Desktop\\a.txt");//真正工作的字节流
OutputStreamWriter osw = new OutputStreamWriter(fos, "utf-8");
osw.write("处处闻啼鸟");//用utf-8码表写的,a.txt变成u8码表格式了,此时Method_Reader2用GBK读就乱码了
osw.close();
}
private static void Method_Reader2() throws IOException {
//解决乱码:文件什么码表,就得用什么码表去读取
//FileReader不能指定码表,必须用到转换流了
FileInputStream fis = new FileInputStream("D:\\Users\\hanzhuan\\Desktop\\a.txt");//真正工作的字节流
InputStreamReader isr=new InputStreamReader(fis,"gbk");//字节流+指定编码(转换流能将字节流根据指定编码转换为字符流)
int b;
while ((b=isr.read())!=-1){
System.out.print((char)b);//"春眠不觉晓"
}
System.out.println();
isr.close();
}
/**
* 直接用IDEA的默认编码(UTF-8)去读
* 桌面创建的a.txt(ANSI编码,另存为可查看),默认Windows的GBK码表
* 肯定会乱码
* @throws IOException
*/
private static void Method_Reader1() throws IOException {
FileReader fr=new FileReader("D:\\Users\\hanzhuan\\Desktop\\a.txt");
int ch;
while ((ch=fr.read())!=-1){
System.out.print((char)ch);//"���߲�����"
//当桌面a.txt是ANSI编码(另存为可查看编码)时,默认uft-8读取肯定乱码
}
System.out.println();
fr.close();
}
2.对象操作流
2.0 引入
传统写对象的方法非常不安全,如下代码:拿到了txt文件,数据就全部暴漏了,极不安全
对象操作流可以把对象以字节的形式写到本地文件,直接打开文件是读不懂的,需要再次用对象操作流读到内存中。
public static void main(String[] args) throws IOException {
User user=new User("zhangsan","qwer");
//需求:把这个用户信息保存到本地文件去
//之前的做法
BufferedWriter bw=new BufferedWriter(new FileWriter("otheriomoudle/user.txt"));
bw.write(user.getUsername());
bw.write("|");
bw.write(user.getPassword());
bw.close();
}
2.1对象序列化流【应用】
- 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
- 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
- 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
- 对象序列化流: ObjectOutputStream
- 将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
方法名
| 说明
|
ObjectOutputStream(OutputStream out)
| 创建一个写入指定的OutputStream的ObjectOutputStream
|
方法名
| 说明
|
void writeObject(Object obj)
| 将指定的对象写入ObjectOutputStream
|
- 示例代码
学生类:(想要序列化一个类,得实现Serializable接口)
//想要序列化一个类,那么这个类必须要实现一个接口 Serializable
public class User implements Serializable {
private String username;
private String password;
//空参、全参、get/set、toString
}
public static void main(String[] args) throws IOException {
User user = new User("zhangsan","qwer");
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("otheriomoudle/user"));
oos.writeObject(user);
oos.close();
}
写入了文件user,直接打开,是看不懂得
查看源码Serializable接口里啥也没有
Serializable 接口的意义:
称之为是一个标记性接口,里面没有任何的抽象方法
只要一个类实现了这个Serializable接口,那么就表示这个类的对象可以被序列化.
2.2对象反序列化流【应用】
- 对象反序列化流: ObjectInputStream
- ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
方法名
| 说明
|
ObjectInputStream(InputStream in)
| 创建从指定的InputStream读取的ObjectInputStream
|
方法名
| 说明
|
Object readObject()
| 从ObjectInputStream读取一个对象
|
- 示例代码
User文件是刚刚序列化流ObjectOutputStream写入的文件
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("otheriomoudle/user"));
User user = (User) ois.readObject();
System.out.println(user);//"User{username='zhangsan', password='qwer'}"
ois.close();
}
2.3serialVersionUID&transient【应用】
- 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的.java类文件,读取数据会不会出问题呢?
- 会出问题,会抛出InvalidClassException异常
- 重新序列化
- 给对象所属的类加一个serialVersionUID
- private static final long serialVersionUID = 42L;
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
- 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
代码1:问题引入
学生类
public class User implements Serializable {
private String username;
public String password;
//已经写到本地文件了 又来修改javaBean的结构(eg:private改成了public)
//肯定要报错的: stream classdesc serialVersionUID = -8528945581751241573, local class serialVersionUID = -2165833800645568445
//serialVersionUID 序列号
//如果我们自己没有定义,那么虚拟机会根据类中的信息(成员构造等信息)会自动的计算出一个序列号.
//问题:如果我们修改了类中的信息.那么虚拟机会再次计算出一个序列号.
//第一步:把User对象序列化到本地. --- 此时本地对象有一个序列号 -8528945581751241573
//第二步:修改了javabean类. 导致 --- 类中的序列号变了 -2165833800645568445
//第三步:把文件中的对象读到内存. 本地中的序列号和类中的序列号不一致了.
//解决? 这么大的问题竟然也有解决方法
//不让虚拟机帮我们自动计算,我们自己手动给出.而且这个值不要变.
//空参、全参、get/set、toString
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1.写对象 --- 序列化
//method1(); //先仅执行method1
//2.修改javabean类
//修改User.java 代码
//3.读对象 --- 反序列化
method2(); //修改User类结构后再注释method1 只执行method2
}
//读对象 --- 反序列化
private static void method2() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("otheriomoudle/user"));
User user = (User) ois.readObject();
System.out.println(user);
ois.close();
}
//写对象 --- 序列化
private static void method1() throws IOException {
User user = new User("lisi", "1234");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("otheriomoudle/user"));
oos.writeObject(user);
oos.close();
}
Exception in thread "main" java.io.InvalidClassException: cn.whu.convertedio.User; local class incompatible: stream classdesc serialVersionUID = -8528945581751241573, local class serialVersionUID = -2165833800645568445
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at cn.whu.convertedio.ConvertedDemo5.method2(ConvertedDemo5.java:21)
at cn.whu.convertedio.ConvertedDemo5.main(ConvertedDemo5.java:15)
已经写到本地文件了 又来修改javaBean的结构(eg:private改成了public)
肯定要报错的: stream classdesc serialVersionUID = -8528945581751241573, local class serialVersionUID = -2165833800645568445
serialVersionUID 序列号
如果我们自己没有定义,那么虚拟机会根据类中的信息(成员构造等信息)会自动的计算出一个序列号.
问题:如果我们修改了类中的信息.那么虚拟机会再次计算出一个序列号.
第一步:把User对象序列化到本地. --- 此时本地对象有一个序列号 -8528945581751241573
第二步:修改了javabean类. 导致 --- 类中的序列号变了 -2165833800645568445
第三步:把文件中的对象读到内存. 本地中的序列号和类中的序列号不一致了.
解决? 这么大的问题竟然也有解决方法
不让虚拟机帮我们自动计算,我们自己手动给出.而且这个值不要变.
代码2:解决问题
public class User implements Serializable {
private static final long serialVersionUID = 1L;//值随便写 ★
//忘记怎么写就查看ArrayList源码,第一个局部变量就是
private String username;
private transient String password;//transient不准序列化此变量 那么写不出也读不到了
//空参、全参、get/set、toString
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1.写对象 --- 序列化
//method1(); //先仅执行method1
//2.修改javabean类
//修改User.java 代码
//3.读对象 --- 反序列化
method2(); //修改User类结构后再注释method1 只执行method2
}
//读对象 --- 反序列化
private static void method2() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("otheriomoudle/user"));
User user = (User) ois.readObject();
System.out.println(user);
ois.close();
}
//写对象 --- 序列化
private static void method1() throws IOException {
User user = new User("lisi", "1234");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("otheriomoudle/user"));
oos.writeObject(user);
oos.close();
}
中途修改User类代码也不报错了,password成员变量的值也被隐藏起来了
2.4对象操作流练习【应用】
- 案例需求
创建多个学生类对象写到文件中,再次读取到内存中 - 实现步骤
- 创建序列化流对象
- 创建多个学生对象
- 将学生对象添加到集合中
- 将集合对象序列化到文件中
- 创建反序列化流对象
- 将文件中的对象数据,读取到内存中
public class Student implements Serializable {
private static final long serialVersionUID = 2L;
private String name;
private Integer age;
//空参、全参、get/set、toString
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化--写对象到本地文件
Student s1 = new Student("云天河", 17);
Student s2 = new Student("韩菱纱", 18);
Student s3 = new Student("慕容紫英", 20);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("otheriomoudle/student.txt"));
oos.writeObject(s1);
oos.writeObject(s2);
oos.writeObject(s3);
oos.close();
//反序列化--从本地文件读对象到内存
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("otheriomoudle/student.txt"));
/*Object obj;
while ((obj=ois.readObject())!=null){
System.out.println(obj);
}
//读是读出来了,但还是报错 EOFException
// ois.readObject()这个读方法比较特殊,结束不是返回-1也不是返回null。 而是真的去读文件结束符
//解决:只能是捕获异常了*/
while (true){
Student stu = null;
try {
stu = (Student) ois.readObject();
System.out.println(stu);
} catch (EOFException e) {
break;//读到末尾break
}
}
ois.close();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化--写对象到本地文件
Student s1 = new Student("云天河", 17);
Student s2 = new Student("韩菱纱", 18);
Student s3 = new Student("慕容紫英", 20);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("otheriomoudle/student.txt"));
ArrayList<Student> list=new ArrayList<>();
list.add(s1);list.add(s2);list.add(s3);
//写的时候只写一个集合对象(只有一个对象,读的时候就方便了)
oos.writeObject(list);//本次往文件写一个集合 集合已经是JVM序列化过的
oos.close();
//反序列化--从本地文件读对象到内存
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("otheriomoudle/student.txt"));
//读一个list集合对象即可
ArrayList<Student> stuList = (ArrayList<Student>) ois.readObject();
for (Student stu : stuList) {
System.out.println(stu);
}
ois.close();
}
运行结果同上
3.Properties集合
查看Properties的继承结构,就是Hashtable
而Hashtable就是双列集合 Map
所以Properties集合是一个双列集合
3.1Properties作为Map集合的使用【应用】
- 是一个Map体系的集合类
- Properties可以保存到流中或从流中加载(有2个跟IO相关的方法)
- 属性列表中的每个键及其对应的值都是一个字符串
一般都使用空参构造,不写泛型,也就是可以存储任意类型,一般都只会存储字符串
小结:使用空参Properties,来存储<字符串,字符串>
public static void main(String[] args) {
Properties prop = new Properties();
//增 prop.put(key,value)
prop.put("豹子头","林冲");
prop.put("霹雳火","秦明");
prop.put("急先锋","索超");
System.out.println(prop);//{霹雳火=秦明, 急先锋=索超, 豹子头=林冲}
//删 prop.remove(key);
prop.remove("豹子头");
System.out.println(prop);
//改
//put --- 如果键不存在,那么就添加,如果键存在,那么就修改
prop.put("急先锋","police");
System.out.println(prop);//{霹雳火=秦明, 急先锋=police}
//查
System.out.println(prop.get("霹雳火"));//秦明
//遍历1
Set<Object> keys = prop.keySet();
for (Object key : keys) {
System.out.print(key+"="+prop.get(key)+" | ");//霹雳火=秦明 | 急先锋=police |
}
System.out.println();
//遍历2
//装的是所有的键值对对象
Set<Map.Entry<Object, Object>> entries = prop.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
System.out.print(entry.getKey()+"="+entry.getValue()+" | ");//霹雳火=秦明 | 急先锋=police |
}
}
3.2 Properties作为Map集合的特有方法【应用】
方法名
| 说明
|
Object setProperty(String key, String value)
| 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
|
String getProperty(String key)
| 使用此属性列表中指定的键搜索属性
|
Set< String> stringPropertyNames()
| 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
|
特点:都是String类型,不是Object了
public static void main(String[] args) {
//Object setProperty (String key, String value) --- 和put方法一样
//设置集合的键和值,都是String类型,底层调用 Hashtable方法 put
Properties prop = new Properties();
prop.setProperty("江苏","南京");
prop.setProperty("安徽","南京");
prop.setProperty("山东","济南");
System.out.println(prop);//{安徽=南京, 山东=济南, 江苏=南京}
//String getProperty(String key) --- 和get方法一样
//使用此属性列表中指定的键搜索属性
String value = prop.getProperty("江苏");
System.out.println(value);//南京
//Set<String> stringPropertyNames() --- keySet
//从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串
Set<String> keys = prop.stringPropertyNames();
for (String key : keys) {
System.out.print(key+"="+prop.get(key)+" | ");//安徽=南京 | 江苏=南京 | 山东=济南 |
}
}
3.3 Properties和IO流相结合的方法【应用】★
方法名
| 说明
|
void load(Reader reader)
| 从输入字符流读取属性列表(键和元素对) 【将本地文件中的键值对数据读取到集合中】 |
void store(Writer writer, String comments)
| 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流 【将集合中的数据以键值对形式保存在本地】 |
(本地文件后缀名一定写成.properties)
Properties对象+xx.properties文件 配合读写也太方便了
Properties对象读本地properties文件:prop.load(fr)
public static void main(String[] args) throws IOException {
//void load(Reader reader) 将本地文件中的键值对数据读取到集合中
//读取
Properties prop = new Properties();
FileReader fr = new FileReader("otheriomoudle/prop.properties");
prop.load(fr);//调用完load后,键值对数据就已经在集合中了 || //通过读字符流 读取本地文件内键值对数据
fr.close();
System.out.println(prop);//{password=123, username=zhangsan}
//Properties对象+xx.properties文件 配合读写也太方便了
}
#不要写空格也不要写分号引号啥的
#直接写key=value即可
username=zhangsan
password=123
注意.properties文件内写法 key=value 不要空格,不要分号,不要引号
Properties对象写map到本地properties文件:prop.store(fw,null);
public static void main(String[] args) throws IOException {
//void store(Writer writer, String comments) 将集合中的数据以键值对形式保存在本地
Properties prop = new Properties();
prop.put("zhangsan","123");
prop.put("lisi","456");
prop.put("wangwu","789");
FileWriter fw = new FileWriter("otheriomoudle/prop.properties");
prop.store(fw,null);//通过写字符流将集合数据写到文件
//prop.store(fw,"文件的第一行注释 可以不传 写个null即可");//中文乱码-..-
fw.close();
}
3.4 Properties集合练习【应用】
- 案例需求
在Properties文件中手动写上姓名和年龄,读取到集合中,将该数据封装成学生对象,写到本地文件 - 实现步骤
- 创建Properties集合,将本地文件中的数据加载到集合中
- 获取集合中的键值对数据,封装到学生对象中
- 创建序列化流对象,将学生对象序列化到本地文件中
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
//空参、全参、set/set、toString
}
测试类
public static void main(String[] args) throws IOException, IOException, ClassNotFoundException {
//1.创建Properties集合,将本地文件中的数据加载到集合中
Properties prop = new Properties();
FileReader fr = new FileReader("otheriomoudle/prop.properties");
prop.load(fr);
fr.close();
//2.获取集合中的键值对数据,封装到学生对象中
String name = prop.getProperty("name");
int age = Integer.parseInt(prop.getProperty("age"));
Student s = new Student(name,age);
//3.创建序列化流对象,将学生对象序列化到本地文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("otheriomoudle/propLX.txt"));
oos.writeObject(s);
oos.close();
//4.读序列化对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("otheriomoudle/propLX.txt"));
Student stu = (Student) ois.readObject();
ois.close();
System.out.println(stu);//"Student{name='zhangsan', age=18}"
}
小知识
光标在某个类名中,按F4,可以清楚地看清类的继承结构