文章目录

  • ※ I/O流
  • ※ java.io
  • 一、 File
  • 1 常用字段
  • 2 常用构造方法
  • 2.1 创建文件夹
  • 2.2 创建文件
  • 3 常用方法
  • 3.1 获取
  • 3.1.1 常用的获取方法
  • 3.1.2 获取文件
  • 3.2 判断
  • 3.3 重命名
  • 3.4 删除
  • 3.5 遍历文件
  • 4 文件路径
  • 5 文件过滤器
  • 写法一
  • 写法二
  • 二、 字节输出流:OutputStream
  • FileOutputStream
  • 构造方法
  • 常用方法
  • 三、 字节输入流:InputStream
  • FileInputStream
  • 构造方法
  • 常用方法
  • 四、 字符输出流:Writer
  • FileWriter
  • 构造方法
  • 五、字符输入流:Reader
  • FileWriter
  • 构造方法
  • 六、 转换流(字节流→字符流)
  • 常规转换流
  • 输出流
  • 输入流
  • Print
  • 字符打印流(输出流)
  • 使用PrintStream
  • 使用PrintWriter
  • 字符输入流
  • BufferedReader
  • 七、 序列化技术Serializable
  • 序列化
  • 反序列化
  • 部分属性序列化
  • 使用transient修饰符
  • 使用static修饰符
  • 默认方法writeObject和readObject
  • Externalizable实现序列化
  • ※收集异常日志
  • ※ Properties
  • 构造方法
  • 常用方法
  • store
  • load
  • ※ try-with-resources


※ I/O流

  • 可以将数据的传输操作,看作一种数据流动,按流动的方向分为输入流输出流,按处理数据类型的单位可以分为字节流字符流

输入流: 程序从数据源中读取数据
输出流: 将数据从程序中写到指定的文件中

字节流: 每次读写一个字节

输入流:InputStream
输出流:OutputStream

字符流: 以字符为单位进行数据处理,基于字节流读取,去查找指定的码表

输入流:Reader
输出流:Writer

区别

字节流

字符流

读写单位

以字节(8位2进制)为单位

以字符为单位

处理对象

所有类型的数据(如图片、avi等)

只能处理字符类型的数据

一次读入或读出数据

8位二进制

16位二进制

Java中I/O操作主要是指Java.io包下一些常用类的使用,通过这些常用类对数据进行输入、输出操作
tips: 一切皆字节,计算机中任何数据流都以二进制形式存储,传输也是,后续传输的任何流,传输时底层都为二进制

※ java.io

Java.io包中最重要的5个类:FileOutputStreamInputStreamWriterReader
Java.io包中最重要的1个接口:Serializable

一、 File

  • 文件和目录路径名的抽象表示
  • 表示处理文件和文件系统的相关信息
    不具有从文件读取信息和向文件写入信息的功能,它仅描述文件本身的属性

1 常用字段

变量和类型

字段

描述

static char

pathSeparatorChar

与系统相关的路径分隔符" ; "

static char

separatorChar

系统相关的默认名称分隔符" \ "

(基本不怎么用)

变量和类型

字段

描述

static String

pathSeparator

与系统相关的路径分隔符

static String

separator

系统相关的默认名称分隔符

2 常用构造方法

构造器

描述

File(String pathname)

通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例

File(File parent, String child)

父抽象路径名子路径名字符串创建新的 File实例(文件夹,文件名称)

File(String parent, String child)

父路径名字符串子路径名字符串创建新的 File实例(文件夹,文件名称)

2.1 创建文件夹
//File(String pathname)

        File dir = new File("d:" + File.separator + "ha");
        dir.mkdir();//创建文件目录
        File dirs = new File("d:" + File.separator + "haha" + File.separator + "hahaha");
        dirs.mkdirs();//创建多层文件目录

		/*
		boolean flag = dir.mkdir();//tips:dir.mkdir()类型为Boolean型,如果创建成功值为true,失败为false
		system.out.println(flag);//文件创建成功,flag = true
		*/
