11.偏头痛杨的Java入门教学系列之IO篇

版权声明:偏头痛杨:本文为博主原创文章,未经作者允许不得转载,版权必究! https://blog.csdn.net/piantoutongyang/article/details/80161323
复习
1.线程的同步与通信有几种方式?
2.创建线程有几种方式?
3.什么是方法重写与方法重载?
4.线程与进程的区别?
5.如何最高效的遍历Map?
6.线程的状态与生命周期?
7.HashTable与HashMap的区别?
8.LinkedList与ArrayList的区别?
9.集合框架的继承体系?
10.什么是并发与并行?

前文链接
1.偏头痛杨的Java入门教学系列之认识Java篇
2.偏头痛杨的Java入门教学系列之变量&数据类型篇
3.偏头痛杨的Java入门教学系列之表达式&运算符&关键字&标识符&表达式篇
4.偏头痛杨的Java入门教学系列之初级面向对象篇
5.偏头痛杨的Java入门教学系列之流程控制语句篇
6.偏头痛杨的Java入门教学系列之数组篇
7.偏头痛杨的Java入门教学系列之进阶面向对象篇
8.偏头痛杨的Java入门教学系列之异常篇
9.偏头痛杨的Java入门教学系列之初级集合框架篇
10.偏头痛杨的Java入门教学系列之初级多线程篇


前戏
有很多同学单纯的认为IO不就是一个读文件和写文件吗,不重要,只要简单的复制粘贴就OK,
会用个File以及什么流就算"熟练掌握 "了。
使用场景也就上传文件才用的到,仅此而已。

呵呵呵呵,那就大错特错了,IO的使用范围很广,最能体现IO价值的就是网络上的数据传递,
尤其是进入互联网时代后,各种常见的分布式架构,都少不了IO的体现。
并且很多大厂的面试题中都会体现出对IO的重视,包括衍生出来的NIO、序列化等等。
因此学好IO,变成了一件很重要的事情。


IO基本概念
IO可以简单的理解成INPUT和OUT,代表输入输出的意思。输入就是读,输出就是写。
IO可以读写硬盘、光盘、内存、键盘、网络等资源上的数据。

IO中的流就相当于现实生活中的水流一样,一打开自来水的龙头开关,水就从一头流向另一头。
可以理解成每个按顺序排列的水滴就是需要传输的字节。
把有序数据理解成流,流负责传输数据,以便于输入输出。数据是流动的,是有方向的流动。

流的分类
按数据的走向可以分为:输入流,输出流。
按数据的单位可以分为:字节流、字符流。
按装饰模式可以分为:节点流(底层)、处理流(上层)。


输入流与输出流
输入流:只能从中读取数据,而不能向其写入数据。一般用于将数据从网络、硬盘读取到内存中。
输出流:只能向其写入数据,而不能从中读取数据。一般用于将数据从内存中写入到网络、硬盘。

输入流主要由InputStream和Reader作为父类。
输出流主要由OutputStream和Writer作为父类。
他们都是抽象的,因此无法直接创建对象。


字节流与字符流
字节流与字符流的用法几乎完全一样,区别在于所操作的单位不同,字节流操作8位的字节,
而字符流操作16位的字符。

字节流主要由InputStream和OutputStream作为父类。
字符流主要由Reader和Writer作为父类。


节点流与处理流
处理流(上层):
对一个已存在的流进行连接或封装,通过封装后的流来实现数据读写功能。

节点流(底层):
向特定的节点写入&读取数据的流。程序连接到实际的数据源,和实际的输入输出节点连接。

使用处理流进行输入输出时,程序不会和实际的输入输出节点连接,对底层的处理流做了一层封装。
程序可以采用相同的输入输出代码来访问不同的数据源。(涉及到装饰器模式)
处理流使得java程序无需理会输入输出的节点是磁盘、网络还是其他,
只要将这些节点流包装成处理流,
就可以使用相同的输入输出代码来读写不同的输入输出设备的数据。

节点流用于和底层的物理存储节点直接关联,不同的物理节点获取节点流的方式可能存在差异。
程序可以把不同的物理节点流包装成统一的处理流,
允许程序使用统一的输入输出代码来读写不同的物理存储节点资源。


