20.Java中的I/O流

一、IO流,什么是IO?

I:Input,将文件从硬盘输入读入内存。
O:Output,将文件从内存输出写入硬盘。
通过IO可以完成硬盘文件的读和写。

二、IO流的分类

1、按照流的方向进行分类(以内存作为参照物

1)输入流

往内存中去,叫做输入(Input)。或者叫做读(Read)。

2)输出流

从内存中出来,叫做输出(Output)。或者叫做写(Write)。

2、按照读取数据方式不同进行分类:

1)字节流

有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等....
例:假设文件file1.txt,内容为“a中国bc张三fe”,采用字节流的话是这样读的:
        第一次读:一个字节,正好读到'a'
        第二次读:一个字节,正好读到'中'字符的一半。
        第三次读:一个字节,正好读到'中'字符的另外一半。

2)字符流

有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。
例:假设文件file1.txt,内容为“a中国bc张三fe”,采用字符流的话是这样读的:
        第一次读:'a'字符('a'字符在windows系统中占用1个字节。)
        第二次读:'中'字符('中'字符在windows系统中占用2个字节。)

三、I/O流的学习方式

Java中的IO流都已经写好了,我们程序员不需要关心,我们最主要还是掌握在java中已经提供了哪些流,每个流的特点是什么,每个流对象上的常用方法有哪些。

java中所有的流都是在:java.io.*;下。

java中主要还是研究:怎么new流对象。调用流对象的哪个方法是读,哪个方法是写。

四、java IO流这块有四大家族:

四大家族的首领:

java.io.InputStream字节输入流
java.io.OutputStream 字节输出流
java.io.Reader        字符输入流
java.io.Writer        字符输出流

1、四大家族的首领都是抽象类。(abstract class)

2、所有的流都实现了java.io.Closeable接口,都是可关闭的,都有close()方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费(占用)很多资源。养成好习惯,用完流一定要关闭。

3、所有的输出流都实现了java.io.Flushable接口,都是可刷新的,都有flush()方法。
养成一个好习惯,输出流在最终输出之后,一定要记得flush()刷新一下。这个刷新表示将通道/管道当中剩余未输出的数据强行输出完(清空管道!)刷新的作用就是清空管道。
注意:如果没有flush()可能会导致丢失数据。

注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流。

五、java.io包下需要掌握的流有16个

1、文件专属:

java.io.FileInputStream(掌握)
java.io.FileOutputStream(掌握)
java.io.FileReader
java.io.FileWriter

1)java.io.FileInputStream

①构造方法

FileInputStream(String name) ;通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。

注:文件不存在会自动创建名为name的文件。

②常用方法

void close() :关闭此文件输入流并释放与此流有关的所有系统资源。  

int read():一次读取一个字节,返回当前读取到的字符的ascii码,当读到文件流末尾返回-1;

int read(byte[] b) :从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。 

int available():返回流当中剩余的没有读到的字节数量。

long skip(long n):跳过几个字节不读。

③固定写法

FileInputStream fis = null;
try {
    fis = new FileInputStream("文件路径");
    ...
}catch () {
    e.printStackTrace();
}finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

动手实验1:基本的读取文件的例子

public class FileInputStreamTest01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("tempFile.txt");
            int readCount = 0;
            byte[] bytes = new byte[4];
            while((readCount = fis.read(bytes)) != -1){
                System.out.print(new String(bytes, 0, readCount));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

动手实验2:测试available方法初始化byte[]数组,读一次就搞定。

public class FileInputStreamTest02 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("tempFile.txt");
//            System.out.println("剩余未读取的字节数:" + fis.available());
//            System.out.println("当前读到的1个字节的字符的ascii码:" + fis.read());
//            System.out.println("剩余未读取的字节数:" + fis.available());
            int len = fis.available();
            byte[] bytes = new byte[len];
            fis.read(bytes);
            System.out.println(new String(bytes));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

动手实验3:在动手实验2的基础上,测试skip方法跳过3个字节不读。

public class FileInputStreamTest02 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("tempFile.txt");
//            System.out.println("剩余未读取的字节数:" + fis.available());
//            System.out.println("当前读到的1个字节的字符的ascii码:" + fis.read());
//            System.out.println("剩余未读取的字节数:" + fis.available());
            int len = fis.available();
            byte[] bytes = new byte[len];
            fis.skip(3);
            fis.read(bytes);
            System.out.println(new String(bytes));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2)java.io.FileOutputStream