2.2 创建文件
//File(File parent, String child)
//File(String parent, String child)

		//在d盘的"ha"文件夹中创建文件"a.txt"
        File a = new File(dir,"a.txt");
        a.createNewFile();
        
        //在d盘的"ha"文件夹中创建文件"b.java"
        File b = new File("d:" + File.separator + "ha","b.java");
        b.createNewFile();
        
        //在d盘的"haha"文件夹中的"hahaha"文件夹中创建文件"c.png"
        File c = new File(dirs,"c.png");
        c.createNewFile();
        
        //在d盘的"haha"文件夹中的"hahaha"文件夹中创建文件"d.png"
        File d = new File("d:" + File.separator + "haha" + File.separator + "hahaha","d.png");
        d.createNewFile();

3 常用方法

3.1 获取

变量和类型

方法

描述

String

getName()

返回此抽象路径名表示的文件或目录的名称

String

getAbsolutePath()

返回此抽象路径名的绝对路径名字符串

String

getParent()

返回此抽象路径名父项的路径名字符串,如果此路径名未指定父目录,则返回 null

File

getParentFile()

返回此抽象路径名父项的抽象路径名,如果此路径名未指定父目录,则返回 null

String

getPath()

将此抽象路径名转换为路径名字符串

long

length()

返回此抽象路径名表示的文件的长度

String[]

list()

返回一个字符串数组,用于命名此抽象路径名表示的目录中的文件和目录

File[]

listFiles()

返回一个抽象路径名数组,表示此抽象路径名表示的目录中的文件

3.1.1 常用的获取方法

代码示例:

/**
         * 常用的获取方法
         */
        System.out.println(c.getName());//获取文件c的名称
        System.out.println(c.getPath());//获取c的绝对路径(地址)
        System.out.println(c.getAbsolutePath());//获取c的绝对路径(地址)
        System.out.println(c.getParent());//获取文件c的父路径
        System.out.println(c.getParentFile());//获取文件c的父路径
        System.out.println(c.length());//获取文件c的大小,以字节为单位

输出结果:

c.png
d:\haha\hahaha\c.png
d:\haha\hahaha\c.png
d:\haha\hahaha
d:\haha\hahaha
0
3.1.2 获取文件

获取文件夹中的所有文件:见3.3重命名后 对文件夹中的全部文件进行输出
遍历文件:见3.5

3.2 判断

变量和类型

方法

描述

boolean

isDirectory()

测试此抽象路径名表示的文件是否为目录

boolean

isFile()

测试此抽象路径名表示的文件是否为普通文件

boolean

exists()

测试此抽象路径名表示的文件或目录是否存在

代码示例:

/**
         * 常用的判断方法
         */
        System.out.println(c.exists());//判断文件c是否存在,true
        c.delete();
        System.out.println(c.exists());//删除文件c后判断文件c是否存在,false
        System.out.println(a.isFile());//判断文件a是否为文件,true
        System.out.println(a.isDirectory());//判断文件a是否为文件夹,false
        System.out.println(dir.isFile());//判断文件夹dir是否为文件,false
        System.out.println(dir.isDirectory());//判断文件夹dir是否为文件夹,true

输出结果:

true
false
true
false
false
true
3.3 重命名

变量和类型

方法

描述

boolean

renameTo(File dest)

重命名此抽象路径名表示的文件

String[]

list()

返回一个字符串数组,用于命名此抽象路径名表示的目录中的文件和目录

代码示例:

/**
         *由上述操作可知已d盘的"haha"文件夹中的"hahaha"文件夹中创建文件"d.png"
         * 移动并重命名文件,删除d(d.png),将新的文件e添加到新路径(也可以改变文件的属性 png→txt)
         */
        File e = new File(dir,"eee.txt");
        d.renameTo(e);

        /**
         * 获取此文件夹的所有文件(包括文件夹),保存在数组中
         */
        String[] files1 = dir.list();
        for (String s:files1) {
            System.out.println(s);
        }

输出结果:

a.txt
b.java
eee.txt
3.4 删除

变量和类型

方法

描述

boolean

delete()

删除此抽象路径名表示的文件或目录

代码示例:

/**
         * 删除文件
         * 文件存在 → 删除成功 → true
         * 文件不存在 → 删除失败 → false
         */
//        System.out.println(a.delete());
//        System.out.println(b.delete());
//        System.out.println(c.delete());//之前已经被删除,因此不存在
//        System.out.println(d.delete());
//        System.out.println(e.delete());

