day22【Properties、ResourceBundle工具类、缓冲流、转换流、序列化】课上

1.属性集(掌握,很重要)

概念介绍

1.Properties,属于双列集合,他的父类Hashtable,jdk1.0就存在了。

2.该集合没有泛型,因为该集合中的存放数据类型都是String类型

3.可以将该集合的数据存储到持久设备上(了解),也可以将持久设备上的数据加载到内存中(很重要)

4.Properties可以将持久设备上的数据加载到内存中,主要是加载和当前项目有关配置文件中的内容

​ 配置文件:就是一个文件(文本文件),存放一些key=value的数据,这些数据都是关于当前项目的一些信息。

​ 有关加载数据库信息 有关配置框架信息,并且配置文件中的数据没有汉字

特有方法

package com.itheima.sh.properties_01;

import java.util.Properties;
import java.util.Set;

/*
    构造方法:
        Properties() 创建一个无默认值的空属性列表。
    Properties属性集的特有方法:
        1.Object setProperty(String key, String value) 调用 Hashtable 的方法 put。添加数据
        2.String getProperty(String key) 用指定的键在此属性列表中搜索属性。 相当于 value get(key)方法
            根据键获取值
        3.Set<String> stringPropertyNames()  获取所有的键 相当于 keySet
 */
public class PropertiesDemo01 {
    public static void main(String[] args) {
        //1.创建集合对象 Properties() 创建一个无默认值的空属性列表。
        Properties p = new Properties();
        //2.添加数据
        p.setProperty("张杰", "谢娜");
        p.setProperty("汪峰", "章子怡");
        p.setProperty("邓超", "孙俪");
        p.setProperty("张杰", "柳岩");
        //3.获取所有键
        Set<String> keys = p.stringPropertyNames();
        //4.取出每个键
        for (String key : keys) {
            //获取值
            String value = p.getProperty(key);
            System.out.println(key+"---"+value);
        }

    }
}

小结:

1.Object setProperty(String key, String value) 调用 Hashtable 的方法 put。添加数据
2.String getProperty(String key) 用指定的键在此属性列表中搜索属性。 相当于 value get(key)方法
根据键获取值
3.Set stringPropertyNames() 获取所有的键 相当于 keySet

使用Properties集合读取配置文件(开发使用)

1.使用该集合读取的配置文件要求如下:

​ 1)必须以key=value形式存在

​ 2)不要在配置文件中书写分号

​ 3)配置文件存在的位置可以任意,可以放到项目根目录,还可以放到src位置(后期都放在src位置)

​ 4)不建议书写中文,但是可以书写

​ 5)不要书写双引号,因为配置文件中的数据都是字符串

2.代码演示:

配置文件:

username=root
password=1234
package com.itheima.sh.properties_01;

import java.io.FileInputStream;
import java.io.FileReader;
import java.util.Properties;
import java.util.Set;

/*
    使用Properties集合读取配置文件
    使用方法:
         1.void load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。
         2.void load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
    说明:
        1.上述加载方法执行完毕之后配置文件中的数据就会被加载到属性集对象中
 */
public class PropertiesDemo02 {
    public static void main(String[] args) throws Exception{
        //1.创建属性集对象
        Properties p = new Properties();
        //2.使用对象调用加载方法加载配置文件的数据
//        FileInputStream fis = new FileInputStream("person.properties");
        //上述加载方法执行完毕之后配置文件中的数据就会被加载到属性集对象中
        //1.void load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。
//        p.load(fis);
        //2.void load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
        p.load(new FileReader("person.properties"));
        //3.遍历集合p
        Set<String> keys = p.stringPropertyNames();
        for (String key : keys) {
            System.out.println("键:"+key);
            System.out.println("值:"+p.getProperty(key));
        }
    }
}

小结:

1.使用属性集Properties中的加载方法可以加载配置文件中的数据:

 1.void load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。
     	如果配置文件中有汉字不能使用,会出现乱码
 2.void load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。

2.配置文件的后缀名不一定是.properties

3.上述加载方法执行完毕之后配置文件中的数据就会被加载到属性集对象中

4.可以将该集合的数据存储到持久设备上(了解):

 void store(OutputStream out, String comments) 将集合中的数据长久保存到持久设备上
     		参数:
     				out:关联要写的文件的路径
                    comments:表示配置文件的说明即注释,不要写中文。配置文件中的注释使用 # 表示
 

