JAVA中File与io流

一、File类型

1.简介

在程序中,我们使用java.io.file这个类来描述或操作磁盘上的一个文件或文件夹;

File这个类,能新建,移动,删除,重命名文件或文件夹,也能获取或修改文件或文件夹的信息(如大小,修改时间等),但File不能访问文件里的内容。如果需要访问文件里的内容,则需要使用输入,输出流。

2.路径

2.1 绝对路径 (Absolute Path)

从磁盘的根目录开始,一层层向内查找,直到找到要找的文件位置。在不同的操作系统中,根目录的表示方式可能略有不同。例如,在UNIX和Linux系统中,根目录用斜杠(/)表示,如/home/user/file.txt;而在Windows系统中,根目录用驱动器名和冒号表示,如C:\Users\User\file.txt

2.2 相对路径 (Relative Path)

相对路径相对的是当前的工作目录或另外一个已知的路径。他描述的是文件或目录与当前位置之间的相对关系。相对路径通常省略了根目录部分,直接从当前目录开始描述路径。

例如,假设当前工作目录是/home/user,要访问该目录下的文件file.txt,可以使用相对路径file.txt,而不需要写出完整的绝对路径。

./   :  表示当前工作目录。  ./可以省略
../  :  表示返回到上一层目录  

示例:

* 1. ./file1.txt           :表示从当前目录中找到file1.txt
* 2. ./doc/file1.txt       :表示从当前目录中找到doc目录,然后在doc目录中找到file1.txt
* 3. ../lucy/file1.txt     :表示从当前目录的上一级目录中找到lucy目录,然后在lucy目录中找到file1.txt
* 4. ../lucy/doc/file1.txt  :表示从当前目录的上一级目录中找到lucy目录,然后在lucy目录中找到doc目录,然后在doc目录中找到file1.txt

2.3两种路径的主要区别

绝对路径相对路径
完整性绝对路径提供了完整的文件或目录路径,从根目录开始,可以唯一地确定位置是相对于当前位置或已知位置的路径,它只提供了与当前位置或已知位置的相对关系。
简洁性从根开始写,路径比较长。相对路径相对于当前位置,通常比绝对路径更简洁,尤其是当文件或目录与当前位置在同一层级或子目录中时。
缺点一旦换一个文件系统,此时这个路径表示 的文件将无法找到只要两者的相对位置发生 了改变,这个文件将无法找到。

3.File的静态属性

关于目录分隔符,在不同的操作系统中,不一样。在windows中,使用 \ 作为目录分隔符,但是,在 ⾮windows的操作系统中,例如: Linux、 Unix,使用 / 作为目录分隔符。

关于路径分隔符,在不同的操作系统中,不一样。在windows中,使用 ; 作为路径分隔符,但是,在 ⾮windows的操作系统中,例如: Linux、 Unix,使用 ;作为路径分隔符。

小贴士:虽然,在windows中,使用 \ 作为目录分隔符,但是大部分情况下,使用 / 也可以。

    public static void main(String[] args) {
        //使用File.separator来表示目录分隔符 输出结果:.\File\Person.txt
        File file = new File("./File"+ File.separator+"Person.txt");
        //判断文件是否存在 如果存在输出结果:true
        System.out.println(file.exists());
        System.out.println(file);

        //使用File.separatorChar来表示目录分隔符 输出结果:.\File\Person.txt
        File file1 = new File("./File"+ File.separatorChar+"Person.txt");
        System.out.println(file1);

        //使用File.pathSeparator来表示路径分隔符 输出结果:.\File;\File\Person.txt
        File file2 = new File("./File"+ File.pathSeparator+"Person.txt");
        System.out.println(file2);

        //使用File.pathSeparatorChar来表示路径分隔符 输出结果:.\File;\File\Person.txt
        File file3 = new File("./File"+ File.pathSeparatorChar+"Person.txt");
        System.out.println(file3);
    }

4.常用构造器