输出结果:

true
ture
flase
true
true
3.5 遍历文件

变量和类型

方法

描述

File[]

listFiles()

返回一个抽象路径名数组,表示此抽象路径名表示的目录中的文件

代码示例:

/**
         * 遍历文件
         */
        File dd = new File("d:\\");//创建文件夹
        File[] files = dd.listFiles();//获取此文件夹(d盘)中的全部文件,保存到Files数组中
        listFiles(files);
/**
     * 遍历文件
     * @param files
     */
    public static void listFiles(File[] files){
        if(files != null && files.length > 0){
            for(File file:files){
                if(file.isFile()){
                    //文件
                    if(file.getName().endsWith(".pdf")){
                        //找到了一个pdf文件
//                        System.out.println("pdf文件:" + file.getPath() + "  " +file.length()/1024 + "kB");
                        if(file.length() > 1000*1024){//>1000kb的文件
                            System.out.println("pdf文件:" + file.getPath() + "  " +file.length()/1024 + "kB");
                        }
                    }
                }else{
                    //文件夹
                    File[] file2 = file.listFiles();//得到该文件夹中的文件
                    listFiles(file2);//递归调用自己
                }
            }
        }
    }//end method

输出结果:

pdf文件:d:\hahatask\Java基础.pdf  3656kB
pdf文件:d:\hahatask\快递e栈.pdf  7275kB

4 文件路径

路径分为绝对路径和相对路径
绝对路径:从盘符开始,是一个完整的路径,例如:d:\ha\绝对路径.txt
相对路径:在Java代码中是相对于项目的目录路径,是一个不完整的便捷路径,在Java开发中经常使用,例如:相对路径.txt
代码示例:

File f1 = new File(dir,"绝对路径.txt");
        File f2 = new File("相对路径.txt");
        System.out.println("f1的路径(绝对路径):" + f1.getAbsolutePath());
        System.out.println("f2的路径(相对路径):" + f2.getAbsolutePath());

输出结果:

f1的路径(绝对路径):d:\ha\绝对路径.txt
f2的路径(相对路径):D:\code_task\helloworld\相对路径.txt

5 文件过滤器

通过implements FileFilter ,自定义过滤规则、对文件进行遍历,输出符合条件的文件

变量和类型

方法

描述

String[]

list(FilenameFilter filter)

返回一个字符串数组,用于命名由此抽象路径名表示的目录中的文件和目录,以满足指定的过滤器

public static void main(String[] args) {
        File d = new File("d:" + File.separator);
        listFiles(d);//对d盘内容进行过滤
    }

过滤通常有两种写法

写法一
/**
     * 文件过滤器
     */
    public static void listFiles(File file){
        //1.创建一个过滤器规则,并描述规则
        FileFilter filter = new PDFFileFilter();

        //2.通过文件获取子文件夹
        File[] files = file.listFiles(filter);
        if(files != null && files.length >0)
        {
            for (File f:files){
                if(f.isDirectory()){
                    //是文件夹
                    listFiles(f);//递归
                }else{
                    //是pdf
                    System.out.println("pdf文件:" + f.getPath() + "  " +f.length()/1024 + "kB");
                }
            }
        }
    }

    /**
     * 过滤的规则
     */
    static class PDFFileFilter implements FileFilter {
        @Override//过滤的方法
        public boolean accept(File pathname) {//pathname是过滤的文件
            if(pathname.getName().endsWith(".pdf") || pathname.isDirectory()){
                return true;//这个文件被保留
            }
            return false;
        }
    }