2.ResourceBundle工具类(掌握)

  • 作用:专门用来加载当前项目以.properties 结尾的配置文件。

  • 属于抽象类,不能创建对象,我们需要使用该工具类中的静态方法获取子类(PropertyResourceBundle )对象

    static ResourceBundle getBundle(String baseName) 根据指定的配置文件名获取ResourceBundle类的对象,不需要给后缀名
        举例:假设配置文件整体名字是:person.properties 这里只需给person 即可
    
  • 根据配置文件中的key获取value:

     String getString(String key)  
    
  • 使用该工具类ResourceBundle读取的配置文件要求必须位于src下面,否则不能读取

  • 代码实现

    username=root
    password=1234
    
    package com.itheima.sh.resourcebundle_02;
    
    import java.util.ResourceBundle;
    
    public class Test01 {
        public static void main(String[] args) {
            //1.获取ResourceBundle类的对象
            /*
                static ResourceBundle getBundle(String baseName) 根据指定的配置文件名获取ResourceBundle类的对象,不需要给后缀名
                    举例:假设配置文件整体名字是:person.properties 这里只需给person 即可
             */
            ResourceBundle bundle = ResourceBundle.getBundle("user");
            //2.String getString(String key)   根据key获取value
            String username = bundle.getString("username");
            String password = bundle.getString("password");
            System.out.println(username+"--"+password);//root--1234
        }
    }
    
    

    小结:

    1.获取ResourceBundle类的对象

 static ResourceBundle getBundle(String baseName) 根据指定的配置文件名获取ResourceBundle类的对象,不需要给后缀名
            举例:假设配置文件整体名字是:person.properties 这里只需给person 即可

​ 2.String getString(String key) 根据key获取value

​ 3.ResourceBundle只能操作src下面并且后缀名是.properties

​ 4.如果配置文件中有中文(了解)

解决乱码:

在这里插入图片描述

在这里插入图片描述

3.缓冲流(掌握)

之前学习的基本流用来操作文件中的数据,但是效率有可能会低一些,为了提高读写效率,我们引入缓冲流.

缓冲流作用:就是为了提高读写效率。

名字:在之前学习的基本流的父类前面加Buffered

1.BufferedInputStream ----> 字节输入缓冲流

2.BufferedOutputStream ----> 字节输出缓冲流

3.BufferedReader ---->字符输入缓冲流

4.BufferedWriter ---->字符输出缓冲流

1.字节缓冲流

1.BufferedInputStream ----> 字节输入缓冲流 父类是:InputStream

BufferedInputStream(InputStream in) 参数in表示字节输入流,关联读取的文件 底层有数组      

2.BufferedOutputStream ----> 字节输出缓冲流 父类是:OutputStream

BufferedOutputStream(OutputStream out) 参数out表示字节输出流,关联写的文件 底层有数组     

代码演示:

需求:分别使用之前学习的基本流和现在的缓冲流复制文件,查看复制的时间

package com.itheima.sh.buffered_03;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/*
    需求:分别使用之前学习的基本流和现在的缓冲流复制文件,查看复制的时间
 */
public class BufferedDemo01 {
    public static void main(String[] args) throws Exception{
        method_2();
    }
    //使用缓冲流复制
    private static void method_2() throws Exception{

        //1.创建字节输入流关联数据源文件D:\test\制服诱惑2.mp4
        FileInputStream fis = new FileInputStream("D:\\test\\制服诱惑2.mp4");
        //BufferedInputStream(InputStream in) 参数in表示字节输入流,关联读取的文件 底层有数组
        BufferedInputStream bis = new BufferedInputStream(fis);

        //2.创建字节输出流关联目的地文件F:\制服诱惑2.mp4
        FileOutputStream fos = new FileOutputStream("F:\\制服诱惑2.mp4");
        //BufferedOutputStream(OutputStream out) 参数out表示字节输出流,关联写的文件 底层有数组

        BufferedOutputStream bos = new BufferedOutputStream(fos);

        //3.定义字节数组
        byte[] buf = new byte[1024*8];
        //4.定义变量保存每次读取的字节个数
        int len;
        long start = System.currentTimeMillis();
        //5.使用循环读取数据
        while((len=bis.read(buf))!=-1){
            //6.使用字节输出流将内存中的数据写到目的地
            bos.write(buf,0,len);
        }
        long end = System.currentTimeMillis();
        System.out.println(end -start);//191ms
        //7.关闭资源  释放资源规律:从下往上释放
        bos.close();
        fos.close();
        bis.close();
        fis.close();


    }

