第10章 I/O


学习目标

1. File类 (文件或文件夹--目录)

1.1 创建File对象

File类的构造方法

方法声明 功能描述
File(String pathname) 通过指定的一个字符串类型的文件路径来创建一个新的File对象
File(String parent,String child) 根据指定的一个字符串类型的父路径和一个字符串类型的子路径(包括文件名称)创建一个File对象
File(File parent,String child) 根据指定的File类的父路径和字符串类型的子路径(包括文件名称)创建一个File对象
package cn.zzh.ch10;
import java.io.File;
public class Ex01_zzh {
    public static void main(String[] args) {
        System.out.println("【例01】new File(...)创建文件(含目录)对象 (郑佐汉)");
        File f1 = new File("D:\\file\\");      //使用绝对路径创建File对象--目录
        File f2 = new File("D:\\file\\a.txt");    //使用绝对路径创建File对象--文件
        File f3 = new File("src\\Hello.java");    //使用相对路径创建File对象
        System.out.println(f1);
        System.out.println(f2);
        System.out.println(f3);
    }
}
// new File(...)  计划、准备创建这个文件,但未创建文件,此时File的对象已创建

注意 创建File对象时传入的路径使用了\,这是因为Windows中的目录符号为反斜线\,但反斜线\在Java中是特殊字符,具有转义作用,所以使用反斜线\时,前面应该再添加一个反斜线,即为\。此外,目录符号还可以用正斜线/表示,如“D:/file/a.txt”。

图1 创建文件(含目录)对象

1.2 File类的常用成员方法

File类的常用方法,能够使用File类的常用方法判断文件是否存在、获取文件的名称、文件的大小、文件的路径、删除文件。

方法声明 功能描述
boolean exists() 判断File对象对应的文件或目录是否存在,若存在则返回true,否则返回fazzhe
boolean delete() 删除File对象对应的文件或目录,若删除成功则返回true,否则返回fazzhe
boolean createNewFile() 当File对象对应的文件不存在时,该方法将新建一个文件,若创建成功则返回true,否则返回fazzhe
String getName() 返回File对象表示的文件或文件夹的名称
String getPath() 返回File对象对应的路径
String getAbsolutePath() 返回File对象对应的绝对路径(在Unix/Linux等系统上,如果路径是以正斜线/开始,则这个路径是绝对路径;在Windows等系统上,如果路径是从盘符开始,则这个路径是绝对路径)
String getParentFile() 返回File对象对应目录的父目录(即返回的目录不包含最后一级子目录)
boolean canRead() 判断File对象对应的文件或目录是否可读,若可读则返回true,反之返回fazzhe
boolean canWrite() 判断File对象对应的文件或目录是否可写,若可写则返回true,反之返回fazzhe
boolean isFile() 判断File对象对应的是否是文件(不是目录),若是文件则返回true,反之返回fazzhe
boolean isDirectory() 判断File对象对应的是否是目录(不是文件),若是目录则返回true,反之返回fazzhe
boolean isAbsolute() 判断File对象对应的文件或目录是否是绝对路径
long lastModified() 返回1970年1月1日0时0分0秒到文件最后修改时间的毫秒值
long length() 返回文件内容的长度(单位是字节)
String[] list() 递归列出指定目录的全部内容(包括子目录与文件),只是列出名称
File[] listFiles() 返回一个包含了File对象所有子文件和子目录的File数组
package cn.zzh.ch10;
import java.io.File;
public class Ex02_zzh {
    public static void main(String[] args) {
        System.out.println("【例02】File类的常用成员方法 (郑佐汉)");
        File file = new File("src//cn//zzh//test.txt");
        System.out.println("文件是否存在:"+file.exists());
        System.out.println("文件名:"+file.getName());
        System.out.println("文件大小:"+file.length()+"bytes");
        System.out.println("文件相对路径:"+file.getPath());
        System.out.println("文件绝对路径:"+file.getAbsolutePath());
        System.out.println("文件对象是否为文件:"+file.isFile());
        System.out.println("文件的父级对象是否为文件:"+file.getParentFile().isFile());
        System.out.println("文件删除是否成功:"+file.delete());
    }
}
图2 File类的常用方法

