首页 > 其他分享 >7 I/O和序列化

7 I/O和序列化

时间:2022-12-06 23:45:40浏览次数:40  
标签:Java 对象 串流 File new 序列化

Head First Java 和 AcWing Java课程做的总结7。

7.1 输入&输出

输入

方式1,效率较低,输入规模较小时使用。(10e6分界条件)

import java.util.Scanner;

public class Main{
    public static void main(String[] args) throws Exception{
        Scanner sc = new Scanner(System.in);
        
        int x = sc.nextInt();// 读入下一个整数
        float y = sc.nextFloat();// 读入下一个单精度浮点数
        double z = sc.nextDouble();// 读入下一个双精度浮点数
        //上述不读入换行符
        
        String line = sc.nextLine();// 读入下一行
        //如果前面有上述,则会读入换行
        //从第一行开始用则不会
        
        String str = sc.next();// 读入下一个字符串
        //不读入空格和换行符
        //去掉多空格,可以用next
        
        
        //多次读入
        while(sc.hasNext()){
            System.out.println("%s ", sc.next());
        }
    }
}

方式2,效率较高,输入规模较大时使用,注意需要抛异常。

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main{
    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str = br.readLine();
        System.out.println(str);
    }
}

输出

方式1,效率较低,输出规模较小时使用。

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println(123);  // 输出整数 + 换行
        System.out.println("Hello World");  // 输出字符串 + 换行
        System.out.print(123);  // 输出整数
        System.out.print("yxc\n");  // 输出字符串
        System.out.printf("%04d %.2f\n", 4, 123.456D);  // 格式化输出,float与double都用%f输出
    }
}

方式2,效率较高,输出规模较大时使用。注意需要抛异常。

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        bw.write("Hello World\n");
        bw.flush();  // 需要手动刷新缓冲区
    }
}

7.2 串流

串流介绍

数据是在串流中移动的。

  • Java的API中带有连接类型的串流,它代表来源与目的地之间的连接,连接串流将串流与其他串流连接起来。

  • Stream连接串流或是链接用的串流

    • 连接串流用来表示源或目的地、文件、网络套接字连接;
    • 链接用串流用来衔接连接串流。
  • 一般来说,串流需要两两连接才能有用——其中一个代表连接,另一个要被调用方法。

    • 连接串流很低层
    • 比如FileOutputStream有可以直接写入字节的方法,但我们通常不直接写字节。而是以对象层次的观点来写入,所以需要高层的连接串流。
  • 每个类只要做好一件事,所以不设计单一的串流。这样可以通过不同的组合来达到最大的适应性。

    • FileOutputStream把字节写入文件;
    • ObjectOutputStream把对象转换成可以写入串流的数据;
    • 调用ObjectOutputStreamwriteObject时,对象被打成串流送到FileOutputStream来写入文件。

写入文本文件

  • //第一版代码
    import java.io.*;
    class WriteAFile{
        public static void main(String[] args){
            try{
                FileWriter writer = new FileWriter("Foo.txt");
                
                writer.write("hello foo!");
                
                writer.close();
            }catch(IOException ex){
                ex.printStackTrace();
            }
        }
    }
    
  • java.io.File

    • File这个类代表磁盘上的文件,但并不是文件中的内容。File对象代表磁盘上的文件或目录的路径名称,但它并不能读取或代表文件中的数据。

    • 它提供了一种比使用字符串文件名来表示文件更安全的方式

    • /*用途*/
      
      //创建出代表现存盘文件的File对象
      File f = new File("MyCode.txt");
      
      //建立新的目录
      File dir = new File("Chapter7");
      dir.mkdir();
      
      //列出目录下的内容
      if(dir.isDirectory()){
          String[] dirContents = dir.list();
          for(int i = 0; i < dirContents.length; i++){
              System.out.println(dirContents[i]);
          }
      }
      
      //取得文件或目录的绝对路径
      System.out.println(dir.getAbsolutePath());
      
      //删除文件或目录
      boolean isDeleted = f.delete();
      
  • 缓冲区

    • 直接调用FileWriterwrite来写文件,每次它都会直接写下去。
    • 每趟磁盘操作比内存操作要花费更多的时间。
    • 通过BufferedWriterFileWriter的链接,BufferedWriter可以暂存一堆数据,到满的时候再实际写入磁盘,这样可以减少对磁盘操作的次数。更有效率。
    • 强制缓冲区立即写入,调用write.flush()
  • //第二版代码
    import java.io.*;
    class WriteAFile{
        public static void main(String[] args){
            
            try{
                File myFile = new File("foo.txt");
                FileWriter fileWriter = new FileWriter(myFile);
    
                BufferedWriter writer = new BufferedWriter(fileWriter);
                for(int i = 0; i < 3; i++){
                    writer.write("hello foo!");
                }
                writer.close();
            }catch(IOException ex){
                ex.printStackTrace();
            }
            
        }
    }
    