    //使用之前学习的基本流复制文件
    //将D:\test\制服诱惑2.mp4复制到F:\制服诱惑2.mp4
    private static void method_1() throws Exception{
        //1.创建字节输入流关联数据源文件D:\test\制服诱惑2.mp4
        FileInputStream fis = new FileInputStream("D:\\test\\制服诱惑2.mp4");
        //2.创建字节输出流关联目的地文件F:\制服诱惑2.mp4
        FileOutputStream fos = new FileOutputStream("F:\\制服诱惑2.mp4");
        //3.定义字节数组
        byte[] buf = new byte[1024*8];
        //4.定义变量保存每次读取的字节个数
        int len;
        long start = System.currentTimeMillis();
        //5.使用循环读取数据
        while((len=fis.read(buf))!=-1){
            //6.使用字节输出流将内存中的数据写到目的地
            fos.write(buf,0,len);
        }
        long end = System.currentTimeMillis();
        System.out.println(end -start);//640ms
        //7.关闭资源
        fos.close();
        fis.close();
    }
}

小结:

字节缓冲流作用:提高读写效率的。

2.字符缓冲流

3.BufferedReader ---->字符输入缓冲流 ----父类Reader

构造方法:

BufferedReader(Reader in) 参数in表示关联的读取的文件路径

特有方法:

String readLine() 读取一个文本行放到返回值中,如果读取到文件末尾返回null

以前基本流模板:

char[] ch = new char[1024];
int len;
while((len=fr.read(ch))!=-1){
    ......
}

高效流模板:

String line = null;//line保存每次读取一行数据
while((line=br.readLine())!=null){
    ......
}

4.BufferedWriter ---->字符输出缓冲流------父类Writer

构造方法:

BufferedWriter(Writer out) 参数out表示关联的写的文件路径

特有方法:

void newLine() 写入一个行分隔符。 

代码演示:

package com.itheima.sh.buffered_03;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;

/*
    字符缓冲流:
    1.BufferedReader ---->字符输入缓冲流 ----父类Reader
        构造方法:
            BufferedReader(Reader in) 参数in表示关联的读取的文件路径
        特有方法:
            String readLine() 读取一个文本行放到返回值中,如果读取到文件末尾返回null
    2.BufferedWriter ---->字符输出缓冲流------父类Writer
        构造方法:
            BufferedWriter(Writer out) 参数out表示关联的写的文件路径
        特有方法:
            void newLine() 写入一个行分隔符。
 */
public class BufferedDemo02 {
    public static void main(String[] args) throws Exception{
        //需求:将D:\test\故事.txt复制到项目根目录
        //1.创建字符输入缓冲流对象关联数据源文件 D:\test\故事.txt
        //BufferedReader(Reader in) 参数in表示关联的读取的文件路径
        FileReader fr = new FileReader("D:\\test\\故事.txt");
        BufferedReader br = new BufferedReader(fr);
        //2.创建字符输出缓存流对象关联目的地文件根目录故事.txt
        FileWriter fw = new FileWriter("故事.txt");
        BufferedWriter bw = new BufferedWriter(fw);
        //3.读取数据
        String line = null;
        while((line=br.readLine())!=null){
            //4.写数据
            bw.write(line);
            //换行
            bw.newLine();
            //刷新
            bw.flush();
        }
        //5.释放资源
        bw.close();
        fw.close();
        br.close();
        fr.close();
    }
}

小结:

1.BufferedReader类提供了一次可以读取一行数据的特有方法:String readLine()读取文件末尾返回null

2.BufferedWriter类中提供了一个换行方法 void newLine()

3.字符缓冲流练习(课下必须完成)

package com.itheima.sh.buffered_03;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.HashMap;