在src\cn\zzh\下创建test.txt并输入:白日依山尽,黄河入海流。

图3 在src\cn\zzh\下创建test.txt
图4 File类的常用方法

操作临时文件

package cn.zzh.ch10;
import java.io.File;

public class Ex03_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例03】临时文件 (郑佐汉)");
        // 提供临时文件的前缀和后缀
        File f = File.createTempFile("zzh-", ".txt");  //前缀改成自己的个性化前缀
        f.deleteOnExit(); // JVM退出时自动删除文件f
        System.out.println("f是否为文件:"+f.isFile());
        System.out.println("f的相对路径:"+f.getPath());
        Thread.sleep(12000); //12秒钟后退出程序,临时文件自动删除
    }
}
图5 临时文件

1.3 遍历目录下的文件

通过调用File类中的list()方法,可以遍历目录下的文件。按照调用方法的不同,目录下的文件遍历可分为以下3种方式。

(1) 遍历指定目录下的所有文件

package cn.zzh.ch10;
import java.io.File;
public class Ex04_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例04】获得指定目录下的所有文件的文件名--含文件夹名 (郑佐汉)");
        // 创建File对象
        File file = new File("D:\\郑佐汉的Java项目");
        if (file.isDirectory()) {           // 判断File对象对应的目录是否存在
            String[] names = file.list (); // 获得目录下的所有文件的文件名
            for (String name : names) {
                System.out.println(name);       // 输出文件名
            }
        }
    }
}

"F:\郑佐汉的Java项目" 须是你电脑上真实存在的文件夹。

图6 获得指定目录下的所有文件的文件名

(2) 遍历指定目录下指定扩展名的文件。

有时程序需要获取指定类型的文件,如获取指定目录下所有的“.java”文件。针对这种需求,File类提供了一个重载的list()方法,该方法接收一个FilenameFilter类型的参数。FilenameFilter是一个接口,被称作文件过滤器,其中定义了一个抽象方法accept()用于依次对指定File的所有子目录或文件进行迭代。在调用list()方法时,需要实现文件过滤器FilenameFilter,并在accept()方法中进行筛选,从而获得指定类型的文件。

package cn.zzh.ch10;
import java.io.File;
import java.io.FilenameFilter;
public class Ex05_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例05】文件及目录:过滤器对象 (郑佐汉)");
        // 创建File对象
        // File file = new File("D:\\郑佐汉的Java项目\\ch10_zzh\\src\\cn\zzh\\ch10");
        File file = new File("src\\cn\\zzh\\ch10");
        // 创建过滤器对象
        FilenameFilter filter = new FilenameFilter() {
            // 实现accept()方法
            public boolean accept(File dir, String name) {
                File currFile = new File(dir, name);
                // 如果文件名以.java结尾返回true,否则返回fazzhe
                if (currFile.isFile() && name.endsWith(".java")) {
                    return true;
                } ezzhe {
                    return fazzhe;
                }
            }
        };
        if (file.exists()) { // 判断File对象对应的目录是否存在
            String[] lists = file.list(filter); // 获得过滤后的所有文件名数组
            for (String name : lists) {
                System.out.println(name);
            }
        }
    }
}

F:\郑佐汉的Java项目\ch10_zzh\src\cn\zzh\ch10须是你电脑上真实存在的文件夹。

图7 遍历指定目录下指定扩展名的文件

(3) 遍历包括子目录中的文件在内的所有文件。

