Java 高级:I/O

Java 高级:I/O

 

目录

目录

Java基础:I/O

IO流概述

节点流

FileReader

FileWriter

FileInputStream和FileOutputStream

缓冲流

转换流的使用

对象流

对象的序列化 


IO流概述

 

  • I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于 处理设备之间的数据传输。如读/写文件,网络通讯等。
  • Java程序中,对于数据的输入/输出操作以“流(stream)” 的 方式进行。
  • java.io 包下提供了各种“流”类和接口,用以获取不同种类的 数据,并通过标准的方法输入或输出数据。

输入 input:读取外部数据(磁盘,光盘等存储设备)到程序(内存)中。

输出 output:将程序内存数据输出到磁盘,光盘等存储设备中。

流的分类

  • 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
    • 字节流适用于图片视频等文件
    • 字符流使用与纯文本的文件
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色不同分为:节点流,处理流
    • 直接作用于在文件上的流成为节点流,比如想把某个文件加入到内存中去,我们在文件和内存中造出一条直接的流,这就是节点流。更直接的语言表示,相当于把水池子的水通过水管放入澡盆,那么连接的这根水管就是节点流。
    • 处理流 更抽象一点,它相当于封装了节点流,对节点流进行处理,比如以水池子的水通过水管放入澡盆这个为例,如果想让水流的更快,通过处理流进行了 一层包裹。

节点流

FileReader

节点流读入数据的基本操作

读取文件:

  • 实例化File,指明要操作的文件
  • 建立一个流对象,将已存在的一个文件加载进流
  • 调用流对象的读取方法将流中的数据读入到数组中。 
  • 关闭资源

实例(一):使用FileReader读取文本文件到控制台


    /**
     * 将h1.txt读取到内存中
     * @throws IOException
     */
    @Test
    public void test() throws IOException {
        // 实例化File,指明要操作的文件
        File file = new File("D:\\io\\1.txt");
        // 提供具体的流
        FileReader fileReader = new FileReader(file);

        // read()返回读入的字符,如果达到文件末尾,则返回 -1
        int readData = fileReader.read();
        while (readData != -1) {
            System.out.println((char) readData);
            //读取下一个字符 直到 = -1 为止
            readData = fileReader.read();
        }
        // 流的关闭操作
        fileReader.close();
    }

需要注意的是关于异常的处理,之前的例子我们把异常直接抛出,这会导致当某个步骤出现问题的时候,接下来的程序将不再执行,那么流是未被释放的。这会导致内存泄漏以及资源浪费。