/*
    需求:将123.txt中内容按顺序写到456.txt中
    分析:
        先读取一行数据:
            String line="3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯。。。"
         上述数据由三部分组成:
            1)序号 3
            2)点 .
            3) 文字
         按照点进行切割 String[] split("切割符号")
         line.split(".")  这里参数的点表示任意字符 如果想表示真正的点 \\.
          String[] arr = line.split("\\.");
           数组中的数据:arr = {"3","侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯。。。"}
         将数组第一个数据序号变为整数---》3  Integer.parseInt("3") ---》 3

         将上述数组中的序号变为整数之后,作为Map集合的key存储  数组的第二个数据作为value存储到map集合
         HashMap<Integer,String> hm = new HashMap<Integer,String>();
         hm.put(Integer.parseInt("3"),"侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯。。。");

         遍历map集合根据序号取出value然后拼接,最后使用字符输出流写到目的地文件456.txt中
    步骤:
    1.创建Map集合对象
    2.创建字符输入缓冲流对象关联数据源文件123.txt
    3.创建字符输出缓冲流对象关联目的地文件456.txt
    4.使用字符输入缓冲流读取133.txt文件,每次读取异常数据存储到变量line中
    5.使用变量line调用切割方法line.split("\\."); 按照点进行切割
    6.取出数组的第一个数据变为整数存储到map集合键位置,第二个数据即文字存储到value位置
    7.遍历map集合取出key和value
    8.将key和value进行拼接并使用字符输出流写到目的地文件456.txt中
    9.关闭资源
 */
public class Test01 {
    public static void main(String[] args) throws Exception {
        //1.创建Map集合对象
        HashMap<Integer, String> hm = new HashMap<Integer, String>();
        //2.创建字符输入缓冲流对象关联数据源文件123.txt
        BufferedReader br = new BufferedReader(new FileReader("123.txt"));

        //3.创建字符输出缓冲流对象关联目的地文件456.txt
        BufferedWriter bw = new BufferedWriter(new FileWriter("456.txt"));
        //4.使用字符输入缓冲流读取123.txt文件,每次读取异常数据存储到变量line中
        String line = null;
        while ((line = br.readLine()) != null) {
            // 5.使用变量line调用切割方法line.split("\\."); 按照点进行切割
            String[] arr = line.split("\\.");
            //6.取出数组的第一个数据变为整数存储到map集合键位置,第二个数据即文字存储到value位置
            //数组中的数据:arr = {"3","侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯。。。"}
            //将数组第一个数据即序号字符串3转换为整数
//            int key = Integer.parseInt(arr[0]);
            hm.put(Integer.parseInt(arr[0]), arr[1]);
        }
        /*
            map的数据:
                3  侍中、侍郎郭攸之
                1  先帝创业未半而中道崩殂
         */
        //7.遍历map集合取出key和value
        /*for(int key=1;key<=hm.size();key++){
            //根据key获取value
            String value = hm.get(key);
            // 8.将key和value进行拼接并使用字符输出流写到目的地文件456.txt中
            bw.write(key+"."+value);
            //换行
            bw.newLine();
            //刷新
            bw.flush();
        }*/
        for (int i = 1; i <= hm.size(); i++) {//i表示序号
            //将序号赋值给变量key
            Integer key = i;
            //根据key获取value
            String value = hm.get(key);
            // 8.将key和value进行拼接并使用字符输出流写到目的地文件456.txt中
            bw.write(key + "." + value);
            //换行
            bw.newLine();
            //刷新
            bw.flush();
        }

        //9.关闭资源
        bw.close();
        br.close();
    }
}

4.转换流(理解)

编码表

介绍

我们可以将编码表看作一个词典。

hello---->你好

编码表中:

字符(人类) 二进制(计算机)

A----------65------01000001