抽象路径应该尽量使用相对路径,并且目录的层级分隔符不要直接写 \ 或 / ,应该使用File.separator这个常量表示,以免在不同系统带来的差异。

File(String pathname)

通过将指定字符串转换成抽象路径来创建一个File实例。

File(File parent,String child)

从父类路径名和子路径名字符串创建新的File实例。

File(String parent,String child)

从父路径名字符串和子路径名字符串创建新的File实例。

5.文件属性的相关方法

        System.out.println("文件名:"+file.getName());
        System.out.println("文件大小:"+file.length());
        System.out.println("文件的绝对路径:"+file.getAbsolutePath());
        System.out.println("文件的父路径:"+file.getParent());
        System.out.println("文件的路径:"+file.getPath());
        System.out.println("是否是一个文件:"+file.isFile());
        System.out.println("是否是一个文件夹:"+file.isDirectory());
        System.out.println("是否可读:"+file.canRead());
        System.out.println("是否可写:"+file.canWrite());
        System.out.println("是否可执行:"+file.canExecute());
//        可执行一定是可读文件,
        System.out.println("是否隐藏:"+file.isHidden());
        System.out.println("最后修改时间:"+file.lastModified());
        System.out.println(" "+file.toPath());

6.文件的查询

import java.io.File;
import java.io.FilenameFilter;

/**
 * 文件查询的相关方法
 */
public class FileDemo {
    public static void main(String[] args) {
        File file = new File("./File");
        File file1 = new File("E:/A");
        
        //1.public String list()获取当前文件夹下的所有文件和文件夹名称
        
        String[] list = file.list();
        for (String i : list) {
            System.out.println(i);
        }

        System.out.println("------------------");
        
        String[] list1 = file1.list();
        for (String string : list1){
            System.out.println(string);
        }

        //2.public File[] listFiles()获取当前文件夹下的所有文件和文件夹的File对象

        File[] files = file.listFiles();
        File[] files1 = file1.listFiles();
        for (File file2 : files) {
            //返回的还是文件名
            System.out.println(file2.getName());
        }
        for (File file3 : files1){
            //返回的是完整路径
            System.out.println(file3);
        }

        System.out.println("------------------");
        //3.public String[] list(FilenameFilter filter)
        //获取当前文件夹下的所有文件和文件夹名称,要求使用过滤器进行过滤

        //要求使用过滤器进行过滤,要求过滤出所有的txt文件
        FilenameFilter list2 = new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.endsWith(".txt");
            }
        };
        String[] s1 = file.list(list2);
        for (String s : s1) {
            System.out.println(s);
        }
        //使用lambda表达式简化匿名内部类
        FilenameFilter list3 = ((fi,s)->s.endsWith(".txt"));
        for (String s : file.list(list3)){
            System.out.println(s);
        }

        System.out.println("-----------------------");
        //使用过滤器,找出所有的文件夹,不是很准确
        FilenameFilter list4 = ((fi,s)->fi.isDirectory());
        for (String s : file1.list(list4)){
            System.out.println(s);
        }

    }
}

7.文件夹的创建及删除

  • boolean createNewFile()

当且仅当具有该名称的文件尚不存在时,创建一个由该抽象路径名命名的新的空文件

  • boolean mkdir()

创建由此抽象路径命名的目录

  • boolean mkdirs()

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

  • boolean delete()

删除由此抽象路径名表示的文件或目录。注意,删除目录时,必须保证此目录下是空目录,如果目录不是空的,需要先删除里面的东西,再删除目录。   还有一点是,删除是永久删除,不会进入回收站

import java.io.File;
/**
 * 文件夹的创建与删除
 */
