菜鸟笔记
提升您的技术认知

java之文件与IO流及序列化

文件

文件:内存中存放的数据在计算机关机后就会消失。要长久的保存数据,就要使用硬盘、光盘、U盘等设备。为了便于数据的管理和检索,引入文件的概念。一篇文章、一段视频,一个可执行程序,都可以被保存为一个文件,并赋予一个文件名。操作系统是以文件为单位管理硬盘中的数据

文件夹:成千上万的文件如果不分类放在一起,用户使用起来显然非常不便,因此又引入了树形目录(目录也叫文件夹)的机制,可以把文件放在不同的文件夹中,文件夹中还可以嵌套文件夹,这就便于用户对文件进行管理和使用

注意:一般来说,文件可分为文本文件、视频文件、音频文件、图像文件、可执行文件等多种类别,这是从文件的功能进行分类的。从数据库存储的角度来说,所有的文件本质上都是一样的,都是由一个个字节组成的,归根到底都是0,1比特串不同文件呈现出不同形态(有的是文本,有的是视频)

java操作文件原理

将盘符上的文件等各种信息封装为对象,该对象属于File类的对象,有了这个对象,我们程序便可以直接操作这个对象,通过这个对象获取文件的各种信息,还可以对文件进行创建,删除等。

File类

封装文件为File类对象

语法:File f=new File(文件路径名);

返回值:操作文件的对象

        //将文件封装为一个File类对象
        File f=new File("C:\\All\\test.txt");
        File f1=new File("C:/All/test.txt");
        File f2=new File("C:"+File.separator+"All"+File.separator+"test.txt");

注意:

  • File.separator属性帮我们获取当前操作系统的路径拼接符
  • File类对equal方法进行了重写,比较的是两个文件的内容

常用文件方法

canRead():查看文件是否可读,可读返回true,否则返回false

canWrite():查看文件是否可写,可写返回true,否则返回false

isHidden():查看文件是否隐藏,隐藏返回true,不隐藏返回false

File对象对文件进行操作

        File f=new File("C:\\All\\test.txt");
        System.out.println("文件是否可读"+f.canRead());
        System.out.println("文件是否可写"+f.canWrite());
        System.out.println("文件的名字" + f.getName());
        System.out.println("文件的上级目录" + f.getParent());
        System.out.println("是否是一个目录" + f.isDirectory());
        System.out.println("是否是一个文件" + f.isFile());
        System.out.println("是否隐藏" + f.isHidden());
        System.out.println("获取文件大小" + f.length());
        System.out.println("文件是否存在" + f.exists());
        System.out.println("获取文件相对路径" + f.getPath());
        System.out.println("打印文件对象" + f.toString());
        System.out.println("获取文件绝对路径" + f.getAbsolutePath());
        if(f.exists()){
            f.delete();//如果文件存在,将文件删除
        }else {
            f.createNewFile();//如果文件不存在,则创建
        }

File对象对目录进行操作

        File f=new File("C:\\All\\testDir");
        System.out.println("目录是否可读"+f.canRead());
        System.out.println("目录是否可写"+f.canWrite());
        System.out.println("目录的名字" + f.getName());
        System.out.println("目录的上级目录" + f.getParent());
        System.out.println("是否是一个目录" + f.isDirectory());
        System.out.println("是否是一个文件" + f.isFile());
        System.out.println("是否隐藏" + f.isHidden());
        System.out.println("获取目录大小" + f.length());
        System.out.println("目录是否存在" + f.exists());
        System.out.println("获取目录相对路径" + f.getPath());
        System.out.println("打印文件对象" + f.toString());
        System.out.println("获取目录绝对路径" + f.getAbsolutePath());
        System.out.println("创建单层目录" + f.mkdir());
        File f2=new File("C:\\All\\testDir\\a\\b");
        System.out.println("创建多层目录" + f2.mkdirs());
        System.out.println("删除一层目录" + f2.delete());//删除空文件夹
        String[] list = f.list();
        System.out.println("获取文件名/目录名数组" + Arrays.toString(list));
        System.out.println("获取文件或目录对象数组" + Arrays.toString(f.listFiles()));

IO流

I/O:input/output的缩写。用于处理设备之间的数据传输(类似一根连接着数据源和程序的一根管)

