栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

13-IO流3-转换流&对象操作流&Properties(jdk11前指定编码读写文字、序列化和反序列化加密存储、)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

13-IO流3-转换流&对象操作流&Properties(jdk11前指定编码读写文字、序列化和反序列化加密存储、)

文章目录
    • 1.转换流
      • 1.1字符流中和编码解码问题相关的两个类【理解】(新学习两个类 )
      • 1.2转换流读写数据【应用】
    • 2.对象操作流
      • 2.0 引入
      • 2.1对象序列化流【应用】
      • 2.2对象反序列化流【应用】
      • 2.3serialVersionUID&transient【应用】
        • 代码1:问题引入
        • 代码2:解决问题
      • 2.4对象操作流练习【应用】
    • 3.Properties集合
      • 3.1Properties作为Map集合的使用【应用】
  • 小知识

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();
    }

    
    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【应用】
  • serialVersionUID

    • 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的.java类文件,读取数据会不会出问题呢?
      • 会出问题,会抛出InvalidClassException异常
    • 如果出问题了,如何解决呢?
      • 重新序列化
      • 给对象所属的类加一个serialVersionUID
        • private static final long serialVersionUID = 42L;
  • transient

    • 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
      • 给该成员变量加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对象操作流练习【应用】
  • 案例需求

    创建多个学生类对象写到文件中,再次读取到内存中

  • 实现步骤

    • 创建序列化流对象
    • 创建多个学生对象
    • 将学生对象添加到集合中
    • 将集合对象序列化到文件中
    • 创建反序列化流对象
    • 将文件中的对象数据,读取到内存中
  • 代码实现1
    学生类

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"));

        

        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 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 stuList = (ArrayList) ois.readObject();
        for (Student stu : stuList) {
            System.out.println(stu);
        }
        ois.close();
    }

运行结果同上

3.Properties集合 3.1Properties作为Map集合的使用【应用】
  • Properties介绍

    • 是一个Map体系的集合类
    • Properties可以保存到流中或从流中加载
    • 属性列表中的每个键及其对应的值都是一个字符串
  • Properties基本使用

小知识

光标在某个类名中,按F4,可以清楚地看清类的继承结构

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1007753.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号