public class Mkdir_DeleteFileDemo {
    public static void main(String[] args) {
//        //创建文件夹
        File file = new File("./File/test");
//        if(!file.exists()){
//            //创建单层文件夹
//            boolean mkdir = file.mkdir();
//            System.out.println(mkdir);
//        }else System.out.println("文件已存在");
//
//
        File file1 = new File("./File/test_1/test_2/test_3/1.txt");
//        if(!file1.exists()){
//            //创建多层文件夹
//            boolean mkdirs = file1.mkdirs();
//            System.out.println(mkdirs);
//        }else System.out.println("文件已存在");

        //删除文件夹
//        boolean b1 = file.delete();
//        System.out.println(b1);

        //删除多层文件夹时,要使用递归算法,且保证文件夹为空,删除是永久的,不会放入回收站
        //删除文件夹的同时,会删除文件中的所有文件,
        // 递归删除时,只需要将文件路径写到要删除的根目录下即可
        //例如下述代码中,test_1为根目录,file2.delete()会删除test_1及文件夹下的所有文件
        File file2 = new File("./File/test_1");
        delete(file2);

        /**
         * 移动文件夹,并且重命名,该方法若路径完全相同,也会重命名路径最后的文件名
         */
//        if (file1.exists()){
//            file1.renameTo(new File("./File/test_1/3.txt"));
//        }


    }

    private static void createNewFile() {
    }

    public static void delete(File file){
        if (!file.exists()){
            System.out.println("文件不存在");
        }
        if (file.isDirectory()){
            File[] files = file.listFiles();
            for (File file1 : files){
                delete(file1);
            }
        }
        file.delete();
    }
}

递归算法

是一种效率极低的方法; 

如果想删除一个非空目录,则需要使用递归算法来实现

递归的定义

在数学与计算机科学中,递归(Recursion)是指在函数的定义中使用函数自身的方法。实际上,递归,顾名思义,其包含了两个意思:递 和 归,这正是递归思想的精华所在。

递归有两种

直接递归:自己调用自己

间接递归:A调用B,B调用A

如:斐波纳契数列,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……

/**
 * 递归实现斐波那契数列
 */
public class RecursionDemo {
    public static void main(String[] args) {
        long sum = 0;
        int i ;
        for ( i = 1; i < 101; i++){
            System.out.println(fib(i));
            sum += fib(i);
        }
        System.out.println((i-1)+"的斐波那契数列的和为:"+sum);
//        Long fib = fib(25);
//        System.out.println(fib);
    }

    public static long fib(int n) {
        if(n < 3){
            return 1;
        }
        return fib(n-1) + fib(n-2);
    }
}

 二、IO流基础

1.IO流的概念

在我们的编程过程中,除了要自身定义一些数据文件信息外,经常会引入外界的数据,或者将自身的数据发送给外界,例如:在硬盘中读取文件或写入文件,要是用到I/O流。

 IO流:Input Output Stream

Input(输入):指数据流流入到程序中,通常在读取外界数据时使用。

Output(输出):指数据从数据中流出,自爱写出数据到外界时使用。

这里的输入和输出相对的是程序。

一个流就是一个从数据源向目的地的数据序列;

I/O流一旦被创建就会自动打开;

通过调用close方法,可以显示关闭任何一个流,如果流对象不再被引用,JAVA的垃圾回收机制会隐式的关闭它;

不论数据到哪里来,去往哪里,也不论数据本身是何类型,读写数据的方法大体上是一样的。 

1.打开一个输入流

2.读信息

3.关闭流

1.打开一个输出流

2.写信息

3.关闭流

2.IO流的分类 

按照数据的流向分类:

输入流

输出流

按照处理数据的单位分类:

字节流

字符流

按照流的功能分类:

节点流:可以从一个特定的IO设备上读写数据的流,也称之为低级流

处理流:是对一个已经存在的流的链接和封装,通过所封装的流的功能调用实现数据读写操作。通常处理流的构造器上都会有带有一个其他流的参数。也称之为高级流或过滤流。

3.IO流的应用场景

传统的文件File,只能够对文件属性进行操作,例如:创建,移动,删除,属性获取等操作。但是不能获取到文件的内容

如果需要对文件中的内容进行读写操作,需要使用到IO流;

