Java学习之文件io流篇

Java学习之文件io流篇

0x00 前言

在平时的一些开发中,普遍都会让脚本运行的时候,去存储一些脚本运行结果的数据,例如开发一个爬虫,这时候我们爬取下来的内容,就需要存储到本地,那么这时候就会用到一些操作文件的类。

0x01 File 类

File类主要用于文件和目录创建、查找、删除等操作的。

先来查看他的构造方法

public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。
public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。

常用方法:

public String getAbsolutePath() :返回此File的绝对路径名字符串。
public String getPath() :将此File转换为路径名字符串。
public String getName() :返回由此File表示的文件或目录的名称。
public long length() :返回由此File表示的文件的长度。

代码实例:

public static void main(String[] args) {
        String Pathname = "a.txt";
        File abc = new File(Pathname);
        System.out.println(abc.getAbsolutePath());
        String Pathname1 = "a\\a.txt";
        File abc1 = new File(Pathname1);
        System.out.println(abc1.getAbsolutePath());
        System.out.println(abc1.getPath());
        System.out.println(abc1.length());
    }

判断方法:

public boolean exists() :此File表示的文件或目录是否实际存在。
public boolean isDirectory() :此File表示的是否为目录。
public boolean isFile() :此File表示的是否为文件。
    public static void main(String[] args) {
        String Pathname = "a.txt";
        File abc = new File(Pathname);
        boolean a = abc.exists();
        System.out.println(a);
        System.out.println(abc.isFile());
        System.out.println(abc.isDirectory());
    }

增删功能方法

public boolean createNewFile() :当前仅当具有该名称的文件尚不存在时,创建一个新的空文件。
public boolean delete() :删除由此File表示的文件或目录。
public boolean mkdir() :创建由此File表示的目录。
public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。
public static void main(String[] args) throws IOException {
        String Pathname = "a.txt";
        File abc = new File(Pathname);
        boolean file = abc.createNewFile();
        System.out.println(file+"\n"+abc.getAbsolutePath());


       File abc1 = new File("abc");
       abc1.mkdir();

       File abc2 = new File("abc\\abc");
       abc2.mkdirs();
    }

如果是对目录进行删除,删除的目录必须为空才能进行删除。

目录遍历

在file里面给我们提供了,可以直接获取file目录下面所有子文件或目录。

public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

代码实例:

public static void main(String[] args) throws IOException {
        String Pathname = "../";
        File abc = new File(Pathname);
        String[] name = abc.list();
        for (String s : name) {
            System.out.println(s);
        }
    }

使用list方法获取所有文件,然后使用增强for循环进行遍历。

0x02 IO流概述

IO流概述

java里面的io流指的是对一些文件内容做一个输入输出的作用。也就是input和output,对文件进行读取和输入数据的操作。

input:把数据从其他设备上读取到内存的流

output:把数据从内存写出到其他设备的流。

字节流

在计算机里面所有的文本数据包括图片、文本、视频这些在存储的时候,都是以二进制的形式进行村粗的。字节流是以一个字节为单位,读写数据的流。

字符流

以一个字符为单位,读写数据的流

0x03 字节流输出流

OutputStream抽象类是字节输入流的超类,他定义了几个共性的方法。

public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输
出到此输出流。
public abstract void write(int b) :将指定的字节输出流。

在操作完成后,必须使用close方法将资源释放。

FileOutputStream 类

FileOutputStream类是文件输出流,用于将数据些出到文件当中。

查看构造方法:

public FileOutputStream(File file) :创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name) : 创建文件输出流以指定的名称写入文件。

如果创建一个io流的对象,必须传入文件的路径,,如果没有该文件就会创建该文件,如果有就会清空原本有的数据。

代码:

public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileOutputStream FileoutDate = new FileOutputStream(file);
        FileoutDate.write(97);
        FileoutDate.close();
    }

这里写进入一个97,但是打开文件会发现写进入的变成了a,这是因为我们这里是以字节进行写入的,,而97的ascii转换为字符后,就是a这个字符。

这里还可以指定写出的数据长度。

public class FileOutput {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileOutputStream FileoutDate = new FileOutputStream(file);
        byte[] b = "abce3".getBytes();
        FileoutDate.write(b,2,2);
        FileoutDate.close();
    }
    }

这里要先获取字符的字节类型数据,使用write写入,从第二位开始索引,写入2个字节。

在程序开发中,有些数据可能没法一次执行获取所有结果,这时候我们如果以上面的方式来循环写入运行结果的话,每次循环就都会被清空一次,只获得最后一次的执行结果。
那么这时候我们就可以使用到追加,把它追加进入,而不是直接覆盖重写。

public FileOutputStream(File file, boolean append) : 创建文件输出流以写入由指定的 File对象表示的
文件。
public FileOutputStream(String name, boolean append) : 创建文件输出流以指定的名称写入文件。