写法二
/**
     * 文件过滤器
     */
    public static void listFiles(File file){
        /*
        //1.创建一个过滤器规则,并描述规则
        FileFilter filter = new FileFilter(){//通过匿名内部类来创建文件过滤器规则
            @Override
            public boolean accept(File pathname) {
                if(pathname.getName().endsWith(".pdf") || pathname.isDirectory()){
                    return true;//这个文件被保留
                }
                return false;
            }
        };
        //2.通过文件获取子文件夹
        File[] files = file.listFiles(filter);
        */
        
        //注释掉的部分可以这样写:直接将1右半部分作为2的参数传进去
        File[] files = file.listFiles(new FileFilter(){//通过匿名内部类来创建文件过滤器规则
            @Override
            public boolean accept(File pathname) {
                if(pathname.getName().endsWith(".pdf") || pathname.isDirectory()){
                    return true;//这个文件被保留
                }
                return false;
            }
        });
        if(files != null && files.length > 0)
        {
            for (File f:files){
                if(f.isDirectory()){
                    //是文件夹
                    listFiles(f);//递归
                }else{
                    //是pdf
                    System.out.println("pdf文件:" + f.getPath() + "  " +f.length()/1024 + "kB");
                }
            }
        }
    }

二、 字节输出流:OutputStream

  • 抽象类,基于字节的输出操作,是所有输出流的父类
  • 定义了所有输出流都具有的共同特征

变量和类型

方法

描述

void

close()

关闭此输出流并释放与此流关联的所有系统资源

void

flush()

刷新此输出流并强制写出任何缓冲的输出字节

void

write(byte[] b)

将 b.length字节从指定的字节数组写入此输出流

void

write(byte[] b, int off, int len)

将从偏移量 off开始的指定字节数组中的 len字节写入此输出流

abstract

void write(int b)

将指定的字节写入此输出流

FileOutputStream

是字节输出流的具体类

java.lang.Object 
java.io.OutputStream 
java.io.FileOutputStream
构造方法

构造器

描述

FileOutputStream(File file)

写入由指定的 File对象表示的文件

FileOutputStream(File file, boolean append)

写入由指定的 File对象表示的文件

FileOutputStream(String name)

写入具有指定名称的文件

FileOutputStream(String name, boolean append)

写入具有指定名称的文件

  • 创建一个对象
//FileOutputStream,OutputStream的子类
        FileOutputStream fos = new FileOutputStream("d:" + File.separator + "test.txt");
常用方法

变量和类型

方法

描述

void

close()

关闭此文件输出流并释放与此流关联的所有系统资源

void

write(byte[] b)

将指定字节数组中的 b.length字节写入此文件输出流

void

write(byte[] b, int off, int len)

将从偏移量 off开始的指定字节数组中的 len字节写入此文件输出流

void

write(int b)

将指定的字节写入此文件输出流

代码示例:

  • 写一个字节
//写一个字节

        fos.write(48);//字节48,表示字符'0',此时d盘中的a.txt中有内容'0'
  • 写一组字节
//写一组字节

        byte[] bytes1 = {49,50,51,52,53,54,55,56,57};//'1','2','3','4','5','6','7','8','9'
        fos.write(bytes1);
        byte[] byte2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes();//输入字符串,再转换成bytes类型,但是这是字符,因此和后续使用字符流
        fos.write(byte2,0,26);//从下标1开始,写入两个长度
        fos.close();//写完之后再关闭,尽可能早地关闭

执行结果:
d盘中的“test.txt”文件中写入内容:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ

三、 字节输入流:InputStream

抽象类,基于字节的输入操作,是所有输入字节流的父类

变量和类型

方法

描述

void

close()

关闭此输入流并释放与该流关联的所有系统资源

abstract int

read()

从输入流中读取下一个数据字节

int

read(byte[] b)

从输入流中读取一些字节数并将它们存储到缓冲区数组 b

int

read(byte[] b, int off, int len)

从输入流 len最多 len字节的数据读入一个字节数组

FileInputStream

是字节输入流的具体类

java.lang.Object 
java.io.InputStream 
java.io.FileInputStream
构造方法

构造器

描述

FileInputStream(File file)

该文件由文件系统中的 File对象file命名

FileInputStream(String name)

该文件由文件系统中的路径名name命名

  • 创建一个对象
//FileInputStream,InputStream的子类
        FileInputStream fis = new FileInputStream("d:" + File.separator + "test.txt");
常用方法

变量和类型

方法

描述

void

close()

关闭此文件输入流并释放与该流关联的所有系统资源

int

read()

从此输入流中读取一个字节的数据

int

read(byte[] b)

从此输入流 b.length最多 b.length字节的数据读 b.length字节数组

int

read(byte[] b, int off, int len)

从此输入流 len最多 len字节的数据读入一个字节数组

