飞道的博客

【Java】关于Java中的各种流

349人阅读  评论(0)

1 IO流

1.1 概念

input:输入(读取)-----> 流:数据(字节/字符) -----> output:输出(写入)
输入:把硬盘中的数据,读取到内存中使用
输出:把内存中的数据,写入到硬盘中保存
内存:临时存储
硬盘:永久存储
1个字符 = 2个字节
1个字节=8个二进制位

顶层父类

输入流 输出流
字节流 字节输入流 InputStream 字节输出流 OutputStream
字符流 字符输入流 Reader 字符输出流 Writer

2 字节流

2.1 字节输出流OutputStream

它是一个抽象类

共性成员方法:

  1. public void close() :关闭此输出流并释放与此流相关联的任何系统资源。当完成流的操作时,必须调用此方法,释放系统资源。
  2. public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
  3. public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
  4. public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  5. public abstract void write(int b) :将指定的字节输出流。1次写1个字节。

2.1.1 文件字节输出流FileOutputStream

,extends OutputStream,把内存中的数据写入硬盘的文件中。

构造方法:创建一个 FileOutputStream 对象。根据参数传递的文件/文件路径,创建一个空文件。把 FileOutputStream 对象指向创建好的文件。

  1. FileOutputStream(String name):文件路径
  2. FileOutputStream(File file):文件

写入数据的原理(内存 —> 硬盘)

Java程序 —> JVM —>OS —> OS调用写数据的方法 —>把数据写入文件中

字节输出流的使用步骤

  1. 创建一个 FileOutputStream 对象,构造方法在传递写入数据的目的地
  2. 抵用 FileOutputStream 对象的write方法,把数据写入文件中
  3. 释放资源,清空流占用的内存,提高程序效率

把a写入a.txt文件

public abstract void write(int b)

	public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("a.txt");
        fos.write(97);
        fos.close();
    }

原理:写数据时会把十进制97转为二进制1100001‬,硬盘中存储的数据都是字节,1个字节等于8个比特位。文本编辑器在打开文件时会查询编码表,把字节转为字符表示,97—>a。


一次写入多个字节
public void write(byte[] b)
public void write(byte[] b, int off, int len) :把数组的一部分写入文件

	public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream(new File("b.txt"));
        byte[] a = {65,66,67,68,69};//ABCDE
        byte[] b = {-65,-66,-67,68,69};//烤紻E
        //如果写的第一个字节是正数 显示会查ascii码表
        //如果是负数,第一个字节和第二个字节会组成一个中文显示 查询GBK
        fos.write(b);
        fos.write(a,1,2);//BC
        fos.close();
    }

使用String类的方法把字符串转换为字节数组。

	public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream(new File("b.txt"));
        byte[] b = "哈哈哈".getBytes();
        System.out.println(Arrays.toString(b));
        fos.write(b);
        fos.close();
    }

GBK:两个字节是一个中文
UTF8:三个字节是一个中文


数据的追加写和换行写
追加写的构造方法 append=true
FileOutputStream(String name, boolean append)
FileOutputStream(File file, boolean append)
换行:win【\r\n】mac【/n】linux【/r】

	public static void main(String[] args) throws IOException {
		//追加写
        FileOutputStream fos = new FileOutputStream(new File("b.txt"),true);
        for (int i = 0; i < 10; i++) {
            fos.write("哈哈哈".getBytes());
            //换行写
            fos.write("\r\n".getBytes());
        }
        fos.close();
    }

2.2 字节输入流InputStream

它是一个抽象类,是所有字节输入流类的超类
共性方法

  • int read()
  • int read(byte[] b)
  • void close

2.2.1 文件字节输入流FileInputStream

作用:把硬盘中的数据,读取到内存中使用

构造方法

  • FileInputStream(String name)
  • FileInputStream(File file)

构造方法的作用

  1. 创建一个FileInputStream对象
  2. 把FileInputStream对象指向指定构造方法中要读取的文件

读取数据原理(硬盘—>内存)
Java程序 —> JVM —>OS —>OS调用读取数据的方法 —> 读取文件

字节输出流的使用步骤

  1. 创建FileInputStream对象,构造方法中绑定要读取的数据源
  2. 调用FileInputStream对象的read方法,读取文件
  3. 释放资源

注意:read每调用一次指针都会后移一位,如果while循环括号中不保存读取的值,输出的就是跳位读取的值。