代码:

 public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileOutputStream FileoutDate = new FileOutputStream(file,true);
        byte[] b = "abce3".getBytes();
        FileoutDate.write(b,2,2);
        FileoutDate.close();
    }

这几行代码和前面的相同,只是在FileOutputStream 构造方法里面传入一个ture,表示使用追加模式,该模式默认为false。

0x04 字节输入流

InputStream抽象类是字节输入流的超类。可以读取字节数据到内存中。

共性方法:

public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read() : 从输入流读取数据的下一个字节。
public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

FileInputStream 类

FileInputStream是文件输入流,从文件中读取字节到内存中。

构造方法:

FileInputStream(File file) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系
统中的 File对象 file命名。
FileInputStream(String name) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件
系统中的路径名 name命名。

代码:

 public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileInputStream fileinputdata = new FileInputStream(file);
        int read = fileinputdata.read();
        System.out.println((char) read);
        read = fileinputdata.read();
        System.out.println((char) read);
        read = fileinputdata.read();
        System.out.println(read);
        fileinputdata.close();
    }

使用read方法读取完后,地址会往后推一位,知道读取到没有,会返回-1。

以上的方法都是读取单个字节,我们可以定义一个字节类型的数值,然后让他每次读取我们指定的长度。

代码:

    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");
        FileInputStream fileinputdata = new FileInputStream(file);
        int len;
        byte[] b = new byte[2];
        while ((len=fileinputdata.read(b))!=-1){
            System.out.println(new String(b,0,len));
        }
        fileinputdata.close();
    }

这里定义了b变量用来接收每次读取的数据产的长度,然后定义一个len变量,接收每次读取的数据,这里可以直接把赋值放在循环条件里面,如果赋值的变量不等于-1,就一直循环,知道读取到-1,停止循环,前面说到如果没有数据读取会输出返回一个-1,结束循环。

0x05 字符流

在字节读写的时候,一些中文字符读写可能会显示乱码。因为一个中文字符可能占用多个字节。所以在一些读写的是字符数据的话,可以使用字符流来处理该数据。

字符输入流

Reader抽象类是表示用于读取字符流的超类,可以读取字符信息到内存中。

共性方法:

public void close() :关闭此流并释放与此流相关联的任何系统资源。
public int read() : 从输入流读取一个字符。
public int read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。

FileReader 类

构造方法:

FileReader(File file) : 创建一个新的 FileReader ,给定要读取的File对象。
FileReader(String fileName) : 创建一个新的 FileReader ,给定要读取的文件的名称。

创建一个流对象,必须传入一个文件路径。

构造方法定义代码:

 public static void main(String[] args) throws IOException {
    File file = new File("a.txt");
        FileReader filereader = new FileReader(file);
    }
或者:
public static void main(String[] args) throws IOException {
        
        FileReader filereader = new FileReader("a.txt");
    }

读取单个字符数据:

public static void main(String[] args) throws IOException {

        FileReader filereader = new FileReader("a.txt");
        int b;
        while ((b = filereader.read())!=-1){
            System.out.println((char)b);
        }
    }

读取多个字符数据:

 public static void main(String[] args) throws IOException {

        FileReader filereader = new FileReader("a.txt");
        int b;
        char[] buf = new char[2];
        while ((b = filereader.read(buf))!=-1){
            System.out.println(new String(buf));
        }
    }

字符输出流

Writer抽象类是字符输出流的超累,将知道的字符信息写出到指定地方。

共性方法:

void write(int c) 写入单个字符。
void write(char[] cbuf) 写入字符数组。
abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len
写的字符个数。
void write(String str) 写入字符串。
void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个
数。
void flush() 刷新该流的缓冲。
void close() 关闭此流,但要先刷新它。

FileWriter 类

FileWriter类是写出字符到文件中的一个类,,构造时候使用默认的字符编码和默认的字节缓冲区。

构造方法:

FileWriter(File file) : 创建一个新的 FileWriter,给定要读取的File对象。
FileWriter(String fileName) : 创建一个新的 FileWriter,给定要读取的文件的名称。

写出数据代码:

public static void main(String[] args) throws IOException {

        FileWriter filewriter = new FileWriter("a.txt");
        filewriter.write("二狗");
        filewriter.write(97);
        filewriter.write(156);
        filewriter.flush();
        filewriter.close();
    }

这里的代码,如果不写flush方法,也不写close是没法写入数据的。
来看看这2个方法的区别:

flush :刷新缓冲区,流对象可以继续使用。
close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

0x06 缓存流

字节流和字符流每次读写都会访问硬盘,当读写频率增加其访问效率不高
而使用缓存流读取时会将大量数据先读取到缓存中,以后每次读取先访问缓存,直到缓存读取完毕再到硬盘读取,缓存流写入数据也是一样,先将数据写入到缓存区,直到缓存区达到一定的量,才把这些数据一起写道硬盘中去,这样减少了IO操作。

字节缓冲流: BufferedInputStream , BufferedOutputStream
字符缓冲流: BufferedReader , BufferedWriter