代码示例:

  • 读一个字节
//读一个字节
        while(true){//读到超过总长度,一直返回-1
            byte b = (byte)fis.read();
            if(b == -1){
                break;//避免读到超过总长度了还一直读下去,因此加一个判断
            }
            System.out.println(b);
        }
  • 读一组字节,推荐使用
//读一组字节,推荐使用
        byte[] bytes = new byte[10];//设定每次都读取10个字节
        while(true){
            int len = fis.read(bytes);
            if(len == -1){
                break;
            }
            //输出的时候,以len为长度输出而不是10,原因在于:
            //由输出可知,最后为UVWXYZ,len只有6,如果此处不写len而默认为10的话,则输出为UVWXYZQRST(最后四位为上一次在数组中的内容)
            System.out.println(new String(bytes,0,len));
        }
        fis.close();

输出结果:

0123456789
ABCDEFGHIJ
KLMNOPQRST
UVWXYZ

四、 字符输出流:Writer

抽象类,基于字符的输出操作
tips:只能操作文字,而字节流能操作全部

变量和类型

方法

描述

Writer

append(char c)

将指定的字符追加到此writer

abstract void

close()

关闭流,先冲洗它

abstract void

flush()

刷新流

void

write(char[] cbuf)

写一个字符数组。

abstract void

write(char[] cbuf, int off, int len)

写一个字符数组的一部分。

void

write(int c)

写一个字符。

void

write(String str)

写一个字符串。

void

write(String str, int off, int len)

写一个字符串的一部分。

FileWriter

字符输出流的具体类

java.lang.Object 
java.io.Writer 
java.io.OutputStreamWriter 
java.io.FileWriter
构造方法

构造器

描述

FileWriter(File file)

给 File写一个 FileWriter

FileWriter(File file, boolean append)

在给出要写入的 FileWriter下构造 File ,并使用平台的 default charset构造一个布尔值,指示是否附加写入的数据

FileWriter(String fileName)

构造一个 FileWriter给出文件名,使用平台的 default charset

FileWriter(String fileName, boolean append)

使用平台的 default charset构造一个 FileWriter给定一个文件名和一个布尔值,指示是否附加写入的数据。

FileWriter fw = new FileWriter("d:" + File.separator + "testRW.txt");
        
//        FileWriter fw = new FileWriter("d:" + File.separator + "testRW.txt",true);
//        append方法:追加,即如果没有则默认false,每次write都会刷新、重新写入(每次都是新文件)
//                           如果有且值为true,则可以在原来的基础上不断追加(在原文件上不断追加)



        fw.write('a');//输入的应该是int,但是char的范围比int小,因此会自动转换
        fw.write("AAAAA,");

        /*
        FileWriter fw2 = (FileWriter) fw.append("BBBBB,");//追加,返回的是fw,因此fw2 = fw
        System.out.println(fw2 == fw);//验证,输出结果为true
//      由于fw2 为fw.append("BBBBB,")返回的对象,因此也可以在fw2的基础上进行追加
        fw2.append("CCCCC,");
        fw.append("DDDDD.");
         */       
         //上述这一段注释 执行完毕后,文件中输入内容:aAAAAA,BBBBB,CCCCC,DDDDD.

//      由于fw.append("BBBBB,")返回的是fw对象,因此可以在fw.append("BBBBB,")的基础上不断追加
        fw.append("BBBBB, ").append("CCCCC,").append("DDDDD.");
        fw.close();

执行结果:
程序执行完后,d盘的"testRW.txt"文件中写入内容

aAAAAA,BBBBB,CCCCC,DDDDD.

五、字符输入流:Reader

抽象类,基于字符的输入操作,读取

变量和类型

方法

描述

abstract void

close()

关闭流并释放与其关联的所有系统资源

int

read()

读一个字符

int

read(char[] cbuf)

将字符读入数组

abstract int

read(char[] cbuf, int off, int len)

将字符读入数组的一部分

FileWriter

字符输入流的具体类

java.lang.Object 
java.io.Reader 
java.io.InputStreamReader 
java.io.FileReader
构造方法

构造器

描述

FileReader(File file)

使用平台 FileReader ,在 File读取时创建一个新的 FileReader