package cn.zzh.ch10;
import java.io.File;
public class Ex06_zzh {
    public static void main(String[] args) {
        System.out.println("【例06】递归文件夹及其子文件夹 (郑佐汉)");
        // 创建一个代表目录的File对象
        File file = new File("D:\\郑佐汉的Java项目\\ch10_zzh\src");
        fileDir(file);                     // 调用FileDir方法
    }
    public static void fileDir(File dir) {
        File[] files = dir.listFiles();   // 获得表示目录下所有文件的数组
        for (File file : files) {         // 遍历所有的子目录和文件
            if (file.isDirectory()) {
                fileDir(file);             // 如果是目录,递归调用fileDir()
            }
            System.out.println(file.getAbsolutePath()); // 输出文件的绝对路径
        }
    }
}
图8 递归文件夹及其子文件夹

1.4 删除文件及目录

(1) 调用delete()成员方法删除文件或文件夹

在操作文件时,可能会遇到需要删除一个目录下的某个文件或者删除整个目录的情况,这时可以调用File类的delete()成员方法

package cn.zzh.ch10;
import java.io.File;
public class Ex07_zzh {
    public static void main(String[] args) {
        System.out.println("【例07】删除文件 (郑佐汉)");
        File file = new File("D:\\郑佐汉的Java项目\\ch10_zzh\\Example10.java");
        if (file.exists()) {
            System.out.println(file.delete());
        }
    }
}

先建立文件:F:\郑佐汉的Java项目\ch10_zzh\Example10.java

图9 删除文件
图10 删除文件夹(包含子目录或文件)

假如File对象代表目录,并且目录下包含子目录或文件,则File类的delete()方法不允许直接删除这个目录。在这种情况下,需要通过递归的方式将整个目录以及目录中的文件全部删除。

(2) 递归删除包含子文件的目录

package cn.zzh.ch10;
import java.io.File;
public class Ex08_zzh {
    public static void main(String[] args) {
        System.out.println("【例08】递归删除包含子文件的目录 (郑佐汉)");
        File file = new File("source2");  //先手工建立用来测试删除的文件夹 source2及其子目录和文件
        deleteDir(file);                           // 调用deleteDir删除方法
        System.out.println("删除成功!");
    }
    public static void deleteDir(File dir) {
        if (dir.exists()) {                       // 判断传入的File对象是否存在
            File[] files = dir.listFiles();    // 得到File数组
            for (File file : files) {           // 遍历所有的子目录和文件
                if (file.isDirectory()) {
                    deleteDir(file);        // 如果是目录,递归调用deleteDir()
                } ezzhe {
                    // 如果是文件,直接删除
                    file.delete();
                }
            }
            // 删除完一个目录里的所有文件后,就删除这个目录
            dir.delete();
        }
    }
}
图11 递归删除包含子文件的目录

注意:删除目录是从JVM直接删除而不放入回收站,文件一旦删除就无法恢复,因此在进行文件删除操作的时候需要格外小心。

2. 字节流

2.1 字节流的概念

在程序的开发中,经常需要处理设备之间的数据传输,而计算机中,无论是文本、图片、音频还是视频,所有文件都是以二进制(字节)形式存在的。对于字节的输入输出,I/O流提供了一系列的流,统称为字节流,字节流是程序中最常用的流,根据数据的传输方向可将其分为字节输入流和字节输出流

图12 抽象类InputStream和OutputStream
图13 InputStream体系结构

InputStream类的常用成员方法

方法声明 功能描述
int read() 从输入流读取一个8位的字节,把它转换为0~255之间的整数,并返回这一整数
int read(byte[] b) 从输入流读取若干字节,把它们保存到参数b指定的字节数组中,返回的整数表示读取字节的数目
int read(byte[] b,int off,int len) 从输入流读取若干字节,把它们保存到参数b指定的字节数组中,off指定字节数组开始保存数据的起始索引,len表示读取的字节数目
void close() 关闭此输入流并释放与该流关联的所有系统资源
图14 OutputStream体系结构

OutputStream类的常用成员方法

方法声明 功能描述
void write(int b) 向输出流写入一个字节
void write(byte[] b) 把参数b指定的字节数组的所有字节写到输出流
void write(byte[] b,int off,int len) 将指定byte数组中从偏移量off开始的len个字节写入输出流
void flush() 刷新此输出流并强制写出所有缓冲的输出字节
void close() 关闭此输出流并释放与此流相关的所有系统资源

