【JAVA CORE_API】Day16 IO流、文件流、换种字节流、对象流

Java IO

IO的基本概念

输入(Input)与输出(Output)

文件的输入和输出哪个是用来读的?哪个用来写的? —— 输入用来读的,输出是用来写的!计算机输入输出和人类认知相反,是因为,输入输出的参照物是程序本身,进入到程序中就是输入input,从程序中输出就是output,一定是相对于程序而言的。

用一种更好理解的方式:
就例如我们发现一本好书,我们读(Read)这本书的过程就是书中的数据通过神经流(Stream)通过某个管道(输入流)输入(Input)到脑海;在写(Write)文章的时候我们发现可以用到这本书中的某句话,那么我们就将脑海里的数据通过神经的流(Stream)经过某个管道(输出流)输出(Output)到纸上。

流的概念

Java将IO比喻为“流”(Stream)。就像是生活中的“电流”、“水流”一样,它是以同一个方向顺序移动的过程。只不过这里流动的是字节(2进制数据)。所以在IO中有输入流和输出流之分,我们理解他们是连接程序与另一端的“管道”,用于获取和发送数据到另一端;

在这里插入图片描述

  • 流(Stream)是我们连接程序和另一端设备的一根管道,通过这个管道来进行读写;

  • 我们观察角度一定要在程序上,设备到程序创建输入流,程序到设备输出流;

在这里插入图片描述

  • 内存(程序)中的数据通过管道读(Read)取文件 —— 这个过程即“输入数据”

在这里插入图片描述

  • 内存(程序)中的数据通过管道写(Write)入文件 —— 这个过程即“输出数据”

InputStream和OutputStream

  • java.io.InputStream:所有字节输入流的父类,其中定义了读取数据的方法。因此在之后不管读取数据的是什么设备(连接设备的流),都具有这些读取的方法,因此我们可以用相同的方法来读取不同设备中的数据。

  • java.io.OutputStream:所有字节输出流的父类,其中定义了写出数据的方法。

OutputStream输出流
  • 实例化一个输出流管道:FileOutputStream fileOutputStream = new FileOutputStream("filePath");

  • OutputStream是所有字节输出流的父类,其定义了基础的写入方法,常用方法如下:

    • fileOutputStreamName.write(int d):写出一个字节,写的是给定的int的“二进制低八位”;

    • 注意:任何流管道打开(使用)以后必须关闭管道,原因是为了防止资源泄露和数据丢失;

InputStream输入流
  • fileInputStreamName.read():读取一个字节,以int形式返回,该int值的“低八位”二进制有效,若返回值为-1则表示EOF(End of File);

  • fileInputStreamName.read(byte[] d):尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量。

文件流

文件流的作用

  • 文件流是实际连接程序与硬盘中文件的“管道”,用于读写文件数据的流;

  • 文件流以字节为单位读写文件数据;

  • 文件输出流:可以将数据从内存写入到磁盘文件中,可以将数据持久保存;

  • 文件输入流:可从硬盘文件中将数据读取回到内存中使程序回复原有状态。

在这里插入图片描述

构造器

常用构造器

  • 构造器:

    • FileInputStream(String filename):创建一个从具有指定名称的文件中读取数据的文件输入流;

      package day16;
      
      import java.io.FileNotFoundException;
      import java.io.FileOutputStream;
      import java.io.IOException;
      
      public class FosDemo {
          public static void main(String[] args) throws IOException;
              // 创建从当前目录下的fos.dat文件中读取数据的文件输入流
              FileOutputStream fos = new FileOutputStream("fos.dat");
          }
      }
      
      
    • FileInputStream(File file):创建一个从指定File对象表示的文件中读取数据的文件输入流;

       package day16;
       
       import java.io.FileNotFoundException;
       import java.io.FileOutputStream;
       import java.io.IOException;
       
       public class FosDemo {
           public static void main(String[] args) throws IOException;
               // 创建从当前项目目录下的fos.dat文件中读取数据的文件输入流
               File file = new File("fos.dat");
               FileOutputStream fos = new FileOutputStream(file);
           }
       }
       
      
    • 如果指定的文件不存在会抛出异常:FileNotFoundException