一次读取一个字节

	public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("a.txt");
        int len = 0;
        while((len = fis.read())!=-1){
            //读取一个字节并返回 文件末尾返回-1
            System.out.print((char) len);
        }
        fis.close();
    }

一次读取多个字节
int read(byte[] b):
注意:byte[]的作用是起缓冲作用,存储每次读取到的多个字节。数组长度一般定义为1024(1kb)或者1024的整数值。int返回值是每次读取的有效字节个数。

原理:创建一个byte数组,数组元素的初始值为0。开始读取,把读取出的数据存入byte数组,指针移动,b.length是一次读取的字节个数。new String()可以把byte数组转换为字符串输出。read方法的返回值是有效读取字节个数。


	public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("a.txt");
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = fis.read(bytes))!=-1){
            System.out.print(new String(bytes,0,len));//打印byte中存的有效位
        }
    }

2.3 字节流练习-文件复制

原理
创建一个输入流的对象,再创建一个输出流的对象,然后输入流对象读取文件内容,写入输出流指向的对象处。
注意:应该先关闭输出流(写),再关闭输入流(读)。因为写完了一定读完了,但读完了不一定写完了。应该先开启输入流,再开启输出流,先读后写。

	public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("D:\\1.png");
        FileOutputStream fos = new FileOutputStream("E:\\1.png");
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len=fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        fos.close();
        fis.close();
    }

2.4 字节流读文件存在的问题

在读取中文字符的时候,可能不会显示完整的字符,因为一个中文可能占用多个字节。按字节流读取,每次只能读取字符的一部分。所以文本文件一般用字符流进行读写
GBK:1个中文2个字节
UTF8:1个中文3个字节

3 字符流

3.1 字符输入流Reader

字符输入流类最顶层的父类,是一个抽象类。

共性方法

  • int read()
  • int read(char[] c)
  • void close()

3.1.1 文件字符输入流FileReader

作用:把硬盘文件中的数据以字符的方式读取到内存中

构造方法:创建一个FileReader对象,把FileReader对象指向要读取的文件/文件路径

  • FileReader(String name)
  • FileReader(File file)

使用步骤

  1. 创建FileReader对象,构造方法中绑定要读取的数据源
  2. FileReader对象调用read方法
  3. 关闭,释放资源

一次读取一个字符

	public static void main(String[] args) throws IOException {
        FileReader fd = new FileReader("a.txt");
        int len = 0;
        while((len = fd.read())!=-1){
            System.out.print((char) len);
        }
        fd.close();
    }

一次读取多个字符

字符数组 —> 字符串 new String(char[], int off, int len)构造方法

	public static void main(String[] args) throws IOException {
        FileReader fd = new FileReader("a.txt");
        char[] c = new char[1024];
        int len = 0;
        while((len = fd.read(c))!=-1){
            System.out.print(new String(c,0,len));
        }
        fd.close();
    }

3.2 字符输出流Writer

共性方法

  • void write(int c) 写入单个字符。
  • void write(char[] cbuf) 写入字符数组。
  • abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
  • void write(String str) 写入字符串。
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
  • void flush() 刷新该流的缓冲。
  • void close() 关闭此流,但要先刷新它。

3.2.1 文件字符输出流FileWriter

作用:内存中的字符数据写入文件中

构造方法

  • FileWriter(String name)
  • FileWriter(File file)

作用:创建FileWriter对象,根据构造方法中传递的文件/文件路径创建文件,会把FileWriter对象指向创建好的文件中。

使用步骤

  1. 创建FileWriter对象,构造方法中绑定写入数据的目的地。
  2. 使用FileWriter中的方法write,把数据写入到内存缓冲区中。(字符转换为字节的过程)
  3. 使用FileWriter中的方法flush,把内存缓冲区的数据,刷新到文件中。
  4. 释放资源(会先把内存缓冲区的数据刷新到文件中)

写入单个字符

	public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("a.txt");//绑定写入位置
        fw.write(97);//写入缓冲区 字符-->字节
        fw.flush();//写入文件
        fw.close();//释放资源
    }

flush和close方法的区别
close在关闭之前会先把内存缓冲区的数据刷新到文件中,但close之后不能继续使用write方法。flush同样也是刷新操作,但flush之后可以继续使用write方法。