在这里插入图片描述

  • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)支持英文字母 数字 符号。

    在ASCII码表中一个字符占一个字节。

  • ISO-8859-1码表:支持欧洲 别名称为Latin1 拉丁文 内部有ASCII

    在ISO-8859-1码表一个字符占一个字节

  • GBxxx字符集 :内部含有ASCII,针对汉字的码表

    • GB2312 支持6000多汉字
    • GBK :国标 支持几万
    • GB18030:支持甲骨文。。。

    在gbk码表中一个汉字占2个字节,英文字母 数字 特殊符号占一个字节

  • Unicode字符集:万国码表,支持所有国家的字符

    • 主要包含:UTF-8 UTF-16 UTF-32
    • 在UTF-8码表中:
      • 英文字母 数字 特殊符号占1个字节
      • 欧洲字符占2个字节
      • 大部分汉字占3个字节
  • 重点掌握的码表:

    • ASCII 一个字符占一个字节。
    • ISO-8859-1码表:在ISO-8859-1码表一个字符占一个字节
    • GBK : 支持汉字 。在gbk码表中一个汉字占2个字节,英文字母 数字 特殊符号占一个字节
    • UTF-8:英文字母 数字 特殊符号占1个字节 。大部分汉字占3个字节
  • 编码、解码、乱码

    • 编码:‘A’-----65----01000001 就是将我们看的懂的字符编码为我们看不懂的内容

    • 解码:01000001 -----65 ---- ‘A’ 就是将我们看不懂的内容变为看的懂的字符

    • 乱码:就是编码和解码使用的编码表不一致导致的

      举例;
      锁哥----按照GBK进行编码   锁:2373632622
      解码:使用UTF-8  锁: 1726272626
         
          此时会产生乱码:
          Γゥ
      解决乱码:
          按照之前的编码进行编码回去,然后在重新解码
          上述使用UTF-8乱码的,我们需要使用UTF-8编码回到之前状态
          在使用正确的码表GBK解码
      

编码引出的问题以及解决方案

package com.itheima.sh.charset_04;

import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStreamReader;

/*
    需求:使用字符输入流读取E:\\1.txt文件中的数据,要求:1.txt使用GBK编码方式保存。
    我们将数据输出到控制台发现出现乱码:
        1.txt文件使用的编码表是GBK
        而我们使用字符输入流FileReader来读取文件,该流使用当前环境默认的编码表:UTF-8
        上述编码和解码使用编码表不一致了产生乱码。
    解决:不能使用FileReader类,因为该类不能随意指定编码表,只能是当前环境默认的编码表,我们可以使用其父类:
        InputStreamReader 属于字符输入转换流 父类-----Reader
        使用构造方法可以指定编码表:
        InputStreamReader(InputStream in, String charsetName)
                参数:
                    in:关联要读取的文件的路径
                    charsetName:指定的编码表
 */
public class Demo01 {
    public static void main(String[] args) throws Exception{
        //1.创建字符输入流关联1.txt文件
//        FileReader fr = new FileReader("E:\\1.txt");

        //创建字符输入流并指定编码表是GBK
        InputStreamReader fr = new InputStreamReader(new FileInputStream("E:\\1.txt"), "GBK");
        //2.读取数据
        char[] ch = new char[1024];
        int len;
        while((len=fr.read(ch))!=-1){
            //输出
            System.out.println(new String(ch,0,len));//你好
        }
        fr.close();
    }
}

小结:

1.FileReader表示字符输入流的基本流,但是只能是按照当前环境默认编码表

2.InputStreamReader作为FileReader的父类。表示字符输入转换流,可以指定任意的编码表。

3.InputStreamReader属于字符流,除了可以指定编码表,还可以将字节输入流转换为字符输入流

InputStreamReader fr = new InputStreamReader(new FileInputStream("E:\\1.txt"), "GBK");

OutputStreamWriter类

1.表示字符输出转换流。

2.该类的父类是Writer,属于字符流

3.该类可以指定编码表并且可以将字节输出流转换为字符输出流

OutputStreamWriter(OutputStream out, String charsetName) 
    	参数:
    		out:表示关联写数据的文件路径 属于字节输出流
            charsetName:指定编码表
OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter。

代码演示:

package com.itheima.sh.charset_04;

import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.OutputStreamWriter;

/*
    需求:使用字符输出转换流写数据,要求文件编码表使用的是GBK
 */
public class Demo02 {
    public static void main(String[] args) throws Exception{
        //创建字符输出的基本流 FileWriter 使用当前环境默认的编码表即UTF-8
//        FileWriter fw = new FileWriter("D:\\aaa\\5.txt");
        /*
            OutputStreamWriter(OutputStream out, String charsetName)
                参数:
                    out:表示关联写数据的文件路径 属于字节输出流
                    charsetName:指定编码表
         */
        OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream("D:\\aaa\\6.txt"), "GBK");
        //写数据
        fw.write("黑马程序员");
        //刷新
        fw.flush();
        //释放资源
        fw.close();
    }
}