写出字节数据

  • 文件输出流继承自java.io.OutputStream

  • 文件输出流提供了父类中要求写出字节的相关方法;

    • fileOutputStreamName.write(int d):写出一个字节。写出的是给定int值d对应二进制的“低八位”;

       package day16;
       
       import java.io.FileNotFoundException;
       import java.io.FileOutputStream;
       import java.io.IOException;
       
       /**
        * 文件流:是连接程序与文件的通道,从而可以读写文件数据;
        * 文件输出流(FileOutputStream):用于向文件中写入数据;
        */
       public class FosDemo {
           public static void main(String[] args) throws IOException {
               // 创建了一个文件的输出流(管道),用于向fos.dat文件中写入数据
               // 创建文件输出流时,若文件不存在则会自动创建出来,若已存在该文件将覆盖源文件;
               // 若目录不存在,则会报错;
               FileOutputStream fos = new FileOutputStream("fos.dat");
               fos.write(1);
               // 其实这里写入的是1的二进制低八位表示,1字节 = 32位,即00000000 00000000 00000000 00000001 --低八位--> 00000001
               fos.write(2);
               // 其实这里写入的是2的二进制低八位表示,1字节 = 32位,即00000000 00000000 00000000 00000010 --低八位--> 00000010
               System.out.println("写出结束!");
               // 实际fos获得的数据是:00000001 00000010 -> 所以会导致输出进入文件一个奇怪的东西,这里暂时不必理会,感受写入的过程即可
               fos.close();  // 关闭文件输出流
           }
       }
       
      
      • 当我们要连续写入更多字符时,就需要用到追加(append)模式:

        • 构造方法:

          • FileOutputStream(File file, boolean append):创建一个向指定File对象表示的文件中写出数据的文件输出流;
           package day16;
           
           import java.io.FileInputStream;
           import java.io.FileNotFoundException;
           import java.io.FileOutputStream;
           import java.io.IOException;
           
           /**
            * 向文件中写入字符数据
            */
           public class Test1 {
               public static void main(String[] args) throws IOException {
                   /**
                    * 创建一个文件输出流对象
                    */
                   FileOutputStream fos = new FileOutputStream("fos.txt", true);  // append参数如果写则为追加模式,默认为覆盖模式;txt文件即可写入可视数据
                   // 写入所有小写字母
                   for (char c = 'a'; c <= 'z'; c++) {
                       fos.write(c);
                   }
                   System.out.println("写出完毕!");
                   fos.close();
               }
           }
           
          
          • FileOutputStream(String filename, boolean append):创建一个向具有指定名称的文件中写出数据的文件输出流;

          • 以上两种构造方法中,第二个参数若为true,那么通过该FOS写出的数据都是在文件末尾追加的,但是如若此处为空不写,则默认覆盖原数据。

    • fileOutputStreamName.write(byte[] data):块写操作,一次性将给定字节数组data中所有字节写出(下面会详细讲块写操作);

    • fileOutputStreamName.write(byte[] data, int offset, int len):块写操作,一次性将给定字节数组data中从下表offset处开始连续输出len个字节写出(下面会详细的讲)。