使用场景:对磁盘中或网络中的文件进行读写操作。

4.IO流体系结构

三、字节流

1.字节流简介

InputStream是字节输入流的顶级父类,是抽象类。定义了基本的读取方法。OutputStream是直接输出流的顶级父类,也是抽象类,定义了基本的写出方法。

InputStream定义的方法

int read()

从输入流读出一个字节,把它转换为0-255之间的整数,并返回这一整数,如果返回-1,说明已经读到文件末尾;

int read(byte[] b)

从输入流中读取若干字节,把他们保存到缓存区b中,返回的整数表示读取的字节数。如果遇到输入流的结尾,返回-1;

int read(byte[] b, int  off, int len )

从输入流读取最多 len 字节的数据到一个字节数组中,从指定的下标off 开始存。返回的整数表示实际读取的字节数。如果遇到输入流的结尾,返回-1;

void close() 

关闭输入流

int available()

返回可以从输入流中读取的字节数目

long skip(long n)

从输入流流中跳过参数n指定数目的字节

boolean markSupported()

测试这个输入流是否支持mark 和 reset 方法

void mark(int raedlimit)

标记此输入流中的当前位置

void reset()

将此流重新定位到上次在此输入流上调用mark方法时的位置

OutputStream定义的方法

void write(int b)

向输出流写出一个字节

void writer(byte[] b)

将 b.lnegth 字节从指定的字节数组写入此输出流

void write(byte[] b,int off ,int len)

从指定的字节数组写入len个字节,从偏移off开始输出到此输出流

void close()

关闭输出流

void flush()

OutputStream类本身的flush方法不执行任何操作,他的一些带缓冲区的子类覆盖了flush方法,该方法强制把缓冲区内的数据写到输出流中。

    public static void main(String[] args) {
        FileInputStream demo = null;
        try {
            //创建一个FileInputStream对象,和指定名称的文件相关联
            demo = new FileInputStream("./File/test02.txt");
            // 1.调用read()方法,读取文件内容并转换为0-255之间的整数,返回-1表示读取结束
            /*
                read() 方法读入的一个字节,与整个文件的大小无关。
                read() 方法每次只读取一个字节的数据,并返回一个0到255之间的整数,表示读取到的字节值。
                如果达到文件末尾,则返回-1。所以它每次读入的内容,只取决于文件中的当前位置。
             */
            System.out.println(demo.read());

            //2.创建一个字节数组,用于存储和读取到的内容
            //调用read(byte[] b)方法,读取文件内容到字节数组中,返回-1表示读取结束
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len=demo.read(bytes))!=-1){
                String str = new String(bytes,0,len);
                System.out.println(str);
            }

            //3.获取指定区间的字节数组
            byte[] bytes1 = new byte[10];
            demo.read(bytes1,0,5);
            System.out.println(new String(bytes1));

            //4.调用skip()方法,跳过指定的字节数
            demo.skip(5);
            System.out.println(new String(bytes));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                //关闭流
                demo.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

2.常用字节流

文件流

FileOutputStream :是文件的字节输出流,以字节为单位写出数据到文件。

重写模式构造方法

FileOutputStream(File file)

创建一个向指定file对象表示的文件中写出数据的文件输出流

FileOutputStream(String filename)

创建一个具体指定名称的文件中写出数据的文件输出流

追加

 追加模式构造方法

FileOutputStream(File file, boolean append)

创建一个向指定file 对象表示的文件中写出数据的文件输出流

FileOutputStream(Stirng filename, boolean append)

创建一个向具体指定名称文件中写出数据的文件输出流

==小贴士==:

若指定的文件已经包含内容,那么当使用该流进行写出数据时,会将原有内容全部清除