引入IO流原因:File类封装文件/目录的各种信息,对目录/文件进行操作,但我们不可以获取到文件/目录中的内容

IO流的分类

按照方向:输入流、输出流

按照处理数据的单位划分:字节流、字符流

按照功能划分:节点流、处理流

节点流与处理流

  • 节点流:单独一根“管”处理数据对应的那个流
  • 处理流:“管”套“管”,组合使用(构造器嵌套)

处理流特点

  • 处理流一般都套用节点流来使用
  • 处理流相对于是一种高级流,如果关闭高级流(处理流)那么嵌在处理流内部的节点流也随之关闭

字节流与字符流

  • 字节流可以处理二进制文件,而字符流只能处理文本文件
  • 字节流可以分为字节输入流、字节输出流;字符流可以分为字符输入流、字符输出流

抽象类

字节流 字符流
输入流 InputStream OutputStream
输出流 Reader Writer

IO流的体系结构

字节输入流

创建对象

语法:FileInputStream in = new FileInputStream(文件对象);

返回值:字节输入流

注意:因为InputStream为抽象类,不可以直接创建对象,因此需要用子类FileInputStream创建对象 

常用方法

read

语法:字节输入流.read()

返回值:int类型字节编码

语法:字节输入流.read(字节数组)

返回值:读取数组中的有效长度,当读取的字节数组没数据时返回-1 

作用:以字节数组为单位读取字节,并放入该字节数组

注意:

  • 文件是utf8进行储存的,英文字符底层占1字节,中文字符底层占3字节
  • read方法读取结束后返回值为-1
  • read方法底层做了处理,让返回的数据都是正数,就是为了避免如果字节返回的是-1,那么不能区分是文件结束还是读入字节

close

语法:字节输入流.close()

返回值:

作用:关流 

将字节一个个读

        File f = new File("C:\\All\\testDir\\tp.png");
        FileInputStream in = new FileInputStream(f);
        int count=0;
        int read = in.read();
        while (read!=-1){
            count++;
            System.out.println(read);
            read=in.read();
        }
        System.out.println(count);
        in.close();

将字节读入字节数组

        File f = new File("C:\\All\\testDir\\tp.png");
        FileInputStream in = new FileInputStream(f);
        byte[] b=new byte[1024*6];
        int len = in.read(b);
        while (len!=-1){
            for (int i=0;i<len;i++){
                System.out.println(b[i]);
            }
            len=in.read(b);
        }
        in.close();

字节输出流

创建对象

语法:FileOutputStream out = new FileOutputStream(文件对象,append);

返回值:字节输出流

注意:

  • 因为OutputStream为抽象类,不可以直接创建对象,因此需要用子类FileOutputStream创建对象 
  • 后面的append为boolean类型,如果为true则表示追加文件,如果为false(默认)表示不追加,那么之后写入的内容将会把源文件内容覆盖

常用方法

write

语法:字节输出流.write(字节)

返回值:

作用:将字节写入到具体文件中

语法:

  • 字节输出流.write(字节数组,下标1,下标2) 
  • 字节输出流.write(字节数组) 

返回值:

作用:将字节数组或字节数组[下标1,下标2)内的字节写入具体的文件中

注意:如果目标文件不存在的话,那么会自动创建文件

close

语法:字节输出流.close()

返回值:

作用:关流

将字节一个个写入

        File f = new File("C:\\All\\testDir\\add.txt");
        FileOutputStream out = new FileOutputStream(f,false);
        String s="hello world";
        out.write(97);
        out.write(98);
        out.close();

将字节以字节数组方式写入

        File f = new File("C:\\All\\testDir\\add.txt");
        FileOutputStream out = new FileOutputStream(f,false);
        String s="hello world";
        out.write(s.getBytes());
        out.close();

字符输入流

创建对象

语法:FileReader fileReader = new FileReader(文件对象);

返回值:字符输入流

注意:因为Reader为抽象类,不可以直接创建对象,因此需要用子类FileReader创建对象 

常用方法 

read

语法:字符输入流.read()

返回值:读取到字符的ASCII码值

语法:字符输入流.read(字符数组) 

返回值:数组有效长度