读取字节数据

  • 文件输出流继承自java.io.InputStream

  • 文件输出流提供了父类中要求的读取字节的相关方法;

    • fileInputStreamName.read():读取一个字节。返回的int值d表示读取的1字节的数据内容,其二进制的“低八位”有效,如果返回值为整数-1,则表示流读取到了末尾(EOF:End of File);

       package day16;
       
       import java.io.FileInputStream;
       import java.io.FileNotFoundException;
       import java.io.IOException;
       
       /**
        * 使用文件输入流从文件中读取数据
        */
       public class FisDemo {
           public static void main(String[] args) throws IOException {
               FileInputStream fis = new FileInputStream("fos.txt");  // 创建文件输入流对象,会出现异常一样抛出异常
       
               /*
                 fos.dat文件的数据:
                 00000001 00000010 文件末尾
       
                 第1次读: 读取文件中的   00000001 00000010 文件末尾
                                      ^^^^^^^^
                 返回的d的二进制数据:
                 00000000 00000000 00000000 00000001------------(1)
                */
               int d = fis.read();  // 读取文件中的数据,返回一个int类型的数据,一样会报异常,抛出异常
               System.out.println(d);  // 输出读取到的数据,1
       
               /*
                 第2次读: 读取文件中的   00000001 00000010 文件末尾
                                               ^^^^^^^^
                 返回的d的二进制数据:
                 00000000 00000000 00000000 00000010------------(2)
                */
               d = fis.read();
               System.out.println(d);  // 2
       
               /*
                 第3次读: 读取文件中的   00000001 00000010 文件末尾
                                                       ^^^^^^^^
                 返回的d的二进制数据:
                 11111111 11111111 11111111 11111111------------(-1)
                 32位2进制全是1,即为整数-1,用于表示流读取到了末尾
                */
               d = fis.read();
               System.out.println(d); // -1
       
               fis.close();  // 关闭文件输入流,不要忘记关水龙头
           }
       }
       
      
      • 同样的,当我们想连续读取更多数据时:
       package day16;
       
       import java.io.FileInputStream;
       import java.io.FileNotFoundException;
       import java.io.FileOutputStream;
       import java.io.IOException;
       
       /**
        * 向文件中写入字符数据
        */
       public class Test1 {
           public static void main(String[] args) throws IOException {
               /**
                * 创建一个文件输入流对象
                */
               FileInputStream fis = new FileInputStream("fos.txt");
               int d;
               while ((d = fis.read()) != -1) {
                   System.out.print((char) d);  // d是int类型,输出时原样输出,即字母的ASCII码值,所以需要强转为char类型
               }
               System.out.println();
               System.out.println("读取完毕!");
               fis.close();
           }
       }
       
      
    • fileInputStreamName.read(byte[] data):块读操作,一次性读取给定字节数组data总长度的数据量并从数组第一个字节位置开始存入到数组中,返回值表示实际读取到的数据量。如果返回值为-1则表示读取到了EOF;

复制文件

单字节复制

文件复制

 package day16;
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 
 /**
  * 文件复制
  */
 public class CopyDemo {
     public static void main(String[] args) throws IOException {
         // 提前准备了一个testPhoto.jpg在jsd2407项目下
         // 1.演示单字节读取方式复制文件
         FileInputStream fis = new FileInputStream("testPhoto.jpg");
         FileOutputStream fos = new FileOutputStream("testPhoto_CP.jpg");
         // 获取自1970年1月1日0时0分0秒0毫秒以来,到此时此刻的毫秒数
         long start = System.currentTimeMillis();  
         int d;
         while ((d = fis.read()) != -1) {
             fos.write(d);
         }
         long end = System.currentTimeMillis();
         System.out.println("复制完毕!总耗时:" + (end - start) + "毫秒");
         fis.close();
         fos.close();
     }
 }
 

在这里插入图片描述

  • 单字节读写完成的赋值操作读写效率差

    • 主要是因为磁盘的硬件特性决定着单字节的读写性能时极其低效的;

    • 这里涉及到磁盘的很多机械特性,马甲带动,电与磁的转换等问题;

    • 越频繁的磁盘与内存之间交互,效率越低。

块读写

块读操作

  • 父类java.io.InputStream上定义了块读字节的方法:public int read(byte b[]) throws IOException{...}

    • 从流中最多读取b.length个字节数据并存入数组b中;

    • 返回值为实际读取到的字节数;

    • 若返回值为-1则表示读取到了末尾;

     package day16;
     
     import java.io.*;
     
     /**
      * 块读写演示:
      */
     public class CopyDemo02 {
         public static void main(String[] args) throws IOException {
             FileInputStream fileInputStream = new FileInputStream("testPhoto.jpg");
             FileOutputStream fileOutputStream = new FileOutputStream("testPhoto_CP2.jpg");
             long start = System.currentTimeMillis();
     
             // 块读写
             int len;
             byte[] data = new byte[1024 * 10];  // 一次10KB的块读写方式
             while ((len = fileInputStream.read(data)) != -1) {  // 块读,也是10KB的速度
                 fileOutputStream.write(data); // 块写,也是10KB的速度
             }
     
             long end = System.currentTimeMillis();
             System.out.println("复制完成,耗时:" + (end - start) + "毫秒");
             fileInputStream.close();
             fileOutputStream.close();
         }
     }
     
    

    在这里插入图片描述