其他写入方法

  • void write(char[] c):写入字符数组
  • abstract void write(char[] c, int off, int len):写入字符数组的某一部分
  • void write(String str):写入字符串
  • void write(String str, int off, int len):写入字符串的某一部分
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("a.txt");
        char[] c = {'a','b','c','d','e','f'};
        fw.write(c);
        fw.write('\n');
        fw.write(c,3,3);
        String str = "嘻嘻哈哈呵呵";
        fw.write(str);
        fw.write('\n');
        fw.write(str,1,3);
        fw.close();
    }

续写和换行写
同字节流输出

	public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("c.txt",true);
        for (int i = 0; i < 10; i++) {
            fw.write("哈哈哈"+i);
            fw.write("\r\n");
        }
        fw.close();
    }

4 IO流的异常处理

在JDK1.7之前可以使用try-catch-finally处理流中的一次

	public static void main(String[] args) {
        FileWriter fw = null;
        try{
            fw = new FileWriter("F:\\c.txt",true);
            for (int i = 0; i < 10; i++) {
                fw.write("哈哈哈"+i);
                fw.write("\r\n");
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(fw != null){
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

JDK7的新特性:可以在try后增加一个 ( ) ( ) ,括号中可以定义流对象,那么这个流对象的作用域就在try中有效,try执行完后,流对象自动释放,不用写finally【常用】

    public static void main(String[] args) {
        try(FileWriter fw = new FileWriter("F:\\c.txt",true)){
            for (int i = 0; i < 10; i++) {
                fw.write("哈哈哈"+i);
                fw.write("\r\n");
            }
        }catch (IOException e){
            e.printStackTrace();
        }
        System.out.println("哈哈哈哈哈");
    }

JDK9的新特性,try前面可以定义对象,在try后边的()中可以引入流对象的名称。try执行完毕后,流对象可以释放掉,不需要写finally。【不常用】

	public static void main(String[] args) {
        FileReader fr = new FileReader("c.txt");
        FileWriter fw = new FileWriter("d.txt");
        try(fr;fw){
            char[] c = new char[1024];
            int len = 0;
            while((len = fr.read(c))!=-1){
                fw.write(c);
            }
            fw.flush();
        }catch (IOException e){
            e.printStackTrace();
        }
        System.out.println("哈哈哈哈哈");
    }

5 属性集

java.util.Properties 继承于Hashtable,来表示一个持久的属性集。是唯一一个和IO流相结合的集合。

它使用键值结构存储数据,每个及其对应都是一个字符串。该类也被许多Java类使用,比如获取系统属性时, System.getProperties 方法就是返回一个Properties 对象。

使用Properties存储数据并遍历取出

操作字符串的特有方法

  • Object setProperty(String key, String value):相当于map.put(k, v)
  • String getProperty(String key):相当于map.get(k)
  • Set <String> stringPropertyNames():相当于map.keySet()
	public static void main(String[] args) {
        Properties prop = new Properties();
        prop.setProperty("张三","20");
        prop.setProperty("李四","25");
        prop.setProperty("王五","30");

        Set<String> set = prop.stringPropertyNames();
        for(String key: set){
            String value = prop.getProperty(key);
            System.out.println(key+" "+value);
        }
    }
  • 可以使用store方法把集合中的临时数据,持久化写入硬盘中存储
  • void store(OutputStream out, String comments):字节输出流,不能写中文
  • void store(Writer writer, String comments):字符输出流,可以写中文

comments是注释,用来解释保存的文件是做什么的,不可以使用中文,默认是Unicode编码,一般使用空字符串

使用步骤

  1. 创建一个Proprorities对象,添加数据
  2. 创建字节输出流/字符输出流对象,构造方法绑定输出位置
  3. 使用store方法把集合中的临时数据持久化写入硬盘中。
  4. 释放输出流对象
	public static void main(String[] args) throws IOException {
        Properties prop = new Properties();
        prop.setProperty("张三","20");
        prop.setProperty("李四","25");
        prop.setProperty("王五","30");

        FileWriter fw = new FileWriter("e.txt");
        prop.store(fw, "");
        fw.close();
    }
  • 可以使用load方法,把硬盘中保存的文件,读取到集合中使用
  • void load(InputStream in):字节输入流,不能读取含有中文的键值对
  • void load(Reader r):字符输入流,可以读取含有中文的键值对

使用步骤

  1. 创建一个Properties对象,load堆区保存键值对的文件
  2. 遍历Properties集合

注意

  • 键值对文件中,键和值默认的连接符可以使用=、空格
  • 可以使用#来注释,被注释的键值对不会再读取
  • 键和值默认都是字符串,不用再加引号
	public static void main(String[] args) throws IOException {
        Properties prop = new Properties();
        prop.load(new FileReader("f.txt"));
        Set<String> set = prop.stringPropertyNames();
        for(String key: set){
            System.out.println(key+" "+prop.getProperty(key));
        }
    }

6 缓冲流

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:
字节缓冲流: BufferedInputStream , BufferedOutputStream
字符缓冲流: BufferedReader , BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

6.1 字节缓冲流

6.1.1 字节缓冲输出流BfferedOutputStream

继承自OutputStream,可使用父类共性方法(见2.1)

构造方法

  • BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以数据写入指定的底层输出流。
  • BufferedOutputStream(OutputSteam out, int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。

使用步骤

  1. 创建FileOutputStream对象,构造方法中绑定输出位置。
  2. 创建BufferedOutputStream对象,构造方法中传递fos对象,提高fos对象效率。
  3. 使用bos对象的write方法,把数据写入到内部缓冲区中。
  4. 使用bos对象的flush方法,把缓冲区的数据刷新到文件中。
  5. 释放资源,close会先调用flush刷新数据再关闭(所以第四步可省略)
	public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("b.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        bos.write("写入内部缓冲区".getBytes());//字节输出流 用字节
        bos.flush();
        bos.close();
    }

6.1.2 字节缓冲输入流BfferedInputStream

继承自InputStream,可使用父类共性方法(见2.2)

构造方法

  • BufferedInputStream(InputStream in):创建一个新的缓冲输入流,保存参数输入流in。
  • BufferedInputStream(InputSteam in, int size):创建一个具有指定缓冲区大小的BufferedInputStream保存其参数,即输入流。

使用步骤

  1. 创建FileInputStream对象,构造方法中绑定读取位置。
  2. 创建BufferedInputStream对象,构造方法中传递fis对象,提高fis对象的读取效率。
  3. 使用bis对象的read方法,把数据写入到内部缓冲区中。
  4. 释放资源。
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("a.txt");
        BufferedInputStream bis = new BufferedInputStream(fis);
        /*
        int len = 0;
        while((len = bis.read()) != -1){
            System.out.print((char)len);
        }
        bis.close();
         */
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len = bis.read(bytes))!=-1){
            System.out.println(new String(bytes));
        }
    }

6.1.3 基本和缓冲效率比较

	public static void main(String[] args) throws IOException {
        long s = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream("D:\\1.png");
        FileOutputStream fos = new FileOutputStream("E:\\1.png");
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        byte[] bytes = new byte[1024];
        int len = 0;
        while((len=bis.read(bytes))!=-1){
            bos.write(bytes,0,len);
        }
        bos.close();
        bis.close();
        long e = System.currentTimeMillis();
        System.out.println(e-s);
    }

文件复制4MB的ppt的效率比较:

基本流(一次读一个字节):17757 ms
缓冲流(一次读一个字节):121 ms
基本流 + 数组缓冲区(一次读多个字节):27 ms
缓冲流 + 数组缓冲区(一次读多个字节):9 ms

6.2 字符缓冲流

6.2.1 字符缓冲输出流BufferedWriter

继承自Writer,可使用父类共性方法(见3.2)

构造方法

  • BufferedWriter(Writer out):创建一个使用默认大小输出缓冲区的缓冲字符输出流。
  • BufferedWriter(Writer out, int size):创建一个使用指定大小输出缓冲区的缓冲字符输出流。

特有方法

  • void newLine():写入一个行分隔符,它会根据不同的操作系统,获取不同的行分隔符。

使用步骤

  1. 创建字符输出流对象,构造方法中传递写入地址
  2. 创建字符缓冲区输出流对象,构造方法中传递字符输出流
  3. 调用flush,将内存缓冲区中的数据刷新到文件中
  4. 释放资源
	public static void main(String[] args) throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
        for (int i = 0; i < 10; i++) {
            bw.write("哈哈哈哈");
            bw.newLine();
            bw.write("嘻嘻嘻嘻");
            bw.newLine();
        }
        bw.close();
    }

6.2.2 字符缓冲输入流BufferedReader

继承自Reader,可使用父类共性方法(见3.1)

构造方法

  • BufferedReader(Reader in):创建一个使用默认大小输入缓冲区的缓冲字符输入流。
  • BufferedReader(Reader in, int size):创建一个使用指定大小输入缓冲区的缓冲字符输入流。

特有方法

  • String readLine():读取一行文本,读取一行数据,流末尾返回null,readLine不读换行符

使用步骤

  1. 创建字符输入流对象,构造方法中传递读取地址
  2. 创建字符缓冲区输入流对象,构造方法中传递字符输入流
  3. 调用read/readLine读取
  4. 释放资源
	public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("b.txt"));
        String line = null;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
    }