FileReader(String fileName)

使用平台 default charset创建一个新的 FileReader ,给定要读取的文件的 名称

代码示例:

  • 创建对象
FileReader fr = new FileReader("d:" + File.separator + "testRW.txt");
  • 一次读一个字符
//一次读一个字符
        /*
        while(true){
            int c = fr.read();
            if(c == -1){
                break;
            }
            System.out.println((char)c);
        }
         */
  • 一次读一组字符
//一次读一组字符
        char[] chars = new char[100];
        int len = fr.read(chars);//这里获取了chars的长度,否则默认为100,空地方空格补齐,浪费
        String text = new String(chars,0,len);
        System.out.println(text);
        fr.close();

输出结果:

aAAAAA,BBBBB,CCCCC,DDDDD.

六、 转换流(字节流→字符流)

转换流,字节流转换为字符流,即字节流 ‘装饰’ 为字符流,使用了装饰者模式

常规转换流

输出流

将字节输出流转换为字符输出流
代码示例:

FileOutputStream fos = new FileOutputStream("d:" + File.separator + "testRW.txt");
        OutputStreamWriter osw = new OutputStreamWriter(fos);
        osw.write("666666");
        osw.flush();
        osw.close();

执行结果:
d盘中的"testRW.txt"文件被写入内容:

666666

输入流

将字节输入流转换为字符输入流
代码示例:

//参数1,要转换的字节流
        FileInputStream fis = new FileInputStream("d:" + File.separator + "testRW.txt");

        //参数2,指定编码名称  InputStreamReader isr = new InputStreamReader(fis,"gbk");
        
        InputStreamReader isr = new InputStreamReader(fis);
        while(true){
            int c = isr.read();
            if(c == -1){
                break;
            }
            System.out.println((char)c);
        }

执行结果:

6
6
6
6
6
6

Print

字符打印流(输出流)
使用PrintStream

代码示例1:

PrintStream ps = new PrintStream("d:" + File.separator + "PrintStream.txt");
        ps.println("啦啦啦啦啦");
        ps.println("哈哈哈哈哈");
        ps.println("嘿嘿嘿嘿嘿");

执行结果:
d盘中的"PrintStream.txt"写入内容:

啦啦啦啦啦
哈哈哈哈哈
嘿嘿嘿嘿嘿


使用PrintWriter

代码示例2:

PrintWriter pw = new PrintWriter("d:" + File.separator + "PrintStream.txt");
        pw.println("啦啦啦啦啦2");
        pw.println("哈哈哈哈哈2");
        pw.println("嘿嘿嘿嘿嘿2");
        pw.flush();//字符与字节最大的不同就是输出时是否要刷新管道,字符需要刷新

执行结果:
d盘中的"PrintStream.txt"写入内容:

啦啦啦啦啦2
哈哈哈哈哈2
嘿嘿嘿嘿嘿2

字符输入流

代码示例:

//转换,PrintWriter转成字符流
        FileOutputStream fos = new FileOutputStream("d:" + File.separator + "PrintStream.txt");
        PrintWriter pw = new PrintWriter(fos);//转换成打印流
        pw.println("你好啦啦啦");
        pw.println("哈哈哈");
        pw.flush();

BufferedReader

缓存读取流,将字符输入流 转为 带有缓存、可以一次读取一行缓存字符读取流
代码示例:

//缓存读取流,将字符输入流 转为 带有缓存、可以一次读取一行的缓存字符读取流
        FileReader fw = new FileReader("d:" + File.separator + "PrintStream.txt");
        BufferedReader br = new BufferedReader(fw);
        String text = br.readLine();
        System.out.println(text);

输出结果:

你好啦啦啦

七、 序列化技术Serializable

序列化,如果想要某个类能够被序列化,该类要实现序列化的接口,即implements Serializable,只要将类实现标识接口——Serializable接口,不需要重写任何方法

  • implements Serializable 只是一个标记
  • 如果没有允许序列化的接口,则会报错:NotSerializableException(判断是否实现了Serializable,没实现则报错)

序列化

序列化:将程序中的对象以文件形式存储下来(按对象在内存中的存储字符序列)
【Java对象转换为字节序列的过程】

代码示例:

  • 首先建一个Book类,implements Serializable,能够被序列化