块读写复制

块写操作

  • 我们发现用块复制后的复制的文件比原来的文件大:

    在这里插入图片描述

    1)这是由于数组data在第一次被定义时存储10240个字节:

    在这里插入图片描述

    2)第二次:

    在这里插入图片描述

    3)第三次:

    在这里插入图片描述

    4)第四次,数组长度不变,就会产生脏数据

    在这里插入图片描述

如何避免脏数据产生

  • 父类java.io.OutputStream上定义了重载块写字节方法:public void write(byte b[], int off, int len) throws IOException{...}

    • 将b数组从下表off处开始的连续len个字节一次性写出;
  • 所以我们可以改造刚才复制的代码:

 package day16;
 
 import java.io.*;
 
 /**
  * 块读写演示:
  */
 public class CopyDemo02 {
     public static void main(String[] args) throws IOException {
         FileInputStream fileInputStream = new FileInputStream("testPhoto.jpg");
         FileOutputStream fileOutputStream = new FileOutputStream("testPhoto_CP2.jpg");
         long start = System.currentTimeMillis();
 
         // 块读写
         int len;
         byte[] data = new byte[1024 * 10];  // 一次10KB的块读写方式
         while ((len = fileInputStream.read(data)) != -1) {  // 块读,也是10KB的速度
         //  fileOutputStream.write(data);  // 块写,也是10KB的速度,有脏数据   
             fileOutputStream.write(data, 0, len); // 块写,也是10KB的速度,可能避免脏数据
         }
 
         long end = System.currentTimeMillis();
         System.out.println("复制完成,耗时:" + (end - start) + "毫秒");
         fileInputStream.close();
         fileOutputStream.close();
     }
 }
 

在这里插入图片描述

写入文本数据

写入文本数据

  • 使用字符串的getBytes()方法将字符串转换为一组字节,不推荐使用这种无参构造方式,因为会产生跨平台问题出现,建议使用下面这种;

  • byte[] getBytes(Charset cn):举个例子看下:

      package day16;
      
      import java.io.FileNotFoundException;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.nio.charset.StandardCharsets;
      
      public class WriteStringDemo {
          public static void main(String[] args) throws IOException {
              FileOutputStream fos = new FileOutputStream("test.txt");
              String line = "缓缓飘落的枫叶像思念 我点燃烛火温暖岁末的秋天 极光掠过天边 北风掠过想你的容颜";
      
              byte[] data = line.getBytes(StandardCharsets.UTF_8);
              fos.write(data);
      
              line = "我把爱烧成了落叶 却换不回熟悉的那张脸";
              data = line.getBytes(StandardCharsets.UTF_8);
              fos.write(data);
              System.out.println("写出完毕!");
              fos.close();
          }
      }
      
    

    在这里插入图片描述

    在这里插入图片描述

  • 补充介绍一下UTD-8编码:

    在Unicode的传输格式基础上增加了长度信息;

    • 英文、符号、数字这类字符还是一个字节(ASCII);

    • 中文、日文、韩文、俄文、泰文等字符,需要两个字节(Unicode);

    • UTF-8,中文变为3个字节,多一个字节用来获取长度;

    在这里插入图片描述

连续写入文本数据(简易记事本)

 package day16;
 
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Scanner;
 
 /**
  * 案例: 简易记事本
  * 需求: 程序启动时,要求将用户在控制台上输入的每一行字符串都写入到note.txt中;
  *      如果用户输入了"exit",则结束写入;
  * 注意: 写入文件的内容不需要考虑换行
  */
 public class NoteTest {
     public static void main(String[] args) throws IOException {
         Scanner scanner = new Scanner(System.in);  // 实例化一个扫描仪对象
         FileOutputStream fileOutputStream = new FileOutputStream("note.txt");  // 实例化一个文件输出流对象
         while (true) {
             System.out.println("请输入要记录的内容:");
             String line = scanner.nextLine();  // 通过扫描仪获取用户输入的字符串
             if ("exit".equalsIgnoreCase(line)) {  // 判断用户输入的字符串是否为exit
                 break;
             }
             byte[] data = line.getBytes(StandardCharsets.UTF_8);  // 将字符串转换为字节数组
             fileOutputStream.write(data); // 将字节数组写入文件
         }
         System.out.println("记录完成!再见!");
         fileOutputStream.close();
     }
 }
 