若指定目录中的指定文件不存在,任何输出流都会自动将该文件创建出来,

        FileOutputStream fileOutputStream = null;
        try {
//            File file = new File("./File/test02.txt");
            fileOutputStream = new FileOutputStream("./File/test02.txt",true);
            //输出内容到文件中,文件中原有的内容会被覆盖,若append为true,则会在文件末尾追加内容
            //如果文件不存在,会自动创建文件
            fileOutputStream.write("你好".getBytes());

            byte[] bytes = "hello world".getBytes();
            fileOutputStream.write(bytes);

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

FileInputStream :是文件的字节输入流,该流以字节为单位从文件中读取数据。

常用构造方法

FileInputStream(File file)

创建一个从指定file对象表示的文件中读取数据文件输入流

FileInputStream(String name)

创建一个指定路径所制定的文件中读取数据的文件输入流

        FileInputStream fileInputStream = null;
        //输入流
        try {
            fileInputStream =
                    new FileInputStream("./File/test02.txt");
            byte[] bytes = new byte[10];
            int len = 0;
            while ((len=fileInputStream.read(bytes))!= -1){
                System.out.println(new String(bytes,0,len));
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                fileInputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

缓冲流

 在向硬件设备做写出操作室,增大写出次数无疑是会降低写出效率,为此,我们可以使用缓冲输出流来一次性批量写出若干数据来减少写出次数来提高写出效率。

BufferedOutputStream

该缓冲输入流内部维护是一个缓冲区,每当我们向该流写数据时,都会先将数据存储缓冲区,当缓冲区已满时,缓冲区会将数据一次性全部写出。

使用该流虽然可以提高写出效率,但是缺乏及时性,此时我们可以使用flush方法,清空缓冲区,强制写出

常用构造器 

BufferedOutputStream(OutputStream out) 

创建一个新的缓冲输出流,一将数据写入指定的底层输入流

BufferOutputStream(OutputStream out , int size)

创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流 

        FileOutputStream fileOutputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        byte[] bytes = "幻音,欢迎,环艺".getBytes();
        try {
            fileOutputStream = new FileOutputStream("./File/test02.txt",true);
            bufferedOutputStream = new BufferedOutputStream(fileOutputStream,1024);
            bufferedOutputStream.write(bytes);
//            Thread.sleep(5000);
            bufferedOutputStream.flush();

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }  finally {
            try {
                bufferedOutputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

BufferedInputStream

  • 读取数据时因为以字节为单位,往往会因为读取次数过于频繁而大大降低读取效率,因此我们可以通过提高一次读取的字节数量来减少读取次数,从而提高读取的效率

  • 该缓冲输入流,内部维护着一个缓冲区。使用该流读取数据时,该流会尽可能多的一次性读取数据存入缓冲区,直到该缓冲区中的数据被全部读取完毕,会再次读取数据存入该缓冲区,反复进行。这样就减少了读取次数,从而提高效率

  • 常用构造器

    • BufferedInputStream(InputStream in)

      以指定节点流in作为参数,创建一个缓冲输入流 

    • BufferedInputStream(InputStream in, int size)

      以指定节点流in和缓冲区大小作为参数,创建一个缓冲输入流。 

        FileInputStream fileInputStream = null;
        BufferedInputStream bufferedInputStream = null;
        byte[] bytes = new byte[10];
        try {
            fileInputStream = new FileInputStream("./File/test02.txt");
            bufferedInputStream = new BufferedInputStream(fileInputStream,1024);
            int len = 0;
            while((len = bufferedInputStream.read(bytes)) != -1){
                System.out.println(new String(bytes,0,len));
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

数据流

DataOutputStream

  • 该流是FilterOutputStream的子类,扩展了一些功能,提供了一些可以直接写出基本数据类型的方法

  • 构造方法

DataOutputStream(OutputStream os)

/**
 * 数据输出流,可以将数据写入到文件中,也可以写入到字节数组中,也可以写入到字符数组中,
 * 可以直接写基本数据类型,也可以直接写引用数据类型,但是引用数据类型必须重写toString方法,
 */
public class DataOutputStreamDemo01 {
    public static void main(String[] args) {
        /*
         *将流的创建写到try模块的小括号中,这样就不用在finally中关闭流了,
         * 流会自动关闭,但是如果在try中创建了流,但是没有在try中使用,那么就会报错,
         */
//        DataOutputStream dos = null;
        try (DataOutputStream dos
                     = new DataOutputStream(
                             new FileOutputStream("./File/Test.txt"))) {
            dos.writeInt(100);//3
            dos.writeDouble(10.0);//4
            dos.writeBoolean(true);//4
            dos.writeChar('a');//5
            dos.writeUTF("你好");//6
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

DataInputStream

  • 该流提供了一些可以直接读取基本数据类型的方法

  • 构造方法

DataInputStream(InputStream is)

/**
 * 流对象和获取流相应的位置顺序有关
 * 输入流对应的位置在输出流一定要遵循,顺序如果有调换,就对应不上了
 * 不能多不能少,对应不到输出的类型有可能会出错
 *
 */
public class DataInputStreamDemo02 {
    public static void main(String[] args) {
        DataInputStream dis = null;
        try{
            dis = new DataInputStream
                    (new FileInputStream("./File/Test.txt"));
            int nums = dis.readInt();
            double v = dis.readDouble();
            boolean b = dis.readBoolean();
            char ch = dis.readChar();
            String s = dis.readUTF();
            System.out.println(nums);
            System.out.println(v);
            System.out.println(b);
            System.out.println(s);
            System.out.println(ch);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                dis.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

对象流

对象是存在于内存中的,有的时候我们需要将对象保存到硬盘中,或者我们要讲对象数据传输到另外一台计算机上等等这些操作。

此时,我们需要将对象转换成一个字节序列,这个过程我们称之为序列化。

相反,讲一个字节序列转换成对应的对象,这个过程我们称之为反序列化。

  • 常用构造器

ObjectOutputStream(OutputStream os)
ObjectInputStream(InputStream is)

 通过ObjectOutputStream流的方法WriteObject(Object o)实现序列化;

/**
 * 序列化:内存中的对象转换为字节数组,这个过程就是序列化,序列化的目的是存储或传输
 * 反序列化:字节数组转换为内存中的对象,这个过程就是反序列化,反序列化的目的是恢复
 *
 * 字节数组就是字节序列的意思
 *
 * 序列化:ObjectOutputStream(ObjectOutputStream out)
 */
public class ObjectOutpetStreamDemo01 {
    public static void main(String[] args) {
        Student student = new Student("lisi", 23, "男");
        Student student1 = new Student("小红", 24, "女");

        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(
                    new BufferedOutputStream(
                            //流的构造器的相对路径,相对的是当前项目的根目录,./表示当前项目的根目录
                            new FileOutputStream("./student.txt",true),4096));
            oos.writeObject(student);
            oos.writeObject(student1);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
}

    /**
     * 自定义的类如果想要序列化(将其对象进行传输),必须实现Serializable接口
     * 接口中没有任何方法,只是一个标记接口,标记该类可以被序列化
     *
     * serailVersionUID:序列化版本号,用于版本控制,如果版本号不一致,则不能进行反序列化
     *  1.序列化时,系统默认给该类自动生成一个序列化版本号,
     *  当反序列化时就会比较该类中的序列化版本号和对象(反序列化)版本号是否一致
     *  2.如果版本号一致,则可以进行反序列化
     *  3.如果版本号不一致,则不能进行反序列化,会抛出InvalidClassException异常,版本号不兼容异常
     *  4.系统自动生成的版本号可能会发生变化,为避免上述情况,可以手动指定版本号,
     *    这样序列化和反序列化用的是同一个版本号,就不会出现不兼容问题
     */
    static class Student implements Comparable<Student>, Serializable {
//        public static final long serialVersionUID = 6296251054416L;

        private String name;
        private int age;
        private String gender;

        public Student() {
        }

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

        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 String getGender() {
            return gender;
        }

        public void setGender(String gender) {
            this.gender = gender;
        }

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

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

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

        @Override
        public int compareTo(Student o) {
            return o.age - this.age;
        }
    }
}

 通过ObjectInputStream流的方法ReadObject()实现对象反序列化。

/*
    反序列化:ObjectInputStream(ObjectInputStream in)

    知识扩展:
       1.序列化时,程序每执行一次都会在开始的位置先写一个流头。如果是追加模式,
       那么这个文件中可能会有多个流头,
       2.反序列化时,默认只读取一次流头,如果想读取多个流头,需要使用peekStream()方法
       剩下的内容是每个对象的信息,所以文件中如果有多个流头时,可能会将除了第一个流头外,
       剩下的流头会被当成对象信息解析,所以在反序列化时,需要注意文件中流头的数量
       会报

       多个对象的时候
 */
public class ObjectInpetStreamDemo02 {
    public static void main(String[] args) {
        //创建一个反序列化的流对象
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(
                    new BufferedInputStream(
                            new FileInputStream("./student.txt"),4096)
            );
            while (true){
                Object obj = ois.readObject();
                System.out.println(obj.toString());
            }
        } catch (IOException e){
            //e.printStackTrace();
            //到达文件末尾,就会抛出EOFException异常
            System.out.println("读取完毕......");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
  • Serializable接口

    ObjectOutputStream在对对象进行序列化时有一个要求,就是需要对象所属的类型必须实现Serializable接口。此接口内什么都没有,只是作为可序列化的标识。

  • serailVersionUID

    通常实现序列化接口的类需要提供一个常量serialVersionUID,表明该类的版本。若不显示的声明,在该对象序列化时也会根据当前类的各个方面计算该类的默认serialVersionUID。但是不同平台的编译器实现有所不同,所以想要跨平台,都应该显示的声明版本号。

    如果类的对象序列化存入硬盘上面,之后随着需求的变化更改了类的属性(增加或减少或改名等),那么当反序列化时,就会出现异常(InvalidClassException),这样就造成了不兼容性的问题。

    但当serialVersionUID相同时,就会将不一样的field以type的预设值反序列化,避免不兼容问题。

  • transient关键字

    对象在序列化后得到的字节序列往往比较大,有时候我们在对一个对象序列化操作时,可以忽略某些不必要的属性,从而对序列化后得到的字节序列“瘦身”。

    使用transient关键字修饰的属性在序列化时其值将被忽略。

当流的输入信息改变之后读取的还是原来的数据是为什么?

         这是因为ObjectInputStream对象一旦被创建,它将从文件的起始位置开始读取数据。即使在读取过程中文件的内容发生了改变,ObjectInputStream对象也不会检测到这些变化,它仍然会按照创建时的位置和顺序读取数据。
如果需要读取文件的最新内容,你需要关闭当前的ObjectInputStream对象,重新创建一个新的ObjectInputStream对象,并指定文件的新内容。        

四、字符流

1.字符流简介

Reader

int read() 读一个字符  
int read(char[] cbuf) 将字符读入数组。  
abstract int read(char[] cbuf, int off, int len) 将字符读入数组的一部分。

Writer

void write(char[] cbuf) 将一个字符数组写出去。  
abstract void write(char[] cbuf, int off, int len) 写入字符数组的一部分。  
void write(int c) 写一个字符  
void write(String str) 写一个字符串  
void write(String str, int off, int len) 写一个字符串的一部分。

2.转换流

OutputStreamWriter: 使用该流可以设置字符集,并按照指定的字符集将字符转换成字节后通过该流写出

    public static void main(String[] args) {
        OutputStreamWriter osw = null;
        try {
            osw = new OutputStreamWriter(
                    new FileOutputStream("./File/test01.txt",true));
            Scanner scanner = new Scanner(System.in);
            while(true){
                System.out.println("  请输入内容:  ");
                System.out.println("  输入over结束  ");
                String s = scanner.nextLine();
                if("over".equals(s)){
                    break;
                }
                osw.write(s);
                osw.write("\r\n");
                osw.flush();
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

InputStreamReader :使用该流可以设置字符集,并按照指定的字符集从流中按照该编码将字节数据转换为字符并读取

    public static void main(String[ ] args){
        InputStreamReader isr = null;
        try {
            isr = new InputStreamReader(
                    new FileInputStream("./File/test01.txt")
            );
            int len = 0;
            char[] chars = new char[1024];
            while((len = isr.read(chars)) != -1){
                String string = new String(chars,0,len);
                System.out.println(string);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

3.缓冲字符流

printWriter            具有自动行刷新的缓冲字符输出流,其提供了比较丰富的构造方法,通常比BufferedWriter更实用。

BufferReader        是缓冲字符输入流,内部提供缓冲区,提高读取效率

    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(
                    new InputStreamReader(
                        new FileInputStream("./File/test01.txt")));

            char[] chars = new char[1024];
            int len = 0;
            while((len=br.read(chars))!=-1){
                String s = new String(chars, 0,len);
                System.out.println(s);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

4.文件字符流

FileWriter         相当于OutputStreamWriter和FileOutputStream合起来的功能

    public static void main(String[] args) {
        FileWriter  fw = null;
        try {
            fw = new FileWriter("./File/test02.txt",true);
//            char[] ch = {'q', 'w', 'e', 'r', 't'};
//            fw.write(ch);
//            //下面代码起换行作用
//            fw.append("\n");

            Scanner scanner = new Scanner(System.in);
            while (true){
                System.out.println("请输入信息,输入over结束");
                String string = scanner.nextLine();
//                fw.write(string);
                fw.append(string);
                fw.append("\n");
                if (string.equals("over")){
                    break;
                }
                //调用flush()方法进行手动刷新
                fw.flush();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

FileReader        相当于InputStreamReader和FileInputStream合起来的功能,但是不能设置字符集

    public static void main(String[] args) {
        FileReader fr = null;
        try {
            //创建FileReader对象,用于读取文件
//            FileReader 相当于InputStreamReader和FileInputStream的结合体
            fr = new FileReader("./File/test02.txt");
            //定义一个数组,用于存储读取到的字符
            char[] ch = new char[1024];

            //定义一个变量,用于记录读取到的字符数
            int len = 0;
            //循环读取文件中的字符
            while ((len = fr.read(ch))!= -1) {
                //将字符数组转换为字符串,并输出
                System.out.println(new String(ch, 0, len));

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

5.其他流

System.out

是PrintStream类型,代表标准输出流,默认数据输出是控制台;

System.in

是InputStream类型,代表标准输入流,默认数据输入时键盘;

System.err

是PrintStream类型,代表标准错误输出流,默认数据输出是控制台。

    public static void main(String[] args) {
        try {
            SystemOut();
            SystemIn();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static void SystemIn() throws Exception {
        InputStream in = System.in;
        //指定要输入的文件
        FileInputStream fis = new FileInputStream("./File/test02.txt");
        System.setIn(fis);
        //使用扫描类
        Scanner sc = new Scanner(System.in);
        //不断检查sc(即Scanner对象)是否有下一行可读。只要有下一行,循环就会继续执行。
        while (sc.hasNextLine()){
//            这在处理日志文件、配置文件或其他文本文件时非常有用,可以方便地查看文件的内容。
            String line = sc.nextLine();
            System.out.println(line);
        }
    }

    public static void SystemOut() throws Exception {
        /**
         * System.out:标准输出流对象
         * 调用的是PrintStream的println方法
         * 修改out输出流的,输出位置
         * 1.将默认的目的地,临时保存起来
         */
        System.out.println("hello world");
        PrintStream ps = System.out;
        PrintStream pw = new PrintStream("./File/test02.txt");
        System.setOut(pw);
        System.out.println("hello          world");
        System.setOut(ps);
    }
  • 9
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值