字节缓存输出流

构造方法:

public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
public BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。

代码:

    public static void main(String[] args) throws IOException {

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A.txt"));

        int b ;
        while ((b = bis.read())!=-1){
            bos.write(b);
        }
    }

上面的代码使用了字节缓存流的方式来进行了一个文件的复制粘贴,如果是使用字节输出流的方式是很慢的。而使用字节缓存流的方式读写的速度会很快。

如果我们想让读写更快的话,可以定义一个每次读写的长度,让他每次读写固定到的长度。

    public static void main(String[] args) throws IOException {

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A.txt"));

        int b ;
        byte[] bytes = new byte[8*1024];
        while ((b = bis.read(bytes))!=-1){
            bos.write(bytes,0,b);
        }
    }

每次读写8*1024个字节,减少文件读写的时间。

字符缓存流

构造方法:

public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
public BufferedWriter(Writer out) : 创建一个新的缓冲输出流。

代码:

public static void main(String[] args) throws IOException {
        BufferedReader bis = new BufferedReader(new FileReader("a.txt"));
            String b = null ;
            while ((b = bis.readLine())!=null){
                System.out.println(b);
            }
            bis.close();
    }

使用readline读取当行数据。

0x07 序列化与反序列化

概述

java提供了一种对象序列化的机制,用一个字节序列表示一个对象,该字节包含对象的数据、对象的类型、对象的存储属性。字节序列写出到文件后,相当于可以持久报错了一个对象信息,这过程叫做序列化。

而反过来,将存储在文件的字节序列从文件中读取出来,重构对象,重新用来创建对象,这步骤叫做反序列化。

总结:简单来讲就是将一个对象,写入文件当中,而反序列化就是将写入文件的对象,读取出来。

ObjectOutputStream 类

ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。也就是对对象进行序列化的一个类。

构造方法:

public ObjectOutputStream(OutputStream out)

注意事项:

1.使用该类必须实现Serializable 接口,Serializable是一个标记接口,不实现此接口的类将不会时任何状态序列化或者是放序列化,会抛出NotSerializableException的异常。

2.该类的所有的属性必须时可以序列化的,如果有一个属性不需要序列化的,测该属性必须标明时瞬态的,使用transient修饰符。

代码:

创建一个类:

public class Method implements Serializable {
    public String name;
    public String address;
    public transient int age;
    public void method(){
        System.out.println(name +address);
    }

}

main方法:

public static void main(String[] args) {
        Method e = new Method();
        e.name = "zhangsan";
        e.address = "beiqinglu";
        e.age = 20;

        try {
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.txt"));
            out.writeObject(e);
            out.close();
            System.out.println("ok");

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }finally {
        }
    }

查看a.txt文件可以看到显示的字符都是乱码,这是因为将对象序列化了,写进入的是一个对象,而不是一些字符。

ObjectInputStream类

ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象

构造方法:

public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。

反序列化方法1

如果我们需要对序列化的对象,进行反序列化,将他从文件读取出来还原成对象那么我们这里调用ObjectOutputStream的readobject方法,读取一个对象。

public final Object readObject () : 读取一个对象。

代码:

 public static void main(String[] args) {
        Method e = null;
        try {
            FileInputStream fis = new FileInputStream("a.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
             e = (Method) ois.readObject();
            ois.close();
            fis.close();
        } catch (IOException | ClassNotFoundException ioException) {
            ioException.printStackTrace();
        }
        System.out.println("name="+e.name);
        System.out.println("address ="+e.address);
        System.out.println("age="+e.age);
    }

反序列化方法2

当jvm方序列化对象的时候,能找到class属性,,但是class文件在序列化之后修改了,那么反序列化操作肯定是失败的,会抛出InvalidClassException。

抛出InvalidClassException的一次原因有3种:

1.该类的序列版本号与从流中读取的类描述符的版本号不匹配

2.该类包含未知数据类型

3.该类没有可访问的无参数构造方法

Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序
列化的对象和对应类是否版本匹配。

public class Employee implements java.io.Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
public int eid;
public void addressCheck() {
System.out.println("Address check : " + name + " ‐‐ " + address);
}
}

0x08 总结


File类:用于创建删除文件与文件夹。

FileoutputStream:字节输出流,用于将字节写出到文件中。

FileinputStream:字节输出流,用于读取文件中的字节。

FileReader类是表示用于读取字符流,可以读取字符信息到内存中。

FileWriter类是写出字符到文件中的一个类,,构造时候使用默认的字符编码和默认的字节缓冲区。

字节缓冲流: BufferedInputStream , BufferedOutputStream

字符缓冲流: BufferedReader , BufferedWriter

0x09 结尾

这篇文章的内容比较多,在后面也写了个简单的小总结,这篇也写了我2天左右。后面去深入研究一下历史爆出的一些反序列化漏洞。

posted @ 2020-08-08 18:50  nice_0e3  阅读(470)  评论(0编辑  收藏  举报