常用的输入输出流体系
io流按功能分成许多类,每个功能又提供字节流&字符流,
字节流与字符流又分别提供了输入流与输出流。
如果输入输出的是文本内容可以使用字符流,如果输入输出的是二进制内容,可以使用字节流。
分类
字节输入流
字节输出流
字符输入流
字符输出流
抽象父类
InputStream
OutputStream
Reader
Writer
访问文件  (节点流 )
FileInputStream
FileOutputStream
FileReader
FileWriter
访问数组  (节点流 )
ByteArrayInputStream
ByteArrayOutputStream
CharArrayReader
CharArrayWriter
访问管道  (节点流)
PipedInputStream
PipedOutputStream
PipedReader
PipedWriter
访问字符串  (节点流 )
   
StringReader
StringWriter
缓冲流
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
转换流
   
InputStreamReader
OutputStreamWriter
对象流
ObjectInputStream
ObjectOutputStream
   
过滤器流
FilterInputStream
FilterOutputStream
FilterReader
FilterWriter
打印流
 
PrintStream
 
PrintReader
推回输入流
PushbackInputStream
 
PushbackReader
 
特殊流
DataInputStream
DataOutputStream
   


字节流
字节流是IO最原始的方式,因为计算机处理数据总是以一个byte为基本单位的,
字节流就是每次读取的单位为byte。字节流是所有流的基础,也是其他高级流的前提。

字节流可以处理所有类型的数据,包括:音乐、图片、文字、视频、各种文件等等。
多数以"Stream"结尾的类都是字节流。

字符流只能处理文本,读写的单位是字符。多数以"Writer"与"Reader"结尾的类都是字节流。

java的基础字节流的类为:InputStream,OutputStream。
通过他们俩可以衍生出许多子类,常见的有:
FileInputStream,FileOutputStream,ObjectInputStream,ObjectOutputStream,BufferedInputStream,BufferedOutputStream等。

byte是计算机最基本的单位,所以字节流可以应付几乎所有的流的处理,只不过,在处理具体数据格式的时候,效率没有具体的实现类高,如字符格式,对象格式等。主要操作对象是byte数组,
通过read()和wirte()方法把byte数组中的数据写入或读出。

使用字节流复制文件
public class FileInputOutStreamDemo1 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        
        try {
            fis = new FileInputStream("enya恩雅-only time.mp3");
            fos = new FileOutputStream("new-enya恩雅-only time.mp3");
            
            byte[] temp = new byte[1024];
            while(fis.read(temp)!=-1) {
                fos.write(temp);
            }
            
            temp = new byte[1024];
            //将字符串写入到文件
            fos = new FileOutputStream("aaa.txt");
            fos.write("我爱你亲爱的姑娘".getBytes());//直接覆盖,而不是追加。如果想追加怎么办?
            fis = new FileInputStream("aaa.txt");
            fis.read(temp);
            System.out.println(new String(temp));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(fis!=null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fos!=null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        System.out.println("结束");
    }
}

可以理解成把输入数据想象成存储在一个水管当中,
输入流的read()方法从水管中读取一个或多个水滴,
水滴的单位是字节或字符,当使用数组作为读取方法参数时,read(byte []),
这个数组相当于一个竹筒,
使用竹筒去水管中取水,程序重复这个取水的过程,直到read(byte [])返回-1为止。


字符流
针对文本文件,使用字符流来写入和读出字符数据。无需再使用字节流进行包装,
字符流是由字节流包装而来,
它包括:StringReader,StringWriter,BufferedReader,BufferedWriter。
对于前者,他们的使用方法和字节流类似,主要还是read()和wirte(),
而后者多加了一个readLine()方法,用于读取文章类型的文本。

FileReader、FileWriter,节点流,会直接和指定文件相关联。

使用字符流复制文本文件
public class FileWriterDemo1 {
    public static void main(String[] args) {

        FileReader fr = null;
        FileWriter fw = null;

        try {
            char[] temp = new char[1024];
            // 将字符串写入到文件
            fw = new FileWriter("aaa.txt");
            fw.write("我爱你亲爱的姑娘55555");// 直接覆盖,而不是追加。如果想追加怎么办?
            fr = new FileReader("aaa.txt");
            fr.read(temp);
            System.out.println(new String(temp));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
            }
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println("结束");
    }
}

字符流对象在创建时,一般需要提供一个输入or输出流。
例如在创建BufferedReader或者BufferedWriter对象时,
需要提供InputStreamReader或OutputStreamWriter对象。
对于特定支付格式的文本内容,还需要在创建的时候提供字符格式类型作为构造参数。