static class Book implements Serializable{
        private String name;
        private String info;

        public Book() {
        }

        public Book(String name, String info) {
            this.name = name;
            this.info = info;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getInfo() {
            return info;
        }

        public void setInfo(String info) {
            this.info = info;
        }

        @Override
        public String toString() {
            return "Book{" +
                    "name='" + name + '\'' +
                    ", info='" + info + '\'' +
                    '}';
        }
    }// end class book
  • 序列化
ArrayList<序列化.Book> list = new ArrayList<序列化.Book>();
        list.add(new 序列化.Book("Java核心技术1","讲述了Java核心技术1的有关内容"));
        list.add(new 序列化.Book("Java核心技术2","讲述了Java核心技术2的有关内容"));
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:" + File.separator + "序列化技术.txt"));
        oos.writeObject(b);
        oos.close();

执行结果:
在d盘新建文档“序列化技术.txt”,并且将对象存储到该文档中

反序列化

反序列化:存储下来的数据读到程序中
【把字节序列恢复为Java对象的过程】

代码示例:

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:" + File.separator + "序列化技术.txt"));
	 
        Object o = ois.readObject();
        System.out.println(o);

执行结果:

Book{name='Java核心技术1', info='讲述了Java核心技术1的有关内容'}
Book{name='Java核心技术2', info='讲述了Java核心技术2的有关内容'}

部分属性序列化

实现部分字段序列化的方式:

① 使用transient修饰符
② 使用static修饰符
③ 默认方法writeObject和readObject
④ Externalizable实现Java序列化

使用transient修饰符

给属性添加transient修饰词,对象被序列化的时候 忽略 这个属性

代码示例:

上述序列化的代码中,将Book的info属性添加transient修饰词

private transient String info;

执行结果:

从输出结果可以看出,对象被序列化的时候忽略了info属性

Book{name='Java核心技术1', info='null'}, 
Book{name='Java核心技术2', info='null'}
使用static修饰符

static修饰符修饰的属性也不会参与序列化和反序列化

代码示例:

上述序列化的代码中,将Book的info属性添加static修饰词

private static String info;

执行结果:

从输出结果可以看出,对象被序列化的时候忽略了info属性

Book{name='Java核心技术1', info='null'}, 
Book{name='Java核心技术2', info='null'}
默认方法writeObject和readObject

如果目标类中没有定义私有的writeObject或readObject方法,序列化和反序列化的时候将调用默认的方法来根据目标类中的属性来进行序列化和反序列化
如果目标类中定义了私有的writeObject或readObject方法,序列化和反序列化的时候将调用目标类指定的writeObject或readObject方法来实现,

注意,writeObject或readObject方法一定有修饰符private void,否则不生效

代码示例1:

  • 在类Book中定义私有的writeObject或readObject方法,此时name和info都能够被序列化
private void writeObject(ObjectOutputStream objOut) throws IOException {
            objOut.writeObject(name);
            objOut.writeObject(info);
        }
        private void readObject(ObjectInputStream objIn) throws IOException,
                ClassNotFoundException {
            name= (String) objIn.readObject();
           info= (String) objIn.readObject();
        }

执行结果:

Book{name='Java核心技术1', info='讲述了Java核心技术1的有关内容'},
Book{name='Java核心技术2', info='讲述了Java核心技术2的有关内容'}

代码示例2:

  • 在类Book中定义私有的writeObject或readObject方法,此时仅name能够被序列化
private void writeObject(ObjectOutputStream objOut) throws IOException {
            objOut.writeObject(name);
           // objOut.writeObject(info);
        }
        private void readObject(ObjectInputStream objIn) throws IOException,
                ClassNotFoundException {
            name= (String) objIn.readObject();
          // info= (String) objIn.readObject();
        }

执行结果:

Book{name='Java核心技术1', info='null'}, 
Book{name='Java核心技术2', info='null'}
Externalizable实现序列化
  • Externalizable继承自Serializable
  • 使用Externalizable接口定义了writeExternal和readExternal两个抽象方法,需要实现readExternal方法和writeExternal方法来实现序列化和反序列化
  • writeExternal和readExternal两个抽象方法对应Serializable接口的writeObject和readObject方法

区 别

Serializable

Externalizable

实现复杂度

实现简单,Java对其有内建支持

实现复杂,由开发人员自己完成

执行效率

所有对象由Java统一保存,性能较低

开发人员决定哪个对象保存,可能造成速度提升

保存信息

保存时占用空间大

部分存储,可能造成空间减少

使用频率


偏低

代码实现:

tips:Book类一定要实现该接口,即implements Externalizable
在类Book中定义私有的readExternal方法和writeExternal方法,此时仅name能够被序列化

public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
      //  out.writeObject(info);
    }
    public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
        name = (String) in.readObject();
      //  info = (String) in.readObject();
    }