2.2 字节流读文件

FileInputStream是InputStream的子类,它是操作文件的字节输入流,专门用于读取文件中的数据。

package cn.zzh.ch10;
import java.io.FileInputStream;
public class Ex09_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例09】文件字节输入流 (郑佐汉)");
        // 创建一个文件字节输入流,并指定源文件名称
        FileInputStream in = new FileInputStream("readtest.txt");
        int b = 0;           // 定义一个int类型的变量b,记住每次读取的一个字节
        while (true) {
            b = in.read(); // 变量b记住读取的一个字节
            if (b == -1) { // 如果读取的字节为-1,跳出while循环
                break;
            }
            System.out.print((char)b); // 否则将b写出
        }
        in.close();
        in = new FileInputStream("readtest.txt");
        while (true) {
            b = in.read(); // 变量b记住读取的一个字节
            if (b == -1) { // 如果读取的字节为-1,跳出while循环
                break;
            }
            System.out.print(b+" "); // 否则将b写出
        }
        in.close();
    }
}

首先在Java项目的根目录下创建一个文本文件readtest.txt,在文件中输入内容“你好,郑佐汉! liushuai.”并保存;然后使用字节输入流对象来读取readtest.txt文本文件(保存成utf8格式)。

图15 文件字节输入流

2.3 字节流写文件

package cn.zzh.ch10;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class Ex10_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例10】FileOutputStream 写入内容 (郑佐汉)");
        // 创建一个文件字节输出流,并指定输出文件名称
        OutputStream out = new FileOutputStream("example.txt");
        String str = "传智教育,郑佐汉编写。";  // 郑佐汉 改写成自己的姓名
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);
        }
        out.close();
    }
}
图16 文件字节输出流:写入字节
package cn.zzh.ch10;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class Ex11_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例11】FileOutputStream 追加内容 (郑佐汉)");
          //创建文件输出流对象,并指定输出文件名称和开启文件内容追加功能
        OutputStream out = new FileOutputStream("example.txt ", true);
        String str = "欢迎你!";
         //将字符串存入byte类型的数组中
        byte[] b = str.getBytes();
        for (int i = 0; i < b.length; i++) {
            out.write(b[i]);
        }
        out.close();
    }
}
图17 文件字节输出流:追加字节

2.4 文件的复制

package cn.zzh.ch10;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Ex12_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例12】FileInputStream、FileOutputStream 文件字节流 复制文件内容 (郑佐汉)");
        // 创建一个字节输入流,用于读取当前目录下source文件夹中的a.png文件
        InputStream in = new FileInputStream("source/黄旭华.jpg");
        // 创建一个文件字节输出流,用于将读取的数据写入target目录下的文件中
        OutputStream out = new FileOutputStream("target/黄老.jpg");
        int len; // 定义一个int类型的变量len,记住每次读取的一个字节
         // 获取复制文件前的系统时间
        long begintime = System.currentTimeMillis();
        while ((len = in.read()) != -1) { // 读取一个字节并判断是否读到文件末尾
            out.write(len); // 将读到的字节写入文件
        }
        // 获取文件复制结束时的系统时间
        long endtime = System.currentTimeMillis();
        System.out.println("复制文件所消耗的时间是:" + (endtime - begintime) + "毫秒");
        in.close();
        out.close();
    }
}
图18 二进制源文件
图19 文件字节流 复制文件
图20 二进制目标文件
package cn.zzh.ch10;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Ex13_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例13】FileInputStream、FileOutputStream 文件字节流复制文件--利用缓冲区 (郑佐汉)");
        // 创建一个字节输入流,用于读取当前目录下source文件夹中的文件a.png
        InputStream in = new FileInputStream("source/邓稼先.jpg");
        // 创建一个文件字节输出流,用于将读取的数据写入当前目录的target文件中
        OutputStream out = new FileOutputStream("target/邓老.jpg");
        // 以下是用缓冲区读写文件
        byte[] buff = new byte[1024];    // 定义一个字节数组,作为缓冲区
        // 定义一个int类型的变量len记住读取读入缓冲区的字节数
        int len;
        long begintime = System.currentTimeMillis();
        while ((len = in.read(buff)) != -1) { // 判断是否读到文件末尾
            out.write(buff, 0, len);  // 从第一个字节开始,向文件写入len个字节
        }
        long endtime = System.currentTimeMillis();
        System.out.println("复制文件所消耗的时间是:" + (endtime - begintime) +
        "毫秒");
        in.close();
        out.close();
    }
}
图21 二进制源文件
图22 文件字节流 复制文件2
图23 二进制目标文件