小例子:字符输入流
File f = new File("D:\\VSS存放目录\\KOOF\\3-开发阶段\\3.3-数据库","koof30.sql");
FileReader fi = new FileReader(f);
BufferedReader in = new BufferedReader(fi);
String s ;
String total = "";
while((s=in.readLine())!=null){
total += s+"\n";
}


处理流(上层)与节点流(底层)
处理流隐藏底层设备上节点流的差异,并对外提供更加方便的输入输出方法,
让我们只关心上层流的操作,有点像应用层的感觉。
可以使用处理流来包装节点流,通过处理流执行输入输出功能,
节点流则负责与底层的IO设别、文件交互。

处理流的构造参数不是一个物理节点,而是已经存在的节点流,而节点流的构造参数都是物理节点。
上面的例子都是使用节点流(FileInputStream、FileOutputStream、FileReader、FileWriter)。
在使用节点流过程中比较繁琐,因此我们可以使用处理流。

public class PrintStreamDemo1 {
    public static void main(String[] args) {
        PrintStream ps = null;
        try {
            ps = new PrintStream(new FileOutputStream("bbb.txt"));
            ps.print("太阳当空照花儿对我笑");
            ps.println("小鸟说早早早你为什么背上小书包");
            ps.println(new PrintStreamDemo1());
            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
        }
    }
}


缓冲流
缓冲流先将数据读写到缓冲区,提高输入输出效率。
BufferedReader可以一次读一行文本,以换行符作为标志,
如果没有遇到换行符则阻塞(此处的阻塞是指数据没读完之前)。