执行结果:

Book{name='Java核心技术1', info='null'},
Book{name='Java核心技术2', info='null'}

面试题中常问:
实现序列化有哪些方式?

  • Serializable 和 Externalizable 两种接口

※收集异常日志

创建文档bug.txt,来收集异常,即每次抛出异常时,将异常写入文档中

代码示例:

try {
            String s = null;
            s.toString();
        }catch(Exception e){
            PrintWriter pw = new PrintWriter("d:" + File.separator + "bug.txt");
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            pw.println(sdf.format(new Date()));
            e.printStackTrace(pw);
            pw.close();
        }

执行结果:
d盘创建"bug.txt"文档并写入内容:

2021-07-09 12:12
java.lang.NullPointerException
at com.company.online.t0103.t03_IO.ExceptionTxt.main(ExceptionTxt.java:14)

※ Properties

Properties可以保存到流中从流中加载
Properties既属于Map,又属于IO

构造方法

构造器

描述

Properties()

创建一个没有默认值的空属性列表

Properties(int initialCapacity)

创建一个没有默认值的空属性列表,并且初始大小容纳指定数量的元素,而无需动态调整大小

Properties(Properties defaults)

创建具有指定默认值的空属性列表

代码示例:

Properties ppt = new Properties();

常用方法

变量和类型

方法

描述

void

load(Reader reader)

以简单的面向行的格式从输入字符流中读取属性列表(键和元素对)

void

store(Writer writer, String comments)

将此 Properties表中的此属性列表(键和元素对)以适合使用 load(Reader)方法的格式写入输出字符流

store

存数据

代码示例:

//ppt.store();  Map中的键值对存储变成.properties文件

        ppt.put("name","Java核心技术");
        ppt.put("info","讲述了Java核心技术的有关内容");
        FileWriter fw = new FileWriter("d:" + File.separator + "book.properties");
        ppt.store(fw,"存储的图书");//(文件,注释)
        fw.close();

执行结果:
在d盘创建“book.properties”文件,文件中写入内容

#\u5B58\u50A8\u7684\u56FE\u4E66(unicode码,翻译一下即可)
#Fri Jul 09 12:33:44 CST 2021(创建的日期)
name = Java核心技术
info = 讲述了Java核心技术的有关内容

load

读数据

代码示例:

//ppt.load(); 传入的文件(字符流字节流都可以)中的内容加载成程序中的Map集合
		Reader r = new FileReader("d:" + File.separator + "book.properties");
        ppt.load(r);
        
//      Map集合中的取值方法
//      System.out.println(ppt.get("name"));
//      System.out.println(ppt.get("info"));

//      properties中的取值方法
        System.out.println(ppt.getProperty("name"));
        System.out.println(ppt.getProperty("info"));

输出结果:

Java核心技术
讲述了Java核心技术的有关内容

※ try-with-resources

JDK1.7之前

FileReader fr = null;
        try{
            fr = new FileReader("PrintStream.txt");
            int c = fr.read();
            System.out.println((char)c);
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

JDK1.7

try(FileReader fr = new FileReader("PrintStream.txt");){
            int c = fr.read();
            System.out.println((char)c);
        } catch (IOException e) {
            e.printStackTrace();
        }

JDK9进行了一些优化

static class CloseDemo implements Closeable{
        @Override
        public void close() throws IOException {
            System.out.println("close方法被调用了");
        }
    }
//CloseDemo 实现接口(implements Closeable)之后不会报错
        try(CloseDemo d = new CloseDemo()){

        }catch(Exception e){

        }