①构造方法

FileOutputStream(String name) ;
执行write()方法时,将文件内容先清空后写入数据。

FileOutputStream(String name, boolean append) ;
执行write()方法时,apend为true时,在文件内容末尾追加写入数据。

②常用方法

void write(byte[] b) ;
将 b.length 个字节从指定 byte 数组写入此文件输出流中。 

void write(byte[] b, int off, int len)
从偏移量 off 开始的指定字节数组中将 len 字节写入此文件输出流。

void close() ;
关闭此文件输出流并释放与此流有关的所有系统资源。 

③固定写法

FileOutputStream fos = null;
try{
    fos = new FileOutputStream(文件路径); //文件路径中的文件不存在时会自动创建
    
    fos.flush(); //写完后一定记得刷新
}catch(){

}finally{
    if(fos != null){
        fos.close();
    }

}

 ④拷贝文件(字节流可拷贝任何类型的文件)

FileInputStream fis = null;
FileOutputStream fos = null;
try {
    fis = new FileInputStream("E:\\CloudMusic\\广东雨神-广东爱情故事.wav");
    fos = new FileOutputStream("G:\\学习\\Java Project\\广东雨神-广东爱情故事.wav");

    // 最核心的:一边读,一边写
    byte[] bytes = new byte[1024 * 1024]; // 1MB(一次最多拷贝1MB。)
    int readCount = 0;
    while((readCount = fis.read(bytes)) != -1) {
        fos.write(bytes, 0, readCount);
    }

    // 刷新,输出流最后要刷新
    fos.flush();        
} catch () {
           
} finally {
    // 分开try,不要一起try。
    // 一起try的时候,其中一个出现异常,可能会影响到另一个流的关闭。
    if (fos != null) {
            fos.close();
    }

    if (fis != null) {
        fis.close();                
    }
}

3)java.io.FileReader

①构造方法

FileReader(String fileName) 
在给定从中读取数据的文件名的情况下创建一个新 FileReader。

②常用方法

int read() :从此输入流中读取一个数据字符。 

int read(char[] c) :从此输入流中将最多 c.length 个字符的数据读入一个 char 数组中。 

动手实验:照葫芦画瓢,根据FileInputStream的写法实例,写FileReader的。