6.3 缓冲流练习-文本排序

b.txt内容:
7.77777777777777
3.张三3333333333
5.王五5555555555
1.大一1111111111
4.李四4444444444
2.小二2222222222
6.六六6666666666

	public static void main(String[] args) throws IOException {
        Map<String , String> map = new HashMap<>();
        BufferedReader br = new BufferedReader(new FileReader("b.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("nb.txt"));
        String line;
        while((line = br.readLine())!=null){
            String[] split = line.split("\\.");
            map.put(split[0],split[1]);
        }
        //map会自动排序
        for(String key: map.keySet()){
            String value = map.get(key);
            bw.write(key+"."+value);
            bw.newLine();
        }
        bw.close();
        br.close();
    }

7 转换流

7.1 字符编码和字符集

字符编码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本f符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。

字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。

字符集
字符集 Charset :也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。

ASCII是最基本的编码表,GBK是中文编码表
Unicode是万国码,兼容各种语言,是应用中优先采用的编码。

7.2 编码常见问题

例如:在IDEA中,使用FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。

解决方法:使用转换流

7.3 转换流相关内容

字节转换为字符(解码)
FileInputStream —> 查询编码表 —> FileReader

7.3.1 OutputStreamWriter

字符流通向字节流的桥梁
可以指定编码格式
写入你想写入的编码格式的文件中

构造方法

  • OutputStreamWriter(OutputStream out):创建使用默认字符编码的OutputStreamWriter(utf8)
  • OutputStreamWriter(OutputStream out, String charSet):创建使用指定字符编码的OutputStreamWriter

使用步骤

  1. 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定编码集
  2. 使用osw对象中的write,把字符转换为字节存在缓冲区中
  3. 使用osw对象中的flush把字节刷新到文件中
  4. 释放资源

你好 GBK 4字节 UTF8 6字节

    public static void main(String[] args) throws IOException {
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk");
        osw.write("你好");
        osw.close();
    }

7.3.2 InputStreamReader

字节流通向字符流的桥梁
可以指定编码格式
读取字节解码为字符 把看不懂的变成看得懂的

构造方法

  • InputStreamReader(InputStream in):创建使用默认字符编码的OutputStreamWriter(utf8)
  • InStreamReader(InputStream in, String charSet):创建使用指定字符编码的InputStreamReader

使用步骤

  1. 创建InputStreamReader对象,构造方法中传递字节输入流和指定编码集
  2. 使用isw对象中的read读取文
  3. 释放资源

注意:如果文件编码和指定编码不一致,则会发生乱码

    public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("utf8.txt"),"utf-8");
        int len = 0;
        while((len = isr.read())!=-1){
            System.out.print((char)len);
        }
        isr.close();
    }