读取文本数据

  • 使用文件输入流读取文件中的所有字节;

  • 使用String的构造器String(byte[] data)可以将读取的字节转换为对应的字符串;

     package day16;
     
     import java.io.File;
     import java.io.FileInputStream;
     import java.io.IOException;
     import java.nio.charset.StandardCharsets;
     
     /**
      * 读取文本数据
      */
     public class ReadStringDemo {
         public static void main(String[] args) throws IOException {
             // 创建一个File构建器,之后创建一个FileInputStream输入流构建器
             File file = new File("test.txt");
             FileInputStream fileInputStream = new FileInputStream(file);
             // 创建一个与test.txt文件等长的字节数组
             byte[] data = new byte[(int)file.length()]; // 获取文件长度,byte数组需要传入int类型的长度,不用担心超出限度,够用了
             // 使用块读操作一次性将文件的所有字节读入到字节数组中
             fileInputStream.read(data);
             // 使用String的构造器可以将给定字节数组所有字节按照UTF-8编码转换为字符串
             String line = new String(data, StandardCharsets.UTF_8); // 要求些什么格式读取什么格式
             System.out.println(line);
             fileInputStream.close();
         }
     }
     
    

处理流

节点流与处理流

什么是低级流、什么是高级流?

简单地说,就例如用水管举例,自来水公司通过水管到各户家里,这个管道就是低级流,但是这时候我想用热水洗澡,我们不能利用烧水再和凉水勾兑,太麻烦了,于是我们使用热水器,自来水管连进热水器,热水器就是一个高级流;再就是我们如果想喝过滤水,我们也可以选择装一个过滤器,这也是一个高级流;那如果我们想用纯净水去洗澡呢?我们就可以先接一个过滤器这个高级流,再接一个热水器高级流,将水进行二次、三次、…处理,在关流的时候关的也是高级流,就像洗完澡要关的是热水器,而不是水阀。

  1. 低级流和高级流,节点流就是低级流,处理流就是高级流;

  2. 前面讲的文件就是节点流,节点流就是真是连接我们程序和设备的那个管道,负责读写那个设备的流称为低级流;

  3. 高级流也叫处理流,它不能独立存在,它总是连接在其他流上,目的是当数据经过它的时候,它做加工处理;

  4. 高级流就是对流中数据做加工处理的,低级流是保证数据从哪里来到哪里去的,可以把低级流理解为搬运数据的流,而高级流是处理数据的流,实际开发中,经常将它们连在一起使用,这个连接的过程叫做“流连接”。