注意:

  • read()方法仅读取到指针对应出字符的ASCII码值,读取完该字符后指针向后移动一位当指针所指的地方没有字符仍然读取的话,那么该方法就会返回-1
  • read(字符数组)方法一次性读取字符数组长度个字符并放入该数组(放入数组的顺序,字符从头到尾放入,如果数组之前有数据则被覆盖),读取完指针指向下一个数组长度的字符位置,当读取到的数组没有数据则返回-1

close

语法:字符输入流.close()

返回值:

作用:关流 

将字符一个一个读取

        //创建文件对象
        File file = new File("C:\\All\\testDir\\test.txt");
        //创建字符输入流
        FileReader fileReader = new FileReader(file);
        //读取文件
        int n=fileReader.read();
        while (n!=-1){
            System.out.format("%c", n);
            n=fileReader.read();
        }
        //关流
        fileReader.close();

一次性读取多个字符

        //创建文件对象
        File file = new File("C:\\All\\testDir\\test.txt");
        //创建字符输入流
        FileReader fileReader = new FileReader(file);
        char[] chars=new char[5];
        int len = fileReader.read(chars);
        while (len!=-1){
            String s = new String(chars, 0, len);
            System.out.print(s);
            len=fileReader.read(chars);
        }
        fileReader.close();

字符输出流

创建对象

语法:FileWriter fileWriter = new FileWriter(文件对象,append);

返回值:字符输出流

注意:

  • 因为Writer为抽象类,不可以直接创建对象,因此需要用子类FileWriter创建对象 
  • 后面的append为boolean类型,如果为true则表示追加文件,如果为false(默认)表示不追加,那么之后写入的内容将会把源文件内容覆盖

常用方法

write

语法:字符输出流.write(字符)

返回值:

作用:将字符写入到具体文件中

语法:

  • 字符输出流.write(字符数组,下标1,下标2) 
  • 字符输出流.write(字符数组) 

返回值:

作用:将字符数组或字符数组[下标1,下标2)内的字符写入具体的文件中

注意:如果目标文件不存在的话,那么会自动创建文件

close

语法:字符输出流.close()

返回值:

作用:关流

将字符一个个写入

        File f = new File("C:\\All\\testDir\\add.txt");
        FileWriter fileWriter = new FileWriter(f);
        String str="hello world";
        for (int i=0;i<str.length();i++){
            fileWriter.write(str.charAt(i));
        }
        fileWriter.close();

将字符以数组方式写入

        File f = new File("C:\\All\\testDir\\add.txt");
        FileWriter fileWriter = new FileWriter(f);
        String str="hello world";
        char[] chars = str.toCharArray();
        fileWriter.write(chars);
        fileWriter.close();

缓冲字节流

创建对象

缓冲字节输入流:BufferedInputStream bis = new BufferedInputStream(字节输入流);

缓冲字节输出流:BufferedOutputStream bos = new BufferedOutputStream(字节输出流);

缓冲字节流实现文件复制

        File f1 = new File("C:\\All\\testDir\\test.txt");
        File f2 = new File("C:\\All\\testDir\\add.txt");
        FileInputStream fis = new FileInputStream(f1);
        FileOutputStream fos = new FileOutputStream(f2, false);
        BufferedInputStream bis = new BufferedInputStream(fis);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        byte[] bytes = new byte[1024 * 6];
        int len = bis.read(bytes);
        while (len!=-1){
            bos.write(bytes, 0,len);
            len=bis.read(bytes);
        }
        bos.close();
        bis.close();

缓冲字符流

创建对象

缓冲字符输入流:BufferedReader br = new BufferedReader(字符输入流);

缓冲字符输出流:BufferedWriter bw = new BufferedWriter(字符输出流);

缓冲字符流特有方法

readLine

语法:缓冲字符输入流.readLine()

返回值:读取到的该行的字符串

newLine

语法:缓冲字符输出流.newLine()

返回值:

作用:新起一行