小结:

1.OutputStreamWriter表示字符输出转换流,父类是Writer

2.作用:

​ 1)可以指定编码表

​ 2)将字节输出流转换为字符输出流

  OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream("D:\\aaa\\6.txt"), "GBK");

总结:

1.只要某个流的类末尾是Stream就属于字节流,否则就是字符流

2.末尾是Reader表示字符输入流 ; 末尾是Witer表示字符输出流

3.字符转换流:

在这里插入图片描述

转换流的练习(课下完成)

package com.itheima.sh.charset_04;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

/*
    需求:把D:\\out.txt以UTF-8编码的文件转换为 以GBK编码的文件
    步骤:
    1.创建字符输入转换流对象,指定读取文件的编码表是UTF-8
    2.创建字符输出转换流指定编码表是GBK将内存中的数据写到指定文件中
    3.使用输入流读取数据源文件D:\\out.txt
    4.使用字符输出转换流将数据写到目的地文件D:\\aaa\\7.txt
    5.关闭资源
 */
public class Test01 {
    public static void main(String[] args) throws Exception{
        //1.创建字符输入转换流对象,指定读取文件的编码表是UTF-8
        InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\out.txt"), "UTF-8");
        //2.创建字符输出转换流指定编码表是GBK将内存中的数据写到指定文件中
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\aaa\\7.txt"), "GBK");
        //3.使用输入流读取数据源文件D:\\out.txt
        char[] ch = new char[1024];
        int len;
        while((len=isr.read(ch))!=-1){
            //4.使用字符输出转换流将数据写到目的地文件D:\\aaa\\7.txt
            osw.write(ch,0,len);
            //刷新
            osw.flush();
        }
        //5.关闭资源
        osw.close();
        isr.close();
    }
}

5.序列化和反序列化流(掌握)

介绍

1.序列化流:可以将我们在程序中即内存中创建的对象长久保存到持久设备上。

Person p = new Person("锁哥",18);

2.反序列化流:就是将之前序列化到持久设备上的对象数据读取到内存中

使用

  • 序列化流的类:ObjectOutputStream—父类 OutputStream

    • 构造方法:

      ObjectOutputStream(OutputStream out) 参数out表示字节输出流关联的写对象的文件的路径
      
    • 写对象方法:序列化方法

      void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。 
      
  • 反序列化流的类:ObjectInputStream — 父类 InputStream

    • 构造方法

      ObjectInputStream(InputStream in) 参数in表示字节输入流关联的读取对象所在的文件路径
      
    • 读对象方法:反序列化方法

       Object readObject() 从 ObjectInputStream 读取对象。 将读取的对象放到返回值中
      
  • 上述两个流都是字节流

  • 序列化和反序列使用注意:

    • 被序列化或者反序列化的对象所属类必须实现序列化接口java.io.Serializable ,该接口表示序列化接口,没有任何成员,属于标记性接口。标记其实现类可以被序列化和反序列化。

代码实现:

package com.itheima.sh.object_stream_05;

import java.io.Serializable;
import java.util.stream.Stream;
/*
    被序列化或者反序列化的对象所属类必须
    实现序列化接口java.io.Serializable ,
    该接口表示序列化接口,没有任何成员,属于标记性接口。
    标记其实现类可以被序列化和反序列化。
 */
public class Person implements Serializable{
    //成员变量
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }

    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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


package com.itheima.sh.object_stream_05;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

/*
    序列化对象
    需求:将创建的自定义类Person的对象序列化到指定文件中
 */
public class ObjectOutputStreamDemo01 {
    public static void main(String[] args) throws Exception{
        //1.创建Person对象
        Person p = new Person("锁哥", 19);
        //2.创建序列化流的对象
        //ObjectOutputStream(OutputStream out) 参数out表示字节输出流关联的写对象的文件的路径
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
        //3.使用对象调用序列化方法序列化对象
        //void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream。
        oos.writeObject(p);
        //4.关闭资源
        oos.close();
    }
}


package com.itheima.sh.object_stream_05;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

/*
    反序列化的
 */