缓冲字节流

  • 在流连接中通常直接连接在低级流上,可以保证读写字节数据的效率;

    在这里插入图片描述

  • BufferedInputStream(OutputStream out):实例化一个缓冲字节输出流并连接在字节输出流上。默认缓冲区大小为8KB(内部维护的byte[] buf数组长度8192)。

    BufferedOutputStream(OutputStream out, int size):实例化一个指定size(缓冲区大小)的缓冲字节输出流并连接在指定的字节输出流上。

    package day16;
    
    import java.io.*;
    
    /**
     * 使用缓冲字节流完成文件的复制
     * 作用:加快读写速度,提高效率
     */
    public class CopyDemo03 {
        public static void main(String[] args) throws IOException {
            // 高级流一定构建在低级流上,所以先构建一个低级流
            FileInputStream fileInputStream = new FileInputStream("testPhoto.jpg");  // 低级流 —— 文件输入流
            BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);  // 高级流 —— 将缓冲字节流(高级流)构建在低级流(低级流)上
            FileOutputStream fileOutputStream = new FileOutputStream("testPhoto_CP_CP_CP.jpg");  // 低级流 —— 文件输出流
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);  // 高级流 —— 将缓冲字节流(高级流)构建在低级流(低级流)上
    
            long start = System.currentTimeMillis();
            int len;
            byte[] data = new byte[1024 * 10];
            while ((len = bufferedInputStream.read(data)) != -1) {
                bufferedOutputStream.write(data, 0, len);
            }
            
            long end = System.currentTimeMillis();
            System.out.println("复制完成,耗时:" + (end - start) + "毫秒");
    
            bufferedInputStream.close();  // 关闭高级流时,顺便将低级流关闭了
            bufferedOutputStream.close();  // 关闭高级流时,顺便将低级流关闭了
        }
    }
    
    
  • 但是,这种方式存在一个弊端,如果我们的数据不足以装满缓冲区8KB时,缓冲字节流将一直等待满时再将数据写出,当我们需要在执行完某写出操作后,希望数据确实写出,而非在缓冲区中保存直到缓冲区满后才写出,我们可以使用void flush()清空缓冲区,将缓冲区中的数据强制写出。

     package day16;
     
     import java.io.BufferedOutputStream;
     import java.io.FileNotFoundException;
     import java.io.FileOutputStream;
     import java.io.IOException;
     import java.nio.charset.StandardCharsets;
     
     /**
      * 缓冲输出流写出数据的缓冲区问题
      */
     public class FlushDemo {
         public static void main(String[] args) throws IOException {
             FileOutputStream fileOutputStream = new FileOutputStream("bos.txt");
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
     
             String string = "而我独缺 你一生的了解......";
             byte[] data = string.getBytes(StandardCharsets.UTF_8);
             bufferedOutputStream.write(data);
             bufferedOutputStream.flush();
             System.out.println("写出完毕");
             bufferedOutputStream.close();
         }
     }
     
    
  • 还有一个细节补充,我们可以按住Ctrl然后点击关闭流用的close(),观察close()方法的源代码:

     public void close() throws IOException {
             try (OutputStream ostream = out) {
                 flush();
             }
         }
    
    • 那我们还用flush()方法干什么,直接close()不就完事了?其实不然,如果我们在做一个聊天软件时,用微信举例,如果我们用close()flush()的话,刚发送一句话还没有回复就把流关掉了,这样肯定是不合理的,如果我们不使用flush()方法,那么我们发消息永远通不过去,一直到我写了一篇8KB的小作文,瞬间全部发出去,这明显也是不合理的。所以,我们在需要即时传递时,要多用flush()

      1. 将缓冲区中已经缓存的数据一次性写出;

      2. 但是除了缓冲输出流以外,其他高级流的flush()方法的作用都是为了调用它连接的流的flush()方法,目的是将flush()方法向下传递,最终传到缓冲输出流,做清空缓冲区操作。

      3. flush()方法是所有输出流都具备的方法,因为该方法时在接口Flushable中定义的,而输出流的父类OutputStream实现了该接口,所以所有输出流都有flush()方法;

       package day16;
       
       import java.io.BufferedOutputStream;
       import java.io.FileNotFoundException;
       import java.io.FileOutputStream;
       import java.io.IOException;
       import java.nio.charset.StandardCharsets;
       
       /**
        * 缓冲输出流写出数据的缓冲区问题
        */
       public class FlushDemo {
           public static void main(String[] args) throws IOException {
               FileOutputStream fileOutputStream = new FileOutputStream("bos.txt");
               BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
       
               /**
                * flush():
                * 1) 将缓冲区中已经缓存的数据一次性写出;
                * 2) flush()方法是所有输出流都具备的方法,因为该方法时在接口Flushable中定义的,而输出流的父类OutputStream实现了该接口,所以所有输出流都有flush()方法;
                * 3) 但是除了缓冲输出流以外,其他高级流的flush()方法的作用都是为了调用它连接的流的flush()方法,目的是将flush()方法向下传递,最终传到缓冲输出流,做清空缓冲区操作。
                */
       
               String string = "而我独缺 你一生的了解......";
               byte[] data = string.getBytes(StandardCharsets.UTF_8);
               bufferedOutputStream.write(data);
               bufferedOutputStream.flush();
               System.out.println("写出完毕");
               bufferedOutputStream.close();
           }
       }
       
      

对象流

对象序列化概念

  • 对象是存在于内存中的。有时候我们需要将对象保存到硬盘上,又有时我们需要将对象传输到另一台计算机上等等这样的操作;

  • 将对象转换为一个字节序列的过程称为对象序列化,相反则称反序列化

在这里插入图片描述

在这里插入图片描述