缓冲字符流实现文件复制

        File f1 = new File("C:\\All\\testDir\\test.txt");
        File f2 = new File("C:\\All\\testDir\\add.txt");
        FileReader fr = new FileReader(f1);
        FileWriter fw = new FileWriter(f2, false);
        BufferedReader br = new BufferedReader(fr);
        BufferedWriter bw = new BufferedWriter(fw);
        //这里注意看,是字符缓冲流特有的方法
        String s = br.readLine();//读取文本文件中一行并返回字符串
        while (s!=null){
            bw.write(s);
            //在文本文件中换行
            bw.newLine();
            s=br.readLine();
        }
        bw.close();
        br.close();

缓冲流总结

  • 缓冲流为一种处理流
  • 缓冲流缓冲区大小默认为8192字节(一次可读取8192个字节)
  • 输入或输出流使用的方法,缓冲输入或缓冲输出流也可以使用

转换流

创建对象

字节输入流转为字符输入流:InputStreamReader isr = new InputStreamReader(字节输入流, 编码格式);

注意:此编码格式为字符串类型,格式应和读取的文件格式统一

字符输出流转为字节输出流:OutputStreamWriter gbk = new OutputStreamWriter(字节输出流, 编码格式);

注意:此编码格式应和输出的文件格式统一

转换流的文件复制

        File f1 = new File("C:\\All\\testDir\\test.txt");
        File f2 = new File("C:\\All\\testDir\\add.txt");
        //需要输入字节流接收文件
        FileInputStream fis = new FileInputStream(f1);
        //加入一个转换流,将字节流转字符流
        //将字节转换为字符的时候需要指定一个编码,此编码跟文件本身的编码格式统一
        InputStreamReader isr = new InputStreamReader(fis, "utf-8");
        //输出流
        FileOutputStream fos = new FileOutputStream(f2);
        OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
        //开始动作
        char[] ch = new char[20];
        int len = isr.read(ch);
        while (len!=-1){
            osw.write(ch,0,len);
            len=isr.read(ch);
        }
        osw.close();
        isr.close();

转换流总结

  • 转换流为一种处理流
  • 转换流应指定编码格式,编码格式为字符串类型,如果不写,则会获取你程序默认的编码格式
  • 转换流作用是将字节流和字符流进行转换
  • InputStreamReader是将字节输入流转为字符输入流,OutputStreamWriter是将字符输出流转为字节输出流

System类对IO流的支持

 System.in

        //得到标准输入流——从键盘输入,键盘中可容纳20字符
        InputStream in = System.in;
        byte[] bytes = new byte[20];
        int len = in.read(bytes);//阻塞方法
        for (int i=0;i<len;i++){
            System.out.print((char) bytes[i]);
        }

System.out

        //写到控制台——打印流
        PrintStream out = System.out;
        out.println("hi");//换行
        out.print("hello");
        System.out.println("我是第一天和你认识的");

数据流

作用:用来操作基本数据类型和字符串的

分类

  • DataInputStream(输入数据流):将文件中储存的基本数据类型和字符串写入内存变量中
  • DataOutputStream(输出数据流):将内存中的基本数据类型和字符串变量写入文件中

创建对象

输入数据流:DataInputStream dis = new DataInputStream(字节输入流);

输出数据流:DataOutputStream dos = new DataOutputStream(字节输出流);

常用方法

readUTF

语法:输入数据流.readUTF()

返回值:读到的字符串

readInt

语法:输入数据流.readInt() 

返回值:读取到的int类型数值

readDouble

语法:输入数据流.readDouble()

返回值:读取到的double类型数值

writeUTF

语法:输出数据流.writeUTF(字符串)

返回值:

作用:向特定文件中写入字符串

writeInt

语法:输出数据流.writeInt(int类型的数) 

返回值:

作用:向特定文件中写入int类型

writeDouble

语法:输出数据流.writeDouble(double类型)

返回值:

作用:向特定文件中写入double

经典案例 

        File f = new File("C:\\All\\testDir\\add.txt");
        FileOutputStream fos = new FileOutputStream(f);
        FileInputStream fis = new FileInputStream(f);
        DataOutputStream dos = new DataOutputStream(fos);
        DataInputStream dis = new DataInputStream(fis);
        //将变量写入到文件中
        dos.writeUTF("您好");//此方法为写入字符串
        dos.writeUTF("大家好才是真滴好");
        dos.writeDouble(3.14);
        dos.writeInt(666);
        //从文件中读取
        //注意:读取的顺序要和写入的顺序一致
        System.out.println(dis.readUTF());
        System.out.println(dis.readUTF());//调用2次相同类型需要读取两次
        System.out.println(dis.readDouble());
        System.out.println(dis.readInt());
        dis.close();
        dos.close();

