37、【JavaSE】【Java 核心类库(下)】I/O 流(2)

1、字符编码

2、数据流

  • 数据流主要负责的是基本数据类型的读、写。写使用的是java.io..DataOutputStream类,读使用的是java.io.DataInputStream类。
2.1、DataOutputStream 类
  • java.io.DataOutputStream类主要用于以适当的方式将基本数据类型写入输出流中。

  • 常用方法:

DataOutputStream 类常用方法
2.2、 DataInputStream 类
  • java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据。

  • 常用方法:

DataInputStream 类常用方法
2.3、使用案例
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataStreamTest {

    public static void main(String[] args) {
        DataOutputStream dos = null;
        try {
            dos = new DataOutputStream(new FileOutputStream("E:\\test\\k.txt"));
            dos.writeInt(66); // 向文件中写入一个整数,大小4个字节
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (dos != null) {
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
writeInt 方法写入整数66

Java 中整数66的为4个字节,写成二进制是“0000 0000 0000 0000 0000 0000 0100 0010”,先写高位,在文本文件中,将这个二进制根据 ASCII 码转换为对应的字符显示,所以在文本文件中显示出来是就是“ B”(B 之前有3个“空格”)。

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class DataStreamTest {

    public static void main(String[] args) {
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(new FileInputStream("E:\\test\\k.txt"));
            int num = dis.readInt();
            System.out.println(num); // 66
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  • 在使用这两个数据流的时候要注意,读、写的方法要对应,比如使用writeInt方法对应要使用readInt方法。

3、对象流

  • 对象流可以用来实现对象的读、写。

  • java.io.ObjectOutputStream类用于写,java.io.ObjectInputStream类用于读。

  • (对象的)序列化(Serialization),是将对象转换为二进制流(字节序列)的过程;反之,将二进制流恢复为对象的过程称为“反序列化(Deserialization)”。

  • 通过序列化,才能将对象进行数据持久化以及网络传输。

  • java.io.Serializable接口,被称为“序列化接口”,一旦类实现了该接口就表示该类“开启”了其序列化的功能。“序列化”是每一个类都具备的能力,只不过默认情况下处于一个“关闭”、“未激活”的状态,实现java.io.Serializable接口,相当于一个显式声明,表示“开启”、“激活”序列化。

  • 对于对象的读、写(输入、输出)操作而言,“写”对应序列化这一过程,“读”对应反序列化这一过程。

  • 注意,一个类中序列化的范围仅限于非静态且没有被transient关键字修饰的成员变量,而像静态成员变量、方法等均不会被序列化。(也很好理解,同一个类只让每一个对象独有的东西(一般就是指非静态成员变量)进行序列化)

  • 序列化的方案、形式有很多,这里所介绍的序列化,是 Java 提供的序列化方案,可以称为“Java 原生序列化”。像后来也会有“JSON 序列化”等其他方案。

3.1、ObjectOutputStream 类
  • java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。

  • 只能将支持(实现)java.io.Serializable接口的对象写入流中。

  • 常用方法:

ObjectOutputStream 类常用方法
3.2、ObjectInputStream 类
  • java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。

  • 常用方法:

ObjectInputStream 类常用方法
3.3、案例

将 User 类型的对象写入一文本文件,以及读出。

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = 475730776938249795L;

    private String account;
    private String password;
    private String phoneNumber;

    public User() {
    }

    public User(String account, String password, String phoneNumber) {
        this.account = account;
        this.password = password;
        this.phoneNumber = phoneNumber;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    @Override
    public String toString() {
        return "User{" +
                "account='" + account + '\'' +
                ", password='" + password + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                '}';
    }

}
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class ObjectOutputTest {

    public static void main(String[] args) {
        ObjectOutputStream oos = null;
        User user = new User("yscyber", "xyl_zw369", "182****0000");
        try {
            oos = new ObjectOutputStream(new FileOutputStream("E:\\test\\p.txt"));
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputTest {

    public static void main(String[] args) {
        ObjectInputStream ois = null;

        try {
            ois = new ObjectInputStream(new FileInputStream("E:\\test\\p.txt"));
            Object obj = ois.readObject();
            if (obj instanceof User) {
                User user = (User) obj;
                System.out.println(user); // User{account='yscyber', password='xyl_zw369', phoneNumber='182****0000'}
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace(); // 一个 catch 的参数可以有多个异常类型,用“|”分隔
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
3.4、补充
3.4.1、序列化版本号
  • 序列化版本号,就是上面User类代码中定义的变量serialVersionUID
private static final long serialVersionUID = 475730776938249795L
  • 一般要求是,如果一个类实现了java.io.Serializable接口,则必须显式地定义serialVersionUID

  • 在进行序列化的时候,serialVersionUID会被 JVM “加到”序列化所得到数据中;在反序列化的过程中,JVM 会从需要进行反序列化的数据中得到的serialVersionUID,与本地相应的类中定义的serialVersionUID相比较。如果一致,可以正常进行反序列化;如果不一致,反序列化失败,会报InvalidCastException异常。

  • 为什么要求必须显式定义serialVersionUID

如果不显式设置serialVersionUID,每次运行时,编译器会基于类的内部实现(包括类名、属性、方法等)来自动生成一个serialVersionUID
可能会认为“自动生成还不好吗,省得写”,但是要注意,如果出现了一些情况使得这个类发生了一些修改(比如增加一个成员变量等),再运行的时候,serialVersionUID已经与以前的不一样了,因为编译器所自动生成的serialVersionUID是基于类的内容的,类的内容都变了,serialVersionUID肯定会变。这样的话,之前已经序列化的数据就没有办法再成功反序列化。
所以,必须显式设置serialVersionUID!只有这样,序列化、反序列化成功与否才能掌握在自己手里。

  • 《阿里巴巴 码出高效》中关于序列化的阐述:
码出高效-Java 原生序列化
3.4.2、transient 关键字
  • transient是 Java 的关键字。该关键字只能修饰类的非静态成员变量(因为不论是否用transient修饰静态成员变量,静态的不参与序列化)。

  • 一旦被transient修饰,被修饰的成员变量将不参与序列化,反序列化时,对应的成员变量的取值将是默认值(null、0等默认值)。

3.4.3、其他
  • 当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,从而避免了通过返回值进行是否达到文件末尾的判断(因为java.io.ObjectInputStream是没法判断是否到达文件末尾的)。

4、RandomAccessFile 类

  • java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。

  • 这个类的最大特点是,可以根据需要“跳跃”读、写文件,也就是说,在读、写文件的时候,可以控制(或指定)读、写位置。

  • 机制类似于“游标”,也就是说,不管在文件中的什么位置,执行一次读或写的操作后,自动向后移动。

  • 常用方法:

RandomAccessFile 类常用方法
  • 案例:
/* 文本文件,文件中的内容是:Hello World! */

import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileTest {

    public static void main(String[] args) {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile("E:\\test\\a.txt", "r");
            int res = raf.read();
            System.out.println((char) res); // H
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
/* 文本文件,文件中的内容是:Hello World! */

import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileTest {

    public static void main(String[] args) {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile("E:\\test\\a.txt", "r");
            raf.seek(1); // 原读写位置向后偏移 1
            int res = raf.read();
            System.out.println((char) res); // e
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
/* 文本文件,文件中的内容是:Hello World! */

import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileTest {

    public static void main(String[] args) {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile("E:\\test\\a.txt", "rw");
            raf.seek(1);
            raf.write('x'); // 写完后文件中内容变成:Hxllo World!
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (raf != null) {
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。