public class FileReaderTest01 {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            fr = new FileReader("outFile.txt");
            char[] chars = new char[4];
            int readCount = 0;
            while((readCount = fr.read(chars)) != -1){
                System.out.print(new String(chars, 0, readCount));
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4)java.io.FileWriter

①构造方法

FileWriter(String fileName) 
文件不存在则创建,若文件存在则调用writer()时会先清空原有数据,再写入数据。 

FileWriter(String fileName, boolean append)  
文件不存在则创建,若文件存在并且append为true时,会在文件末尾追加数据。

②常用方法

void write(int c);
将ascii码c所代表的的一个字符写入文件。

void write(char[] cbuf);
将字符数组cbuf中的所有字符写入文件。

void write(String str);
将字符串str写入文件。

动手实验:照葫芦画瓢,根据FileOutputStream的写法实例,写FileWriter的。

public class FileWriterTest01 {
    public static void main(String[] args) {
        FileWriter fw = null;
        try {
            fw = new FileWriter("outFile2.txt");

            char[] chars = {'我','是','中','国','人'};
            fw.write(chars);
            fw.write(chars, 2, 3);
            
            String str = "我爱中国";
            fw.write(str);
            fw.write(str, 0, 2);

            fw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

④拷贝文件(字符流只能拷贝文本文件)

FileReader fr = null;
FileWriter fw = null;
try {
    fr = new FileReader("E:\\ThreeBody.txt");
    fw = new FileWriter("G:\\学习\\Java Project\\ThreeBoday.txt");

    char[] chars = new char[1024 * 512]; 
    // 在java中1个char类型字符占用两个字节,1024 * 512 * 2就占用1MB

    int num = 0;
    while((num = fr.read(chars)) != -1){
        fw.write(chars, 0, num);
    }
    fw.flush();
} catch () {

} finally{
    if(fr != null){
        fr.close();
    }
    
    if(){
        fw.close();
    }
    
}

2、缓冲流专属:

java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream

当一个流的构造方法中需要一个流的时候,这个被传进来的流叫做:节点流。
外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流。
   

1)java.io.BufferedReader

①构造方法

BufferedReader(Reader in) 
创建一个使用默认大小输入缓冲区的缓冲字符输入流,参数为Reader的子类对象。

②常用方法

String readLine() 
读取一个文本行,当读到没有文本行时,返回null。 

动手实验1:使用BufferedReader读取文本文件

public class BufferedReaderTest01 {
    public static void main(String[] args) {
        FileReader fr = null;
        BufferedReader br = null;
        try {
            fr = new FileReader("tempFile.txt");
            br = new BufferedReader(fr);

            String str = null;
            while((str = br.readLine()) != null){ //readLine()方法每次读取文本文件中的一行,读到文件尾部返回null
                System.out.println(str);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

动手实验2:将一个字节流转换成字符流,使用BufferedReader读取文本文件

public class BufferedReaderTest02 {
    public static void main(String[] args) {
        // 字节流如何转换成字符流
        // InputStreamReader
        FileInputStream fis = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            fis = new FileInputStream("test02/src/com/lzk/io/test01/CopyTest01.java");
            isr = new InputStreamReader(fis);
            br = new BufferedReader(isr);
            String str = null;
            while((str = br.readLine()) != null){
                System.out.println(str);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

动手实验3:将动手实验2中三句new对象的代码合并成一句;

public class BufferedReaderTest03 {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(new FileInputStream("Test02/src/com/lzk/io/test01/CopyTest01.java")));
            String str = null;
            while((str = br.readLine()) != null){
                System.out.println(str);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2)java.io.BufferedWriter

①构造方法

BufferedWriter(Writer out) 
创建一个使用默认大小输出缓冲区的缓冲字符输出流,参数为Writer的子类对象。

②常用方法

void write(String s) 
写入一个字符串。 

动手实验:使用BufferedWriter将若干字符串写入文本文件中

public class BufferedWriterTest01 {
    public static void main(String[] args) {
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter("xxx"));
            bw.write("hello world");
            bw.write("I will be back!");
            bw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

动手实验:使用OutputStreamWriter将字节流转换成字符流

public class BufferedWriterTest02 {
    public static void main(String[] args) {
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("xxx", true)));
            bw.write("my name is Joker!");
            bw.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3、转换流:(将字节流转换成字符流)

java.io.InputStreamReader
java.io.OutputStreamWriter

4、数据流专属:

java.io.DataInputStream
java.io.DataOutputStream

注意:

DataOutputStream可以将数据连同数据的类型一并写入文件。这个文件不是普通文本文档。(这个文件使用记事本打不开。)
DataOutputStream写的文件,只能使用DataInputStream去读。并且读的时候你需要提前知道写入的顺序。
读的顺序需要和写的顺序一致。才可以正常取出数据。

1)java.io.DataOutputStream

(1)构造方法

DataOutputStream(OutputStream out) 
创建一个新的数据输出流,将数据写入指定基础输出流,参数为OutputStream的子类对象。

(2)常用方法

动手实验:利用DataOutputStream把八种数据类型的数据写入文本文件中

public class DataOutputStreamTest01 {
    public static void main(String[] args) {
        DataOutputStream dos = null;
        try {
            dos = new DataOutputStream(new FileOutputStream("yyy"));
            byte b = 1;
            short s = 2;
            int i = 3;
            long l = 4;
            float f = 3.14f;
            double d = 2.45;
            boolean bool = true;
            char c = 'a';
            dos.writeByte(b);
            dos.writeShort(s);
            dos.writeInt(i);
            dos.writeLong(l);
            dos.writeFloat(f);
            dos.writeDouble(d);
            dos.writeBoolean(bool);
            dos.writeChar(c);
            dos.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (dos != null) {
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

动手实验:利用DataInputStream把八种数据类型的数据写入文本文件中

public class DateInputStreamTest01 {
    public static void main(String[] args) {
        DataInputStream dis = null;
        try {
            dis = new DataInputStream(new FileInputStream("YYY"));
            byte b = dis.readByte();
            short s = dis.readShort();
            int i = dis.readInt();
            long l = dis.readLong();
            float f = dis.readFloat();
            double d = dis.readDouble();
            boolean bool = dis.readBoolean();
            char c = dis.readChar();
            System.out.println(b);
            System.out.println(s);
            System.out.println(i);
            System.out.println(l);
            System.out.println(f);
            System.out.println(d);
            System.out.println(b);
            System.out.println(c);


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (dis != null) {
                try {
                    dis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2)java.io.DataInputStream

(1)构造方法

DataInputStream(InputStream in) 
使用指定的底层 InputStream 创建一个 DataInputStream。

(2)常用方法

boolean readBoolean() 
byte readByte() 
char readChar() 
double readDouble() 
float readFloat() 
int readInt() 
long readLong() 
short readShort() 

(3)固定写法

DataInputStream dis = null;
try {
    dis = new DataInputStream(new FileInputStream("info.txt"));
            
} catch () {

} finally{
    if(dis != null){
        dis.close();
    }
}

5、标准输出流:

java.io.PrintWriter
java.io.PrintStream(掌握)

1)java.io.PrintStream(掌握)

标准的字节输出流,默认输出到控制台,可以改变输出方向(如输出到某个文件),不需要手动close()关闭。

(1)构造方法

PrintStream(OutputStream out) 
创建新的打印流,参数为OutputStream的子类对象。

(2)固定写法

// 默认输出到控制台
System.out.println("I will be back");
System.out.println("I am T-800!");

// 改变输出方向,将其输出到文件mm.txt
System.setOut(new PrintStream(new FileOutputStream("mm.txt")));

// 改变后就输出到文件mm.txt了
System.out.println("I will be back!");
System.out.println("I am T-800!");

6、对象专属流:

java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)

1)序列化和反序列化的理解

2)参与序列化的类型必须实现java.io.Serializable接口

否则会报告异常信息:java.io.NotSerializableException,即Student对象不支持序列化!!!!

注意:通过源代码发现,Serializable接口只是一个标志接口:

public interface Serializable {

}

这个接口当中什么代码都没有。但是它起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。

Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到这个接口之后,会为该类自动生成一个序列化版本号。

动手实验:序列化一个java对象

public class SerializableTest01{
    public static void main(String[] args) {
        Student stu = new Student("joker", 18);
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("stu"));

            oos.writeObject(stu);

            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Student  implements Serializable{
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

动手实验:把刚才序列化后java对象反序列化输出

public class SerializableTest02 {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("stu"));
            Object o = ois.readObject();
            System.out.println(o);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3)可以一次序列化多个对象吗?

可以,可以将对象放到集合当中,序列化集合。
提示:参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。

动手实验:序列化多个对象,再反序列化输出

public class SerializableTest03 {
    public static void main(String[] args) {
        List<User> list = new ArrayList<>();
        list.add(new User("joker", 18));
        list.add(new User("danny", 19));
        list.add(new User("tony", 11));
        list.add(new User("superman", 25));
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("users"));
            oos.writeObject(list);

            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class User implements Serializable {
    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
                Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class SerializableTest04 {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("users"));
            Object o = ois.readObject();
            if(o instanceof List){
                List<User> list = (List<User>)o;
                for(User user : list){
                    System.out.println(user);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally{
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

4)可以不让某个属性参与序列化吗?

可以,如果不希望类中某个属性参与序列化,则在该属性前加关键字transient

动手实验:让一个类的某个属性不参与序列化,对该类的对象进行序列化后反序列化输出。

public class SerializableTest03 {
    public static void main(String[] args) {
        List<User> list = new ArrayList<>();
        list.add(new User("joker", 18));
        list.add(new User("danny", 19));
        list.add(new User("tony", 11));
        list.add(new User("superman", 25));
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("users"));
            oos.writeObject(list);

            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class User implements Serializable {
    transient String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
                Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class SerializableTest04 {
    public static void main(String[] args) {
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream("users"));
            Object o = ois.readObject();
            if(o instanceof List){
                List<User> list = (List<User>)o;
                for(User user : list){
                    System.out.println(user);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally{
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

4、序列化版本号有什么用呢?

自动生成的序列化版本号:
十年前编写了一个学生类,编译后生成了字节码,运行时自动生成了一个序列化版本号,硬盘上的学生对象数据时对应该序列化版本号。十年后我们要重新修改这个学生类中的一些属性,编译后生成了新的字节码,运行时自动生成了一个新的序列化版本号,这时反序列化时候就会失败,因为新生成的序列化版本号与老的序列化版本号不对应。会报告以下错误:

java.io.InvalidClassException:
        com.bjpowernode.java.bean.Student;
        local class incompatible:
            stream classdesc serialVersionUID = -684255398724514298(十年后),
            local class serialVersionUID = -3463447116624555755(十年前)

java语言中是采用什么机制来区分类的?
        第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
        第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。

A编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
B编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
不同的人编写了同一个类,但“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了。
对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。(这是自动生成序列化版本号的好处)

自动生成序列化版本号有什么缺陷?
一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类。(这样就不好了!)

最终结论:
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。并且建议将序列化版本号手动的写出来:private static final long serialVersionUID = 1L;
这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private int heigh;
//    private boolean gender;


    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
                Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

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

六、java.io.File类

File类的常用方法。

File(String pathname)
通过将给定的路径名字符串转换为抽象路径名来创建一个新的 File 实例。

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

boolean createNewFile()
当且仅当具有此名称的文件尚不存在时,以原子方式创建一个以此抽象路径名命名的新空文件。

boolean mkdir() 
创建以此抽象路径名命名的目录。

boolean mkdirs()
创建以此抽象路径名命名的目录,包括任何必需但不存在的父目录。

String getParent()
返回此抽象路径名的父目录的路径名字符串,如果此路径名未指定父目录,则返回 null。

File getAbsoluteFile()
返回此抽象路径名的绝对形式。

String getName()
返回此抽象路径名表示的文件或目录的名称。

boolean isDirectory()
测试此抽象路径名表示的文件是否为目录。

boolean isFile()
测试此抽象路径名表示的文件是否为普通文件。

long lastModified()
返回上次修改此抽象路径名表示的文件的时间。

File[] listFiles() 
返回一个抽象路径名数组,表示此抽象路径名表示的目录中的文件。

七、IO+Properties的联合应用

非常好的一个设计理念:以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启。就可以拿到动态的信息。

类似于以上机制的这种文件被称为配置文件。并且当配置文件中的内容格式是:
        key1=value
        key2=value
的时候,我们把这种配置文件叫做属性配置文件。

java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。这种以.properties结尾的文件在java中被称为:属性配置文件。其中Properties是专门存放属性配置文件内容的一个类。

        FileInputStream fis = null;
        try {
            fis = new FileInputStream("userInfo.properties");
            Properties p = new Properties();
            p.load(fis); // load()中的参数除了InputStream的子类对象还可以是Reader的子类对象
            System.out.println(p.getProperty("username"));
            System.out.println(p.getProperty("no"));
            System.out.println(p.getProperty("age"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值