public class BufferedDemo1 {
    public static void main(String[] args) {
        // jdk1.7支持的自动关闭资源的try写法
        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("123.mp3"));
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream("enya恩雅-only time.mp3"));
                BufferedReader br = new BufferedReader(new FileReader("456.txt"));
                BufferedWriter bw = new BufferedWriter(new FileWriter("789.txt"))) {
            
            String tempStr = null;
            while ((tempStr = br.readLine()) != null) {
                // 如遇乱码问题可以:
                // 1.将文本文件保存到UTF-8的编码集
                // 2.使用转换流并设置编码集而并非FileReader文件流
                System.out.println(tempStr);
                //BufferedWriter不会自动换行,可以使用PrintWriter自动换行。
                bw.write(tempStr);
            }

            byte[] temp = new byte[1024];
            while (bis.read(temp) > 0) {
                bos.write(temp);
            }
            System.out.println("结束");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


RandomAccessFile
RandomAccessFile是传统io中功能丰富的文件访问类,同时支持读写、随机访问,
程序可以跳到文件的任意位置进行读写。如果只希望访问文件的部分内容,
而不是从头读到尾,以及追加原有文件,在原有文件后面输出内容,可使用RandomAccessFile。

创建RandomAccessFile对象需要指定一个mode参数。
r:以只读方式打开指定文件,此时想写入或文件不存在则会抛出异常。
rw:以读写方式打开指定文件,如果文件不存在则尝试创建该文件。
rws:以读写方式打开指定文件,对文件内容的每个更新都同步写入到底层存储设备。
public class RandomAccessFileDemo1 {
    public static void main(String[] args) {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile("aaa.txt","rw");
            //获取指针位置,初始位置为0
            System.out.println(raf.getFilePointer());
            raf.write("江山如此多娇,引无数英雄尽折腰。".getBytes());
            //移动指针位置
            raf.seek(6);
            System.out.println(raf.getFilePointer());
            //追加内容
            raf.seek(raf.length());
            raf.write("昔秦皇汉武略输文采,唐宗宋祖稍逊风骚。".getBytes());
            //从头到尾读
            raf.seek(0);
            byte[] temp = new byte[1024];
            while(raf.read(temp)>0) {
                System.out.println(new String(temp));
            }
            System.out.println("结束");
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                raf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

RandomAccessFile将指针移动到文件中间位置后输出,则新输出的内容会覆盖原有内容。
如果想不覆盖,需要先把要插入内容后面的内容读入缓冲区或写入临时文件,
先把要插入内容写入文件后,在将缓冲区或临时文件的内容追加到文件后面。



File
java用File类来表示目录和文件(用isDirectory()或isFile()方法判断),
我们可以通过File类来操作文件与目录。
并且在创建File对象的时候,并不检查该目录或文件是否存在,只作为一种代表。
File能新建、删除、重命名文件和目录,File不能访问文件内容本身,要想访问需要使用IO流。
public class FileDemo1 {
    public static void main(String[] args) throws Exception {
        File f1 = new File("enya恩雅-only time.mp3");//相对路径文件名
        File f2 = new File("D:\\yangWorkSpaces\\yangDemoWorkSpace\\javademo");//绝对路径
        /**
         * 访问文件名&路径相关方法
         */
        System.out.println(f1.getName());//返回文件名
        System.out.println(f2.getName());//返回路径名,只返回最后一级子路径名
        System.out.println(f2.getPath());//返回路径名,在File的构造里怎么写的就返回什么
        System.out.println(f1.getAbsolutePath());//返回绝对路径
        System.out.println(f1.getAbsoluteFile());//返回绝对路径对应的File对象
        System.out.println(f2.getParentFile());//返回父目录对应的File对象
        System.out.println(f2.getParent());//返回父目录对象
        System.out.println(f1.renameTo(new File("abc.mp3")));//重命名文件&目录,成功返回true,否则返回false
        /**
         * 文件检查相关方法
         */
        System.out.println(f1.exists());//判断文件&目录是否存在
        System.out.println(f1.canWrite());//判断文件&目录是否可写
        System.out.println(f1.canRead());//判断文件&目录是否可读
        System.out.println(f1.isFile());//判断文件&目录是否是文件
        System.out.println(f1.isDirectory());//判断文件&目录是否是目录
        System.out.println(f2.isAbsolute());//判断文件&目录是否是绝对路径
        /**
         * 获取常规文件信息
         */
        System.out.println(f1.lastModified());//返回文件&目录的最后修改时间,long类型
        System.out.println(f1.length());//返回文件&目录的长度
        /**
         * 文件操作相关方法
         */
        //当File对象不存在时创建对象,创建成功返回true,否则返回false
        System.out.println(new File("4.mp3").createNewFile());
        System.out.println(new File("4.mp3").delete());//删除文件&目录,删除成功返回true,否则返回false
        //创建临时空文件,在前缀与后缀中间会有一串随机数组成临时文件名
        System.out.println(File.createTempFile("aaa", ".txt"));
        System.out.println(new File("4.mp3").createNewFile());
        //在jvm退出后删除文件
        new File("4.mp3").deleteOnExit();
        //删除文件,成功返回true,失败返回false
        System.out.println(new File("4.mp3").delete());
        /**
         * 目录操作相关方法
         */
        System.out.println(new File("D:\\aaa\\bb").mkdir());//创建目录,成功返回true,错误返回false
        System.out.println(Arrays.asList(new File("D:\\aaa").list()));//返回子文件名&目录名的字符串数组
        System.out.println(Arrays.asList(new File("D:\\aaa").listFiles()));//返回子文件名&目录名的文件数组
        System.out.println(Arrays.asList(File.listRoots()));//列出系统根路径,例如[C:\, D:\]
        /**
         * 文件过滤
         */
        File f = new File("D:\\yangWorkSpaces\\yangDemoWorkSpace\\javase\\src\\main\\java\\com\\mingyisoft\\javase\\thread\\threadsafe\\demo1");
        System.out.println(Arrays.asList(f.list(new FilenameFilter(){
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".java") || new File(name).isDirectory();
            }
        })));
    }
}


序列化
把java对象存储到磁盘或网络传输java对象。序列化机制使得对象可以脱离程序的运行而独立存在。
其他程序或节点获取到这个二进制流,无论是从硬盘上还是网络上,
都可以将二进制流恢复成原来的java对象。

序列化:把对象转换为字节序列的过程。
反序列化:把字节序列恢复为对象的过程。

注意事项:
~方法、静态变量、瞬态变量不会被序列化。
~使用transient关键字修饰实例变量可使该变量无法被序列化。
~反序列化对象时必须有序列化对象的class文件。
~必须按照写入的顺序读取数据。

使用Serializable接口实现序列化机制
必须让类实现java.io.Serializable接口表示该类的对象支持序列化机制,该接口没有需要实现的方法,
他仅仅告诉java底层该类的对象是可以进行序列化的,
并且序列化版本ID由serialVersionUID变量提供。

反序列化无需通过构造器来初始化java对象。
如果使用序列化机制写入多个java对象,使用反序列化恢复java对象时必须按写入的顺序读取,
否则抛异常。
如果被序列化的类有父类,则父类也需要实现Serializable接口,否则父类的属性无法被序列化。
如果被序列化的类内有嵌套类对象,则嵌套类也需要实现Serializable接口,否则抛异常。

java的io提供了ObjectOutputStream,ObjectInputStream用作对象的序列化和反序列化。
public class SerializableDemo1 {
    public static void main(String[] args) {
        Teacher t = new Teacher();
        t.setName("alex");
        t.setAge(18);
        
        Student s1 = new Student();
        s1.setName("张三555");
        s1.setSex('5');
        s1.setYear(5);
        s1.description = "xxx";
        //嵌套对象
        s1.setTeacher(t);
        
        Student s2 = new Student();
        s2.setName("李四555");
        s2.setSex('6');
        s2.setYear(6);
        s2.description = "666";
        //嵌套对象
        s2.setTeacher(t);
        
        try {
            // Student对象序列化过程,使用ObjectOutputStream
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.ser"));
            oos.writeObject(s1);
            oos.writeObject(s2);
            oos.writeObject(t);
            s1.setName("王二麻子");
            oos.writeObject(s1);
            
            Student2 s3 = new Student2();
            s3.setYear(33);
            s3.setGpa(33);
            oos.writeObject(s3);
            
            oos.flush();
            oos.close();

            // Student对象反序列化过程,使用ObjectInputStream
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.ser"));
            Student ss1 = (Student) ois.readObject();
            System.out.println("name = " + ss1.getName());
            System.out.println("sex = " + ss1.getSex());
            System.out.println("year = " + ss1.getYear());
            System.out.println("gpa = " + ss1.getGpa());
            System.out.println("description = " + ss1.description);
            System.out.println("t.name = " + ss1.getTeacher().getName());
            System.out.println("t.age = " + ss1.getTeacher().getAge());
            
            Student ss2 = (Student) ois.readObject();
            System.out.println("name = " + ss2.getName());
            System.out.println("sex = " + ss2.getSex());
            System.out.println("year = " + ss2.getYear());
            System.out.println("gpa = " + ss2.getGpa());
            System.out.println("description = " + ss1.description);
            System.out.println("t.name = " + ss2.getTeacher().getName());
            System.out.println("t.age = " + ss2.getTeacher().getAge());
            
            Teacher tt = (Teacher) ois.readObject();
            System.out.println(tt.getName()+" "+tt.getAge());
            /**
             * 所有采用序列化机制的java对象都有一个序列化编号(不同于序列化版本号),
             * 当程序尝试序列化一个对象时,先检查当前对象是否已经被序列化过,只有从未序列化(本次jvm进程)过的对象,
             * 系统才会将该对象转换成字节序列,如果当前对象已经被序列化过,则自动使用其序列化编号而不是重新
             */
            System.out.println(ss1.getTeacher()==ss2.getTeacher());
            System.out.println(ss1.getTeacher()==tt);
            System.out.println(ss2.getTeacher()==tt);
            
            Student ss3 = (Student) ois.readObject();
            /**
             * 即使属性改变,也不会序列化成新属性。内存里只有1个s1对象,并且s1的属性不能再被改变。
             */
            System.out.println("应该是王二麻子吗->"+ss3.getName());
            /**
             * 自定义序列化,返回集合
             */
            List returnList = (List) ois.readObject();
            for (Object obj : returnList) {
                System.out.println(obj);
            }
            ois.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class Student extends Person implements Serializable{
    private String name;
    /**
     * 不进行序列化transient关键字,transient只能修饰属性
     */
    private transient char sex;
    private int year;
    private double gpa;
    
    private Teacher teacher;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public char getSex() {
        return sex;
    }
    public void setSex(char sex) {
        this.sex = sex;
    }
    public int getYear() {
        return year;
    }
    public void setYear(int year) {
        this.year = year;
    }
    public double getGpa() {
        return gpa;
    }
    public void setGpa(double gpa) {
        this.gpa = gpa;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
}

class Teacher implements Serializable{
    /**
     *
     */
    private static final long serialVersionUID = -1053051757102136827L;
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

class Student2 extends Person implements Serializable{
    private String name;
    /**
     * 不进行序列化transient关键字,transient只能修饰属性
     */
    private transient char sex;
    private int year;
    private double gpa;
    
    private Teacher teacher;
    
    private Object writeReplace() {
        List<Object> returnList = new ArrayList<Object>();
        returnList.add(year);
        returnList.add(gpa);
        return returnList;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public char getSex() {
        return sex;
    }
    public void setSex(char sex) {
        this.sex = sex;
    }
    public int getYear() {
        return year;
    }
    public void setYear(int year) {
        this.year = year;
    }
    public double getGpa() {
        return gpa;
    }
    public void setGpa(double gpa) {
        this.gpa = gpa;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
}


class Person implements Serializable{
    protected String description;
}

使用Serializable接口实现自定义序列化
通过java提供的自定义序列化机制可以让程序员来控制如何序列化&不序列化各个属性。
为了实现自定义序列化机制,类中需要提供若干方法。自定义序列化可以用来做加密处理,
例如被黑客截取到流中的信息也是加密过的。
class Teacher implements Serializable{
    /**
     *
     */
    private static final long serialVersionUID = -1053051757102136827L;
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        System.out.println("自定义写出");
        //反转一下名字
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        System.out.println("自定义读入");
        //反转一下名字
        this.name = (((StringBuffer)in.readObject()).reverse()).toString();
        this.age = in.readInt();
    }
}

class Student2 extends Person implements Serializable{
    private String name;
    /**
     * 不进行序列化transient关键字,transient只能修饰属性
     */
    private transient char sex;
    private int year;
    private double gpa;
    
    private Teacher teacher;
    
    private Object writeReplace() {
        List<Object> returnList = new ArrayList<Object>();
        returnList.add(year);
        returnList.add(gpa);
        return returnList;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public char getSex() {
        return sex;
    }
    public void setSex(char sex) {
        this.sex = sex;
    }
    public int getYear() {
        return year;
    }
    public void setYear(int year) {
        this.year = year;
    }
    public double getGpa() {
        return gpa;
    }
    public void setGpa(double gpa) {
        this.gpa = gpa;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
}

使用Externalizable接口实现序列化机制
对实现了Serializable接口的类,其序列化与反序列化采用默认的序列化方式,
Externalizable接口继承了Serializable接口,实现了Externalizable接口的类,
由程序员完全控制序列化与反序列化行为,更加灵活。
public class ExternalizableDemo1 {
    public static void main(String[] args) {
        try {

            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("xxx.aaa"));
            Car c1 = new Car("alex", 12);
            Car c2 = new Car("jack", 15);

            oos.writeObject(c1);
            oos.writeObject(c2);

            oos.flush();
            oos.close();

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("xxx.aaa"));

            Car c11 = (Car) ois.readObject();
            Car c22 = (Car) ois.readObject();

            System.out.println(c11);
            System.out.println(c22);

            ois.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Car implements Externalizable {
    private String name;
    private Integer age;

    public Car() {

    }

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

    public String toString() {
        return this.name + this.age;
    }

    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.name = (((StringBuffer) in.readObject()).reverse()).toString();
        this.age = in.readInt();
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

序列化版本号
变量serialVersionUID是一个静态的long型的常量,用于在序列化和反序列化过程中,
起到一个辨别类的序列化版本作用,如果两个类名完全相同,
则通过serialVersionUID来判断该类是否符合要求,如果不行则抛异常。

当一个类升级后(对原有类进行修改并编译成class文件),只要serialVersionUID保持不变,
序列化机制会把他们当成同一个序列化版本。

如果不显式定义则jvm会自动计算创建serialVersionUID,
而修改后的类与修改前的类计算结果往往不同,而造成版本不兼容问题。
我们应该显式的定义个serialVersionUID,
即使在某个对象被序列化之后,它所对应的class文件被修改了,
该对象也依然可以被正确的反序列化。

序列化的时机
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。
比如最常见的是tomcat中的Session对象,当有 10万用户并发访问,
就有可能出现10万个Session对象,内存可能吃不消,
于是tomcat就会把一些seesion先序列化到硬盘中,等要用了,
再把保存在硬盘中的对象还原到内存中。

当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,
都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,
才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

通过浏览器发送了一个普通的HTTP请求,该请求携带了一个JSON格式的参数,
在服务端需要将该JSON参数转换为普通的Java对象,这个转换过程称为序列化。

再比如,在服务端获取了数据,此时该数据是一个普通的Java对象,
然后需要将这个Java对象转换为JSON字符串,并将其返回到浏览器中进行渲染,
这个转换过程称为反序列化。



总结
其实发现IO并不只是单纯的保存文件而已,IO在很多场景下都会出现,学好IO后下面就衍生出NIO,
以及后面的AIO。

猜你喜欢

转载自blog.csdn.net/piantoutongyang/article/details/80161323
今日推荐