3. 字符流

3.1 字符流定义及基本用法

前面讲解内容都是通过字节流直接对文件进行读写,如果读写的文件内容是字符,考虑到使用字节流读写字符可能存在传输效率以及数据编码问题,此时建议使用字符流。

同字节流一样,字符流也有两个抽象的顶级父类,分别是Reader类和Writer类。其中Reader类是字符输入流,用于从某个源设备读取字符。Writer类是字符输出流,用于向某个目标设备写入字符。

图24 Reader体系结构

Reader类的常用成员方法

方法声明 功能描述
int read() 以字符为单位读数据
int read(char cbuf[]) 将数据读入char类型数组,并返回数据长度
int read(char cbuf[],int off,int len) 将数据读入char类型数组的指定区间,并返回数据长度
void close() 关闭数据流
long transferTo(Writer out) 将数据直接读入字符输出流
图25 Writer体系结构

Writer类的常用成员方法

方法声明 功能描述
void write(int c) 以字符为单位写数据
void write(char cbuf[]) 将char类型数组中的数据写出
void write(char cbuf[],int off,int len) 将char类型数组中指定区间的数据写出
void write(String str) 将String类型的数据写出
void wirte(String str,int off,int len) 将String类型指定区间的数据写出
void flush() 可以强制将缓冲区的数据同步到输出流中
void close() 关闭数据流

3.2 字符流读文件

source/袁隆平.txt utf8编码

杂交水稻之父袁隆平(1930年9月7日-2021年5月22日)

袁隆平1930年9月出生于北京,祖籍江西九江德安县,被誉为“世界杂交水稻之父”。

袁隆平发明出了“三系法”籼型杂交水稻、“两系法”杂交水稻,创建了著名的超级杂交稻技术体系,不仅使中国人民填饱了肚子,也将粮食安全牢牢抓在我们中国人自己手中。

2004年,袁隆平荣获“世界粮食奖”,2019年又荣获“共和国勋章”。
package cn.zzh.ch10;
import java.io.FileReader;
public class Ex14_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例14】字符流读文件(郑佐汉)");
        // 创建一个FileReader对象用来读取文件中的字符
        FileReader reader = new FileReader("source/袁隆平.txt");
        int ch;                             // 定义一个变量用于记录读取的字符
        while ((ch = reader.read()) != -1) {     // 循环判断是否读取到文件的末尾
            System.out.print ((char) ch);         // 不是字符流末尾就转为字符打印
        }
        reader.close(); // 关闭文件读取流,释放资源
    }
}
图26 字符流读文件

3.3 字符流写文件

package cn.zzh.ch10;
import java.io.FileWriter;
public class Ex15_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例15】字符流写文件(郑佐汉)");
        // 创建一个FileWriter对象用于向文件中写入数据
        FileWriter writer = new FileWriter("target/于敏.txt");
        String str = "中国氢弹之父于敏。\r\n" +
                "    于敏1926年出生于河北宁河县(今天津宁河区),被誉为中国“氢弹之父”,不仅填补了中国在原子核理论领域内的空白,而且在氢弹研制工作中发挥了关键作用。\r\n" +
                "    于敏负责研发的氢弹项目,创造性地使用了“于敏结构”,为氢弹后续的小型化、实战化,奠定了坚实的基础,使得我国在氢弹领域迈进了世界先进行列,而著名的“于敏结构”,不论是先行国家还是后来者,都无法在技术领域对其形成压制或者突破,可谓独步天下。\r\n" +
                "    2019年,于敏被授予新中国最高荣誉勋章——“共和国勋章”。";
        writer.write(str);  // 将字符数据写入到文本文件中
        writer.write("\r\n");  // 将输出语句换行
        writer.close(); // 关闭写入流,释放资源
    }
}
图27 字符流写文件
图28 字符流写文件:目标文件