所以我们需要用try catch的方式捕捉异常,并且最后一定会关闭流。 

  /**
     * 将h1.txt读取到内存中
     */
    @Test
    public void test() {
        FileReader fileReader = null;
        try {
            // 实例化File,指明要操作的文件
            File file = new File("D:\\io\\1.txt");
            // 提供具体的流
            fileReader = new FileReader(file);
            // read()返回读入的字符,如果达到文件末尾,则返回 -1
            int readData = fileReader.read();
            while (readData != -1) {
                System.out.println((char) readData);
                //读取下一个字符 直到 = -1 为止
                readData = fileReader.read();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 流的关闭操作本身也会有异常,但如果一开始创建fileReader对象就抛出异常,那么在这里的fileReader则为null
                // 这又导致空指针异常,因此需要做处理
                if (fileReader != null) {
                    fileReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();

            }
        }
    }

总结:

  1. read()的理解:返回读取的一个字符,如果文件达到末尾,则返回1。
  2. 异常的处理:为了保证流资源一定可以执行关闭操作,使用 try-catch-finally 来处理。
  3. 读入的文件一定要存在,否则会报出FileNotFoundException的异常。

FileReader中使用read()的重载方法(常用

对于读取文件,不外乎创建File实例,创建流实例,执行读取操作,以及关闭资源。

read()的重载方法在于执行读取操作的不同。

这里我们用到了 `read(char[] cbuf)`

实例(二):使用FileReader读取文本文件到控制台

    @Test
    public void test2() {
        FileReader fileReader = null;
        try {
            // 1.File类的实例化
            File file = new File("D:\\io\\1.txt");
            // 2.FileReader流的实例化
            fileReader = new FileReader(file);
            char[] cbuf = new char[5];
            int len;
            // 3.读入操作 read(char[] cbuf):返回每次读入cbuf数组中字符的个数,如果达到文件末尾,返回1
            while ((len = fileReader.read(cbuf)) != -1) {
                //这里需要注意的是 循环的是len,len有几次就遍历几次,而不是数组的长度.
                for (int i = 0; i < len; i++) {
                    System.out.print(cbuf[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 流的关闭操作
            try {
                fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

实例(三):使用FileReader读取文本文件到控制台,使用String的方式来接受数据。

 
 @Test
    public void test2() {
        FileReader fileReader = null;
        try {
            // 1.File类的实例化
            File file = new File("hello.txt");
            // 2.FileReader流的实例化
            fileReader = new FileReader(file);
            char[] cbuf = new char[5];
            int len;
            // 3.读入操作 read(char[] cbuf):返回每次读入cbuf数组中字符的个数,如果达到文件末尾,返回1
            while ((len = fileReader.read(cbuf)) != -1) {
                //第二种写法
                String  str = new String(cbuf, 0, len);
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 流的关闭操作
            try {
                fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

FileWriter

写入文件的步骤:

  1. 创建流对象,建立数据存放文件 
  2. 调用流对象的写入方法,将数据写入流 
  3. 关闭流资源,并将流中的数据清空到文件中。
    /**
     * 从内存中写出数据到硬盘里
     * 如果文件不存在,则创建
     * 如果文件存在,根据构造器的不同
     * 如果是  FileWriter(file,true) 则追加
     * 如果是 FileWriter(file,false) 则覆盖
     */
    @Test
    public void testFileWriter() {
        FileWriter fileWriter = null;
        try {
            // 1. 创建File实例
            File file = new File("h1.txt");
            // 2.创建FileWriter的实例
            fileWriter = new FileWriter(file, false);
            // 3.写入文件
            fileWriter.write("小蟹写代码!");
            fileWriter.write("真的很难!");
            // 4.关闭流
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileWriter != null) {
                    fileWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

FileInputStream和FileOutputStream

Reader和Writer无法处理字节流,也就是无法处理非文本文件(.jpg ,.mp3 ,.mp4,.doc,.ppt)

对于非文本文件,使用字节流处理。

通过FileInputStream读取一张图片,把读取的文件通过FileOutputStream写出来:

    /**
     * 通过FileInputStream读取一张图片,把读取的文件通过FileOutputStream写出来.
     */
    @Test
    public void test() {
        FileInputStream is1 = null;
        FileOutputStream is2 = null;
        try {
            File file1 = new File("D:\\Enviroment\\image\\2.jpg");
            File file2 = new File("D:\\Enviroment\\image\\3.jpg");
            is1 = new FileInputStream(file1);
            is2 = new FileOutputStream(file2);
            byte[] readByte = new byte[5];
            int len;
            while ((len = is1.read(readByte)) != -1) {
                is2.write(readByte, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

实例(二):抽取使用FileInputStream和FileOutputStream对于文件的复制操作

这样的一段代码可以抽取出来,它相当于复制某个文件的功能,抽取出来以后我们可以再试着看看复制视频所花费的时间,以便接下来对于缓冲流的学习。

 /**
     * 复制文件
     * @param filepath
     * @param File2
     */
    public static void copyFile(String filepath, String File2) {
        FileInputStream is1 = null;
        FileOutputStream is2 = null;
        try {
            File file = new File(filepath);
            File file2 = new File(File2);
            is1 = new FileInputStream(filepath);
            is2 = new FileOutputStream(File2);
            byte[] readByte = new byte[5];
            int len;
            while ((len = is1.read(readByte)) != -1) {
                is2.write(readByte, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

 传入稍微大一点的文件来操作,便于时间上的观察,复制了一个flv格式的视频。

 结果:

复制操作花费的时间为:4135

  /**
     * 通过字节流读取文件并复制写出
     */
    @Test
    public void testCopyFile() {
        long start = System.currentTimeMillis();
        String filePath1 = "D:\\BaiduNetdiskDownload\\04 Spring\\01.Spring框架简介\\01.spring课程四天安排.flv";
        String filePath2 = "D:\\BaiduNetdiskDownload\\04 Spring\\01.Spring框架简介\\01.spring课程四天安排2.flv";
        copyFile(filePath1, filePath2);
        long end = System.currentTimeMillis();
        System.out.println("复制操作花费的时间为:" + (end - start));
    }

缓冲流

为了提高数据读写的速度,JavaAPI提高了带缓冲功能的流类,在使用这些流时,会创建一个内部缓冲区数组,缺省使用8192个字节(8kb)的缓冲区。

源码可以看出:DEFAULT_BUFFER_SIZE = 8192

缓冲流要 “套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:

字节流:BufferedInputStream 和 BufferedOutputStream

字符流:BufferdReader 和 BufferdWriter

对于缓冲流的理解:

  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
  • 当使用BufferdInputStream读取字节文件时,BufferdInputStream会一次读取8192个,存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组
  • 向流中写字节时,不会直接写到文件,先写道缓冲区直到缓冲区写满。BufferdOutputStream才会把缓冲区中的数据一次性写到文件里。
    • 使用flush()可以强制将缓冲区的内容全部写入输出流
  • 关闭流的时候,只要关闭最外层的流即可,关闭最外层流也会相应关闭内存节点流。
  • flush()方法的使用:手动将buffer中内容写入文件
  • 如果是带缓冲区的流的Close()方法,不但会关闭流,还会再关闭流之前刷新缓冲区,关闭后不能再写出

为什么缓冲流会更快?

因为缓冲流是先把文件的数据缓冲8192个字节/字符到缓冲区内存中,然后冲内存中读取1024个字节/字符,从内存中读取要比从硬盘中读取的效率高。

 

实例(一):使用缓冲流实现非文本文件的复制

   /**
     * 使用缓冲流实现非文本文件的复制
     */
    @Test
    public void bufferInputStreamTest() {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            // 1.File实例
            File fileRead = new File("1564588424(1).jpg");
            File fileWrite = new File("harry.jpg");

            // 2.流实例
            FileInputStream is = new FileInputStream(fileRead);
            FileOutputStream os = new FileOutputStream(fileWrite);

            // 3.缓冲流实例
            bis = new BufferedInputStream(is);
            bos = new BufferedOutputStream(os);

            // 4.读取文件
            byte[] raadData = new byte[1024];
            int len;
            while ((len = bis.read(raadData)) != -1) {
                String str = new String(raadData, 0, len);
                // 5. 写入文件
                bos.write(raadData, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 6.关闭流
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

实例(二)抽取使用缓冲流复制文件的操作

    /**
     * 使用缓冲流复制文件
     * @param filePath1
     * @param filePath2
     */
    public static void bufferedCopyFile(String filePath1 ,String filePath2) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            // 1.File实例
            File fileRead = new File(filePath1);
            File fileWrite = new File(filePath2);
            // 2.流实例
            FileInputStream is = new FileInputStream(fileRead);
            FileOutputStream os = new FileOutputStream(fileWrite);
            // 3.缓冲流实例
            bis = new BufferedInputStream(is);
            bos = new BufferedOutputStream(os);
            // 4.读取文件
            byte[] raadData = new byte[1024];
            int len;
            while ((len = bis.read(raadData)) != -1) {
                String str = new String(raadData, 0, len);
                // 5. 写入文件
                bos.write(raadData, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 6.关闭流
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

实例(三)使用缓冲流对视频进行复制

    /**
     * 使用缓冲流进行对视频的复制
     */
    @Test
    public void useBufferCompareTo() {
        long start = System.currentTimeMillis();
        String filePath1 = "D:\\BaiduNetdiskDownload\\04 Spring\\01.Spring框架简介\\01.spring课程四天安排.flv";
        String filePath2 = "D:\\BaiduNetdiskDownload\\04 Spring\\01.Spring框架简介\\01.spring课程四天安排_buffer.flv";
        bufferedCopyFile(filePath1,filePath2);
        long end = System.currentTimeMillis();
        System.out.println("复制操作花费的时间为:" + (end - start));
    }

结果:

复制操作花费的时间为:147,在上面的我们也对此视频进行了复制,所花费的时间是4135快了非常多。

实例(四)  使用BufferdReader和BufferdWirter对文本文件进行复制

  /**
     * 使用BufferdReader和BufferdWirter对文本文件进行复制.
     */
    @Test
    public void testBufferReaderAndeWriter() {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            // 1.创建文件和缓冲流
            br = new BufferedReader(new FileReader(new File("test1.txt")));
            bw = new BufferedWriter(new FileWriter("test2.txt"));
            // 2. 读取操作,方式一
            char[] cbvf = new char[1024];
            int len;
            while ((len = br.read(cbvf)) != -1){
                // 3.写操作
                bw.write(cbvf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流
            if( br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bw!=null){

                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

实例(五)  第一种方式 ,使用BufferdReader和BufferdWirter对文本文件进行复制,用`readLine()`方法

`readLine()` 使用String类型来接受,它一次只能读取一行,并且不自带换行功能。

    /**
     * 使用BufferdReader和BufferdWirter对文本文件进行复制,用`readLine()`方法
     */
    @Test
    public void test2BufferReaderAndeWriter() {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            // 1.创建文件和缓冲流
            br = new BufferedReader(new FileReader(new File("test1.txt")));
            bw = new BufferedWriter(new FileWriter("test2.txt"));
            // 2. 读取操作,方式二
            String str;
            int len;
            //readLine 一次读取一行
            while ((str = br.readLine())!=null) {
                    bw.write(str); // 一次写入一行字符串
                    bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 4.关闭流
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bw != null) {

                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

test1.txt 

 

test2.txt 复制后未换行 

转换流的使用

转换流提供了在字节流和字符流之间的转换

Java API提供了两个转换流:

  •  InputStreamReader:将InputStream转换为Reader   将字节输入流转换为字符输入流

  •  OutputStreamWriter:将Writer转换为OutputStream  将字符输出流转换为字节输出流

字节流中的数据都是字符时,转成字符流操作更高效。

很多时候我们使用转换流来处理文件乱码问题。实现编码和 解码的功能。

相当于解码和编码

解码:字节,字节数组  ---------->字符,字符数组

编码:字符,字符数组  ---------->字节,字节数组

这里涉及到字符集的问题,具体在实例中说明。

实例(一):使用转换流读取文本文件到控制台

这里使用转换流的空参构造器,空参构造器指定的字符集是系统默认的字符集,也就是IDEA中的字符集。

它有第二个参数,指明字符集,具体使用哪个字符集,取决于test.1保存时使用的字符集

    /**
     * 使用转换流读取文本文件到控制台
     */
    @Test
    public void inputStreamReaderTest(){
        InputStreamReader isr = null;
        try {
            // 1.创建File实例
            File file = new File("test1.txt");
            // 2.创建流实例
            FileInputStream is = new FileInputStream(file);
            // 3.创建转换流实例
            isr = new InputStreamReader(is);
            int len;
            char[] cbuf = new char[1024];
            // 4.读取文件
            while( (len=isr.read(cbuf)) !=-1){
               String str = new String(cbuf,0,len);
               // 5.输出到控制台
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (isr!=null){
                // 6.关闭流
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

控制台结果: 

​ 

如果使用带有字符集参数的构造器,test1.txt是用UTF-8的字符集,如果转换为GBK。

             // 3.创建转换流实例
            isr = new InputStreamReader(is,"GBK");

控制台输出乱码 

 

 

实例(二):读取一个字符集为 utf-8的文件,并通过GBK的字符集输出出来.

    /**
     * 综合使用OutputStreamWriter和InputStreamReader
     * 读取一个字符集为 utf-8的文件,并通过GBK的字符集输出出来.
     */
    @Test
    public void outPutStreamWriterTest() {

        InputStreamReader isr = null;
        OutputStreamWriter osw = null;
        try {
            File file1 = new File("test1.txt");
            File file2 = new File("test2.txt");

            FileInputStream  is = new FileInputStream(file1);
            FileOutputStream os = new FileOutputStream(file2);

            isr = new InputStreamReader(is, StandardCharsets.UTF_8);
            osw = new OutputStreamWriter(os, "GBK");
            char[] cbuf = new char[1024];
            int len;
            while ((len = isr.read(cbuf)) != -1) {
                osw.write(cbuf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (isr != null) {
                    isr.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (osw != null) {
                    osw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

​ 

执行后:

reload in GBK一下就能正常阅读了

​ 

对象流

ObjectInputStreamOjbectOutputSteam

用于存储和读取基本数据类型数据或对象的处理流,它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

序列化:用ObjectOutputStream类保存基本类型数据或对象的机制

反序列化:用ObjectInputStream类读取基本类型的数据或对象的机制

ObjectOutputStream和ObjectInputStream 不能序列化static和transient修饰的成员变量

对象序列化机制

  • 序列化:允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或者通过网络将这种二进制流传输到另一个网络节点
  • 反序列化:当其他程序获取了这种二进制流,就可以恢复成原来的Java对象

如果需要让某个对象支持序列化机制,则必须让对象所属的及其属性是可以序列化的,为了让某个类是可以序列化的,该类必须实现如下两个接口之一,否则会抛出NotSerializableException异常

  • Serializable

  • Externalizable

实例(一):使用ObjectOutputStream实现序列化,将String类型的对象保存在硬盘上

    /**
     * 序列化过程:将内存中的java对象保存到硬盘或者通过网络传输出去
     * 使用ObjectOutputStream实现序列化,将 String类型的对象保存在硬盘上.
     */
    @Test
    public void objectOutputStreamTest() {
        ObjectOutputStream oos = null;
        try {
            //1.创建流
            FileOutputStream fis = new FileOutputStream(new File("test.dat"));
            //2.创建ObjectOutputStream实例
            oos = new ObjectOutputStream(fis);
            String str = "什么是序列化呢?是将内存中的java对象保存到硬盘或者通过网络传输出去";
            // 3. 写出数据
            oos.writeObject(str);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    // 4.释放资源
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

实例(二):使用ObjectOutputStream实现序列化,将String类型的对象保存在硬盘上

    /**
     * 反序列化:将保存在test.dat里的对象读取出来
     * 通过ObjectInputStream将刚刚保存在硬盘上的数据读取出来
     */
    @Test
    public void objectInputStreamTest() {
        ObjectInputStream ois = null;
        try {
            // 1. FileInputStream 实例
            FileInputStream fis = new FileInputStream(new File("test.dat"));
            // 2.ObjectOutputStream 实例
            ois = new ObjectInputStream(fis);
            // 3.读取文件 因为我们知道是用String对象,所以直接强转即可.
            String str = (String) ois.readObject();
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
            // 4.关闭流
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

对象的序列化 

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态常量:

  • private static final long serialVersionUID;
  • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象 进行版本控制,有关各版本反序列化时是否兼容。
  • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自 动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议, 显式声明。

简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验 证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同 就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异 常。(InvalidCastException)

再直白一点来说,如果我们需要存放一个Person类,我们已经把它序列化了,但是没有加serialVersionUID,然后也存放好了,此时如果再对Person类进行修改,比如添加几个属性,或者方法。此时Person类自动生成的UID根据内部细节的改变也改变了,再反序列化的时候,这个已经修改过了Person类已经不再是最开始的Person类了,于是在反序列化的时候,就出现了问题。

我们从实例中来理解上诉理论,接下来我们将试着将实体类对象存储到硬盘中 先创建一个实体类 Person

/**
 * 实体类对象Person
 * @author Claw
 */
public class Person {

    private  String name;
    private  Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

实例(二)使用ObjectOutputStream实现Person类的序列化,将Person类型的对象保存在test.dat文件中.

    /**
     * 使用ObjectOutputStream实现Person类的序列化,将Person类型的对象保存在test.dat文件中.
     */
    @Test
    public void objectOutputStreamTest() {

        ObjectOutputStream oos = null;
        try {
            // 1.创建流
            FileOutputStream fis = new FileOutputStream(new File("test.dat"));
            // 2.创建ObjectOutputStream实例
            oos = new ObjectOutputStream(fis);
            // 3. 创建实体类对象Person实例
            Person person = new Person("小蟹",16);
            // 4. 将Person类对象写出到硬盘中
            oos.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    // 5.释放资源
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

此时抛出异常:java.io.NotSerializableException

在对象序列化的机制里已经说过了,如果想让某个对象支持序列化,则必须让对象所属的类及其属性是可序列化的。

所以我们序列化的类需要实现Serializable接口,我们再写出String类型的时候,为什么可以序列化呢?点击String类的源码,其实可以看到String类已经 implements了 Serilazable接口

Serilazable 是一个标识接口,它什么抽象方法都没有,这种接口一般称为标识接口,凡是实现这个接口的类,都识别为可序列化的。

光实现这个接口还不够,在源码中可以看到这样一行注释:

它希望我们提供一个声明为 static final long serialVersionUID 的属性

这个UID的值可以随便写一个,这个是序列版本号。在不写的情况下,serialVersionUID的值会根据内部细节自动生成。但如果类的实例变量做了修改,serialVersionUID会发生变化,所以我们需要显示声明。

改动一下:

/**
 * 实体类对象Person
 * @author Claw
 */
public class Person implements Serializable {

    private static  final  long serialVersionUID = 212313413L;

    private  String name;
    private  Integer age;
    
    // ......构造方法之类的已省略

}

此时再把这个Person保存到test.dat中就能运行成功,同时也能读取出来。

如果此时改动Person类,增加一个属性。

/**
 * 实体类对象Person
 * @author Claw
 */
public class Person implements Serializable {

    private static  final  long serialVersionUID = 212313413L;
    private  String name;
    private  Integer age;
    //增加一个属性
    private  String sex;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    // 增加一个构造方法
    public Person(String name, Integer age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // toString方法也重新生成
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }

此时再去反序列化读取内容,也能读取出来

结果为:sex因为没有赋值,所以是null

Person{name='小蟹', age=16, sex='null'}

如果没有显示的声明serialVersionUID,再改动Person类后会报错。

总结:

序列化需要满足如下要求:

  • 实现接口:Serializable
  • 当前类提供一个全局常量:serialVersionUID
  • 除了当前类需要实现Serializable接口,还要保证其内部所有属性必须是可序列化的。(默认情况下,基本类型数据可序列化)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值