数据流总结

  • 数据流属于一种处理流
  • 使用方法写入文件内容后发现看不懂,正常,因为是给程序看的
  • 读取文件内容要和写入文件内容一致,先写int类型就先读int类型
  • 如果写入相同的类型多个相连,那么读取该类型也应读取多次

序列化和反序列化

对象流:(ObjectInputStream、ObjectOutputStream)用于存储和读取基本数据类型数据和对象的处理流

作用:可以把java对象写入到数据源中,也可以把对象从数据源中还原出来

序列化和反序列化

序列化:用ObjectOutputStream类把内存中的java对象转换成平台无关的二进制数据,从而允许把这种二进制数据持久的保存在磁盘上,或通过网络将这种二进制数据传输到另一个网络节点

反序列化:当其他程序获取了这种二进制数据,那么用ObjectInputStream类就可以将其恢复成原来的java对象

创建对象

序列化:ObjectOutputStream oos = new ObjectOutputStream(字节输出流);

反序列化:ObjectInputStream ois = new ObjectInputStream(字节输入流);

对象流常用的方法

readObject

语法:对象输入流.readObject()

返回值:从文件中读取到的对象 

writeObject

语法:对象输出流.writeObject(对象)

返回值:

作用:将对象写入到特定文件中

操作常用数据 

将数据写到文件中

        File f = new File("C:\\All\\testDir\\add.txt");
        FileOutputStream fos = new FileOutputStream(f);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeUTF("您好,世界");
        oos.writeInt(13);
        oos.close();

从文件中读取数据

        File f = new File("C:\\All\\testDir\\add.txt");
        FileInputStream fis = new FileInputStream(f);
        ObjectInputStream ois = new ObjectInputStream(fis);
        System.out.println(ois.readUTF());
        System.out.println(ois.readInt());
        ois.close();

操作自定义类的对象

使用的Person类

public class Person implements Serializable {
    //序列化版本标识符——格式固定
    private static final long serialVersionUID=12345L;
    private String name;
    private transient int 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;
    }

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

注意:

  • 我们在反序列化时jvm会拿着序列化流中的serialVersionUID与序列化时相应的实体类中的serialVersionUID来比较,如果不一致就无法正常反序列化,进而出现序列化版本不一致异常 
  • 序列化只对属性序列化,不对方法序列化,只不过方法的改变会改变类的serialVersionUID
  • 如果我们在序列化前没有加入serialVersionUID,那么java序列化机制就会根据编译的class自动生成一个,只有同一次编译生成的class才是和文件中的serialVersionUID一样的
  • 被序列化的类的内部所有属性都必须是可序列化的,否则直接报错

将对象序列化

        //序列化
        Person p = new Person("lili",18);
        //对象流
        File f = new File("C:\\All\\testDir\\add.txt");
        FileOutputStream fos = new FileOutputStream(f);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(p);
        oos.close();

将文件中的对象反序列化

        File f = new File("C:\\All\\testDir\\add.txt");
        FileInputStream fis = new FileInputStream(f);
        ObjectInputStream ois = new ObjectInputStream(fis);
        Person p = (Person)ois.readObject();
        System.out.println(p);

对象流总结

  • 一个对象若想被序列化,那么该对象必须实现Serializable接口
  • 被序列化的类的内部所有的属性必须都是序列化的;否则运行直接报错
  • Serializable接口是一个空接口,里面一个方法都没有,作用是用来当作标记,标记着此类可以被序列化
  • static属于类资源不会被序列化到文件中
  • 不需要序列化的数据也可以被修饰成transient(临时的),属性被改关键字修饰后,该属性只在程序运行期间在内存中存在,不会被序列化持久保存
  • 每一个被序列化的文件都有一个唯一的serialVersionUID,如果没有此serialVersionUID编译器会根据类的定义信息计算产生一个,为了后续使用我们应该在序列化前手动添加名字固定为serialVersionUID的变量,具体格式见上
  • 对象流是一种处理流
  • 对象流具有数据流的方法