4. 转换流

package cn.zzh.ch10;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class Ex16_zzh {
    public static void main(String[] args) throws Exception {
        System.out.println("【例16】转换流 (郑佐汉)");
        // 创建一个字节输入流in,并指定源文件为src.txt
        FileInputStream in = new FileInputStream("target/于敏.txt");
        // 将字节输入流in转换成字符输入流isr
        InputStreamReader isr = new InputStreamReader(in,"UTF-8");
        // 创建一个字节输出流对象out,并指定目标文件为des.txt
        FileOutputStream out = new FileOutputStream("target/于敏2.txt");
        // 将字节输出流out转换成字符输出流osw
        OutputStreamWriter osw = new OutputStreamWriter(out,"GBK");
        int ch;                     // 定义一个变量用于记录读取的字符
        while ((ch = isr.read()) != -1) {     // 循环判断是否读取到文件的末尾
            osw.write(ch);  // 将字符数据写入des.txt文件中
        }
        isr.close(); // 关闭字符输入流,释放资源
        osw.close(); // 关闭字符输出流,释放资源
    }
}
图29 转换流:字符编码转换
图30 转换流--字符编码转换:目标文件

5. 序列化和反序列化

序列化和反序列化

程序在运行过程中,数据都保存在Java中的对象中(内存),但很多情况下我们都需要将一些数据永久保存到磁盘上。为此,Java提供了对象序列化,对象序列化可以将对象中的数据保存到磁盘。

图31 序列化和反序列化

实现序列化方法

对象实现支持序列化机制,这个对象所属的类必须是可序列化的。在Java中可序列化的类必须实现Serializable或Externalizable两个接口之一

Serializable接口和Externalizable接口实现序列化机制的主要区别

Serializable接口 Externalizable接口
系统自动存储必要的信息 由程序员决定所存储的信息
Java内部支持,易于实现,只需实现该接口即可,不需要其他代码支持 接口中只提供了两个抽象方法,实现该接口必须要实现这两个抽象方法
性能较差 性能较好

使用Serializable接口实现对象序列化

在实际开发时,大部分都采用实现Serializable接口的方式来实现对象序列化。使用Serializable接口实现对象序列化非常简单,只需要让目标类实现Serializable接口即可,无需实现任何方法。

public class Xxxxx implements Serializable{
    //为该类指定一个serialVersionUID变量值
    private static final long serialVersionUID = 1L;
    //声明变量
    private int xxx;
    private String yyy;
    ...
    // 此处省略各属性的getter和setter方法
    ...
}

serialVersionUID适用于Java的对象序列化机制。简单来说,Java的对象序列化机制是通过判断类的serialVersionUID来验证版本一致性。在进行反序列化时,JVM会把字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会抛出序列化版本不一致的异常。因此,为了在反序列化时确保序列化版本的兼容性,最好在每一个要序列化的类中加入private static final long serialVersionUID的变量值,具体数值可自定义,默认是1L。

17package cn.zzh.ch10;
import java.io.*;
class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private static String val = "1234";
    private Integer id;
    private String name;
    public transient int SSN;  //transient,序列化对象的时候,这个属性就不会被序列化。
    public int number;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", SSN=" + SSN +
                ", number=" + number +
                '}';
    }
}