序列化与反序列化

  • 构造器:

    • ObjectOutputStream(OutputStream out):创建一个对象输出流并连接到指定的字节输出流上;

    • ObjectInputStream(InputStream in):创建一个对象输入流并连接到指定的字节输入流上;

  • 常用方法:

    • void writeObject(Object obj):将给定对象进行序列化后写出;

      • 序列化的对象必须实现:java.io.Serializable接口;

      • 否则会抛出异常:NotSerializableException

    • Object readObject():读取若干字节并进行反序列化对象将其返回;

  • 序列化操作演示:

     package day16;
     
     import java.util.Arrays;
     
     /**
      *         Person类
      * 测试对象流的序列化与反序列化操作
      */
     public class Person implements Serializable{  // 必须实现接口
         private String name;  // 姓名
         private int age;  // 年龄
         private String gender;  // 性别
         private String[] otherInfo;  // 其他信息
     
         // 无参构造
         public Person() {
         }
     
         // 有参构造
         public Person(String name, int age, String gender, String[] otherInfo) {
             this.name = name;
             this.age = age;
             this.gender = gender;
             this.otherInfo = otherInfo;
         }
     
         // getter和setter方法
         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;
         }
     
         public String getGender() {
             return gender;
         }
     
         public void setGender(String gender) {
             this.gender = gender;
         }
     
         public String[] getOtherInfo() {
             return otherInfo;
         }
     
         public void setOtherInfo(String[] otherInfo) {
             this.otherInfo = otherInfo;
         }
     
         // toString方法
         @Override
         public String toString() {
             return "Person{" +
                     "name='" + name + '\'' +
                     ", age=" + age +
                     ", gender='" + gender + '\'' +
                     ", otherInfo=" + Arrays.toString(otherInfo) +
                     '}';
         }
     }
     
    
     package day16;
     
     import java.io.FileOutputStream;
     import java.io.IOException;
     import java.io.ObjectOutputStream;
     
     /**
      * 对象是高级流,作用是进行对象的序列化与反序列化
      */
     public class OosDemo {
         public static void main(String[] args) throws IOException {
             String name = "张禹垚";
             int age = 21;
             String gender = "男";
             String[] otherInfo = {"Java", "C", "Python"};
             Person person = new Person(name, age, gender, otherInfo);
     
             FileOutputStream fileOutputStream = new FileOutputStream("person.obj"); // 低级流(文件流)
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);  // 高级流(对象流)
             /*
             对象输出流在进行序列化时,要求该对象必须实现可序列化接口Serializable
             否则调用writeObject()时会发生序列化异常NotSerializableException
              */
             objectOutputStream.writeObject(person);  // 写出对象
             System.out.println("写出对象成功!");
             objectOutputStream.close();
         }
     }
     
    
  • 反序列化操作演示:

     package day16;
     
     import java.io.*;
     
     /**
      * 使用对象输入流完成反序列化操作
      */
     public class OisDemo {
         public static void main(String[] args) throws IOException, ClassNotFoundException {
             FileInputStream fileInputStream = new FileInputStream("person.obj");  // 低级流(文件流)
             ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);  // 高级流(对象流)
             
             Person person = (Person) objectInputStream.readObject();  // 反序列化,因为我们写入时是Person类型,那么读取的也应该是Person类型,所以需要进行强转
             System.out.println(person);
             objectInputStream.close();
             
         }
     }
     
    
  • 我们发现,这个对象文件特别的大:

    在这里插入图片描述

    其实我们才写了那么几个数据,是因为这个对象里面的信息包含了每个成员变量的属性等,所以文件会比较大,如果这样的对象要应用在网络层面,那这样的性能将非常差,所以我们要进行瘦身。

transient关键字

  • 对象在序列化后得到的字节序列较大,对一个对象进行序列化时可忽略不必要的属性,从而对序列化后得到的字节序列“瘦身”;

  • 关键字transient修饰的属性在序列化时其值将被忽略;

    在这里插入图片描述

  • 我们观察序列化生成的obj大小:

    在这里插入图片描述

  • 这样我们就实现了对对象的瘦身,我们再反序列化查看结果:
    在这里插入图片描述

    发现otherInfo属性里面的数据没有被获取到,所以在实际开发时,我们对没有太大用途的属性可以给他一个transient关键字修饰,实现瘦身效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值