7.4 练习:转换文件编码

将GBK编码的文本文件,转换为UTF8编码的文本文件

public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"gbk");
        OutputStreamWriter osr = new OutputStreamWriter(new FileOutputStream("gbk2utf8.txt"),"utf-8");
        int len = 0;
        while((len = isr.read()) != -1){
            osr.write(len);
        }
        osr.close();
        isr.close();
    }

8 序列化和反序列化

8.1 概念

ObjectOutputStream对象的序列化流
把对象以流的方式写入到文件中保存,叫写对象,也叫对象的序列化。对象中包含的不仅是字符,使用字节流。

ObjectInputStream对象的反序列化流
把文件中的对象,以流的方式读取出来,叫读对象,也叫对象的反序列化。文件保存的都是字节,使用字节流。

8.2 对象的序列化流ObjectOutputStream

构造方法

  • ObjectOutputStream(OutputStream out)

特有方法

  • void writeObject(Object obj)

使用步骤

  1. 创建ObjectOutputStream对象,构造方法中传递字节输出流
  2. 使用writeObject方法写入对象
  3. 释放资源

序列化和反序列化时,会抛出NotSerializableException没有序列化异常。
类通过实现Serializable接口以启动序列化功能,没有实现此接口的类无法使其任何状态序列化或反序列化。
Serializable接口也叫标记型接口,进行序列化和反序列化,必须要实现Serializable接口,给类添加标记。在使用时,会检测是否有标记。