public class Ex17_zzh {
    public static void main(String[] args) {
        System.out.println("【例17】 使用Serializable接口实现对象序列化(郑佐汉)");                 Ex17_zzh serializeDemo = new Ex17_zzh();
        serializeDemo.serialize();
        serializeDemo.deSerialize();
    }
    public void serialize() {
        User e = new User();
        e.setId(100001);
        e.setName("郑佐汉");
        e.SSN = 11122333;
        e.number = 101;
        try {
            FileOutputStream fileOut = new FileOutputStream("user.ser");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(e);
            out.close();
            fileOut.close();
            System.out.println("序列化后的文件保存在:user.ser");
        } catch (Exception i) {
            i.printStackTrace();
        }
    }
    public void deSerialize() {
        FileInputStream fileIn = null;
        try {
            fileIn = new FileInputStream("user.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            User user = (User) in.readObject();
            System.out.println(user.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
图32 使用Serializable接口实现对象序列化
图33 user.ser文件内容 -- 乱码

使用Externalizable接口实现对象序列化

与实现Serializable接口相比,虽然实现Externalizable接口可以带来一定性能上的提升,但由于实现Externalizable接口,需要实现两个抽象方法,所以实现Externalizable接口也将导致编程的复杂度增加。

package cn.zzh.ch10;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;

import static java.lang.Thread.sleep;

class User18 implements Externalizable {
    private static final long serialVersionUID = 1L;
    String 用户名;
    String 密码;
    int 年龄;

    /**
     * 需要有一个默认构造器,否则会报no valid constructor异常
     */
    public User18() {
    }

    public User18(String 用户名, String 密码, int 年龄) {
        this.用户名 = 用户名;
        this.密码 = 密码;
        this.年龄 = 年龄;
    }

    @Override
    public String toString() {
        return "User18{" +
                "用户名='" + 用户名 + '\'' +
                ", 密码='" + 密码 + '\'' +
                ", 年龄=" + 年龄 +
                '}';
    }

    /**
     * 在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,
     * 所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理
     *
     * @param out
     * @throws IOException
     */
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // 增加一个新的对象
        Date date = new Date();
        out.writeObject(用户名);
        out.writeObject(密码);
        out.writeObject(年龄);
        out.writeObject(date);
    }

    /**
     * 在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回,然后在测试类接收,就完成了反序列
     *
     * @param in
     * @throws IOException
     * @throws ClassNotFoundException
     */
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        // 注意这里的接受顺序是有限制的,否则的话会出错的
        // 例如上面先write的是A对象的话,那么下面先接受的也一定是A对象...
        用户名 = (String) in.readObject();
        密码 = (String) in.readObject();
        年龄 = (int) in.readObject();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        Date date = (Date) in.readObject();
        System.out.println("反序列化后的日期为:" + sdf.format(date));
    }
}

class Operate {
    /**
     * 序列化方法
     *
     * @throws IOException
     * @throws FileNotFoundException
     */
    public void serializable(User18 person) throws FileNotFoundException, IOException {
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("user18.ser"));
        outputStream.writeObject(person);
    }

    /**
     * 反序列化的方法
     *
     * @throws IOException
     * @throws FileNotFoundException
     * @throws ClassNotFoundException
     */
    public User18 deSerializable() throws FileNotFoundException, IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user18.ser"));
        return (User18) ois.readObject();
    }
}

public class Ex18_zzh {
        public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException, InterruptedException {
            System.out.println("[【例18】 使用Externalizable接口实现对象序列化(郑佐汉)" );
            Operate operate = new Operate();
            User18 person = new User18("郑佐汉", "123456", 20);
            System.out.println("为序列化之前的相关数据如下:\n" + person.toString());
            operate.serializable(person);
            Thread.sleep(9000);
            User18 newPerson = operate.deSerializable();
            System.out.println("-------------------------------------------------------");
            System.out.println("序列化之后的相关数据如下:\n" + newPerson.toString());
        }
}
图34 使用Externalizable接口实现对象序列化
图35 user18.ser文件内容 -- 乱码
图36 本章系统开发文件名截图

返回