读取文本文件

  • //使用File对象,FileReader执行实际读取,并用BufferedReader使读取更有效率
    import java.io.*;
    
    class ReadAFile{
        public static void main(String[] args){
            
            try{
                File myFile = new File("MyText.txt");
                FileReader fileReader = new FileReader(myFile);
                
                BufferedReader reader = new BufferedReader(fileReader);
                //只有在缓冲区读空的时候才会回头去磁盘读取
                
                /*最常见的读取数据方式*/
                String line = null;
                while((line = reader.readLine()) != null){
                    System.out.println(line);
                }
                reader.close();
            }catch(Exception ex){
                ex.printStackTrace();
            }
        }
    }
    

7.3 对象序列化

序列化(Serialization)

如果需要存储对象状态:

  • 对每个对象逐个把每项变量的值写到特定格式的文件中;
    • (数据给非Java程序用必选)
  • 面向对象的方式——把对象本身“碾平”后“恢复”。
    • (只有Java程序会用到)
    • 产生的文件,很难阅读,更易恢复且比较安全。

将序列化对象写入文件的方法:

import java.io.*;
//创建出FileOutputStream
FileOutputStream fileStream = new FileOutputStream("MyGame.ser");

//创建出ObjectOutputStream
ObjectOutputStream os = new ObjectOutputStream(fileStream);

//写入对象
os.writeObject(characterOne);
os.writeObject(characterTwo);
os.writeObject(characterThree);

//关闭ObjectOutputStream
os.close();

对象序列化后发生的事:

  • 序列化的对象保存了实例变量的值;
    • primitive主数据类型时,就保留字节值;
    • 对象中有引用变量且引用到其他对象,则被引用的对象也被序列化......操作自动进行
  • 还要保存Java虚拟机所需的信息(比如类名称)。

实现序列化的条件:

要让类能够被序列化,就实现Serializable

  • Serializable接口又被称为marker或tag类的标记用接口,因为此接口并没有任何方法需要实现。唯一目的就是声明有实现它的类是可以被序列化的。(告诉虚拟机)
  • 某类可序列化,则其子类自动可以序列化。
  • os.writeObject(myBox);mybox必须实现序列化。
  • 序列化是全有或全无的。
  • 如果某实例变量不能或不应该被序列化
    • 把它标记为transient(瞬时)的,序列化程序员会把它跳过;
    • 不序列化的原因
      • 忘记实现Serializable
      • 动态数据只可以在执行时求出而不能或不必存储。

7.4 对象解序列化

解序列化(Deserialization)

可以在序列化后,不同的虚拟机执行期,甚至不是同一个Java虚拟机,把对象恢复到存储时的状态。

从文件读出序列化后的对象的方法:

import java.io.*;
//创建FileInputStream
FileInputStream fileStream = new FileInputStream("MyGame.ser");

//创建ObjectInputStream
ObjectInputStream os = new ObjectInputStream(fileStream);

//读取对象
Object one = os.readObject();
Object two = os.readObject();
Object three = os.readObject();

//转换对象类型
GameCharacter elf = (GameCharacter) one;
GameCharacter troll = (GameCharacter) two;
GameCharacter magician = (GameCharacter) three;

//关闭ObjectInputStream
os.close();//FileInputStream会跟着关闭

对象解序列化时发生的事:

  • Java虚拟机会尝试在堆上创建新的对象,让它维持与被序列化时有相同的状态来恢复对象的原状。
  • 对于transient的变量,不是null(对引用变量),就是默认值(主数据类型)
  • 具体步骤:
    • 对象从stream中读出来;
    • Java虚拟机从存储的信息判断出对象的class类型;
    • Java虚拟机尝试寻找和加载对象的类。如果Java虚拟机找不到或无法加载该类,则Java虚拟机抛出异常。
    • 新对象会被配置在堆上,但构造函数不会执行
    • 如果对象在继承树上有个不可序列化的祖先类,则该不可序列化类以及在它之上的类的构造函数(就算是可序列化也一样)就会执行。一旦构造函数连锁启动之后将无法停止,也就是说,从第一个不可序列化的父类开始,全部都会重新初始化状态。
    • 对象的实例变量会被还原成序列化时点的状态值,transient变量会被赋值null的对象引用或主数据类型默认的0、false等值。
  • 类不会存储为对象的一部分:
    • 序列化有将对象送到网络联机上的用途,如果每个序列化对象都有类,带宽的消耗是个大问题。
    • 对于网络传输序列化对象来说,有一种机制:让类使用URL来指定位置,用于RMI。
  • 静态变量:
    • 不会被序列化

能够被解序列化的条件:

  • 要有对象对应的类才能还原和使用该对象。
  • 版本控制:
    • 序列化后类被修改,该如何处理?
      • 会损害解序列化的修改
      • 通常不会有事的修改
    • serialVersionUID——类的版本识别ID
      • 当对象被序列化时,该对象不会盖上一个类的版本识别ID;
      • 根据类的结构信息计算出来的
      • Java尝试还原对象是,它会比对对象与Java虚拟机上的类的serialVersionUID
    • 可以把serialVersionUID放在class中,让类在演化过程中维持相同的ID。
      • serialver Dog可以用来取得版本ID
      • static final long serialVersionUID = -6849794470754667710L;

标签:Java,对象,串流,File,new,序列化
From: https://www.cnblogs.com/whxky/p/16961802.html

相关文章