public class ObjectInputStreamDemo02 {
    public static void main(String[] args) throws Exception{
        //1.创建反序列流的对象
        //ObjectInputStream(InputStream in) 参数in表示字节输入流关联的读取对象所在的文件路径
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"));
        //2.使用对象调用反序列化方法进行读取之前序列化到硬盘中的对象
        // Object readObject() 从 ObjectInputStream 读取对象。 将读取的对象放到返回值中
        Person p = (Person) ois.readObject();
        //3.获取姓名和年龄
        System.out.println(p.getName()+"----"+p.getAge());
    }
}

小结:

1.ObjectOutputStream表示序列化类,使用方法writeObject可以将任意对象写到持久设备上

2.ObjectInputStream表示反序列化类,使用方法readObject可以读取序列化的对象

3.被序列化或者反序列化的对象所属类必须实现序列化标记性接口java.io.Serializable

序列化和反序列化使用细节

1.被序列化或者反序列化的对象所属类必须实现序列化标记性接口java.io.Serializable

2.如果对象的某个成员变量的值不想被序列化到硬盘上,我们可以使用瞬态关键字transient修饰或者使用static修饰

//transient 表示瞬态 就是修饰的内容不会被序列化到硬盘上
    private transient String name;
    private static int age;

3.如果我们对于某个类的对象已经序列化了,然后我们又修改了该类的内容,然后不用直接序列化,而是反序列化就会出现异常:

java.io.InvalidClassException: com.itheima.sh.object_stream_05.Person; 
local class incompatible:
	stream classdesc serialVersionUID = -2950267769722938759, 
	local class serialVersionUID = -1806161995263188763
        
说明:
        报上述无效的类异常原因:该类的序列版本号与从流中读取的类描述符的版本号不匹配 
        
        
  1.在java中如果一个类实现了序列化版本号Serializable,那么编译器就会根据该类的所有内容(类的成员变量 成员方法 修饰符等)在对应的.class文件中生成一个默认的版本号 serialVersionUID ,该版本号是一个long类型的,该版本号我们称为本地版本号:local class serialVersionUID 
        
  2.当我们使用序列化流将某个类的对象即Person对象序列化到文件中,同时也会将.class文件中的序列化版本号也写到指定文件中,那么此时该文件中也会有一个版本号,此版本号称为流中的版本号:stream classdesc serialVersionUID
        
  3.每次只要修改被序列化的类(Person),那么就会根据新的内容在本地重新生成一个新的本地版本号,如果我们不进行序列化,直接反序列化,由于生成的序列化文件中的流版本号还是之前的,那么就会导致本地新的版本号和流中反序列化的版本号不一致了,此时就会报异常
        
  4.每次在反序列化的时候都会拿本地版本号和流中版本号进行比对是否相等,如果不相等,则报异常InvalidClassException
        
        如果两个版本号相等:则可以正常反序列化
        
        
  5.为何要像上述这样做?
        为了安全
  6.这样做虽然安全了,但是对于程序员来说过于敏感。所以我们在实际开发中建议在需要被序列化的类中显示声明一个版本号,这样每次修改类就不会根据类的成员内容生成版本号了。
        
 public class Person implements Serializable{
    private static final long serialVersionUID = -8838871950488599879L;
 }

在这里插入图片描述

序列化集合

需求:我想序列化多个对象。

可以将多个对象存储到集合中,然后序列化集合即可。

package com.itheima.sh.object_stream_05;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;

public class Test01 {
    public static void main(String[] args) throws Exception{
        //1.创建集合存储自定义Person类的对象
        ArrayList<Person> list = new ArrayList<>();
        //2.创建Person对象
        Person p1 = new Person("锁哥",18);
        Person p2 = new Person("岩岩",19);
        Person p3 = new Person("杨幂",20);
        Person p4 = new Person("冰冰",18);
        //3.添加数据
        Collections.addAll(list, p1, p2, p3, p4);

        //4.创建序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));
        //5.调用方法进行序列化
        oos.writeObject(list);

        //6.创建反序列化类的对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt"));
        //7.反序列化集合
        ArrayList<Person> list2 = (ArrayList<Person>) ois.readObject();
        //8.遍历
        for (Person p : list2) {
            System.out.println(p.getName()+"----"+p.getAge());
        }

    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章