使用前提
Person类的实现 要加上implement Serializable

	public static void main(String[] args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
        oos.writeObject(new Person("张三",15));
        oos.close();
    }

8.3 对象的反序列化流ObjectInputStream

构造方法

  • ObjectInputStream(InputStream in)

特有方法

  • void readObject(Object obj) 读取对象

使用步骤

  1. 创建ObjectOutputStream对象,构造方法中传递字节输入流
  2. 使用writeObject方法读取对象
  3. 释放资源
  4. 打印对象

readObject方法声明抛出了ClassFoundException,class文件找不到异常,当不存在对象的class文件时抛出异常
使用前提
Person类的实现 要加上implement Serializable
对象的class类文件要存在

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.txt"));
Object o = ois.readObject();
ois.close();
System.out.println(o);

【注意!!!】
InvalidClassException异常:序列化之后对类进行更改之后,不重新序列化,而是直接反序列化会出现异常。

因为javac编译器会把java文件编译生成class文件。类如果实现了Serializable接口,就会根据类的定义给类的class文件添加一个序列化号serialVersionUID,输出的文件也会写入这个ID,反序列化的时候,会使用class文件的序列号和输出文件的序列号比较,如果ID一致,则反序列化一致,否则会抛出InvalidClassException异常

而修改了类的定义之后,会给class文件重新编译生成一个新的序列号,但是输出文件的序列号没有改。

解决方案:手动给类加一个序列号。无论是否修改类都不再修改序列号。

自定义类中添加成员常量。

private static final long serialVersionID = 1L;

瞬态关键字transient
static静态关键字:静态优先于非静态加载到内存中,(静态优先于对象),所以static修饰的成员变量是不能被序列化的,序列化的都是对象。
【在上例中,如果Person类的age成员变量是静态的,序列化和非序列化age将永远是初始值0,无法更改】

transient瞬态关键字:被它修饰的成员变量不能被序列化。如果想要对象中的成员变量不被序列化,可以使用瞬态关键字。

8.4 练习:序列化集合

当我们想在文件中保存多个对象的时候,可以把对象存在集合中,对集合进行序列化和反序列化。

	public static void main(String[] args) throws IOException, ClassNotFoundException {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("张三",15));
        list.add(new Person("李四",19));
        list.add(new Person("王五",20));
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("PersonList.txt"));
        oos.writeObject(list);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("PersonList.txt"));
        Object o = ois.readObject();
        ArrayList<Person> list2 = (ArrayList<Person>) o;
        for (Person p : list2){
            System.out.println(p);
        }
        ois.close();
        oos.close();
    }

9 打印流

9.1 PrintSteam类

平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。

特点

  • 不会抛出IO异常
  • 只负责输出,不负责读取
  • 特有方法print、println

构造方法

  • PrintStream(File file):输出目的地是一个文件
  • PrintStream(OutputStream out):输出目的地是一个字节输出流
  • PrintStream(String path):输出目的地是一个文件路径

注意

  • 如果用继承父类的write方法写数据,查看数据的时候就会查询编码表,再打印
  • 如果使用特有方法print和println则会原样输出
	public static void main(String[] args) throws IOException, ClassNotFoundException {
        PrintStream ps = new PrintStream("a.txt");
        ps.write(97);
        ps.write('\n');
        ps.println(97);
        ps.print("a1#$@哈哈哈");
        ps.close();
    }

可以改变输出语句的目的地,即打印流的流向。
输出语句,默认在控制台输出,使用System.setOut方向改变输出语句的目的地改为参数中传递的打印流的目的地。

	public static void main(String[] args) throws IOException, ClassNotFoundException {
        System.out.println("控制台输出:哈哈哈");
        PrintStream ps = new PrintStream("打印流输出");
        System.setOut(ps);
        System.out.println("打印流输出:哈哈哈");//控制台不输出这一句 而是写在"打印流输出"文件中
    }

转载:https://blog.csdn.net/xd963625627/article/details/105358210
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场