java11 - File与IO流

1. File

1.1 File简介

        在程序中,我们使用java.io.File这个类来描述和操作磁盘上的一个文件或文件夹(目录)

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

1.2 绝对/相对路径

路径:用来描述一个文件或者文件夹所存在的位置,可以分为 绝对路径相对路径

1)绝对路径 

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

2)  相对路径

        是相对于当前工作目录或另一个已知位置的路径。它描述的是文件或目录与当前位置之间的相对关系。相对路径通常省略了根目录部分,直接从当前目录开始描述路径。例如,假设当前工作目录是/home/user,要访问该目录下的文件file.txt,可以使用相对路径file.txt,而不需要写出完整的绝对路径。

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

3) 两者的主要区别

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

 总之,绝对路径提供了完整的路径信息,而相对路径描述的是与当前位置的相对关系。选择使用哪种路径形式取决于具体的需求和使用场景。

1.3File的静态属性

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

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

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

而File的静态常量会获取程序所在的系统环境中的具体分隔符。这样就省去了程序员来区分不同操作系统的麻烦。

// 目录分隔符,用来分隔两个目录,返回一个字符串
System.out.println(File.separator);
// 目录分隔符,用来分隔两个目录,返回一个字符
System.out.println(File.separatorChar);
// 路径分隔符,用来分隔两个路径,返回一个字符串
System.out.println(File.pathSeparator);
// 路径分隔符,用来分隔两个路径,返回一个字符
System.out.println(File.pathSeparatorChar);

1.4 常用构造器

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

File的常用构造器
        1.如果我们想在程序中来描述或操作磁盘上的一个文件或者文件夹,可以使用File类型
        2.File类型位于java.io包下。
        3.File可以新建,删除,修改,重命名文件或者文件夹,也可以对文件或者文件夹的属性进行访问,但是不能对文件的内容进行访问(读和写)
        4.常用构造器
                File(String pathname)
                File(String parent,String child)
                File(File parent,String child)

        构造器只负责用来接收程序员传入的路径,并不会校验路径的真实性

测试代码:

public class FileDemo02 {
    public static void main(String[] args) {
        //使用File类型来描述一个文件夹 F盘下的dir1文件夹
//        File file = new File("D:/dir1");
        File file = new File("D:"+File.separator+"dir1");
        boolean result = file.exists();
        if (result)
            System.out.println("所描述的文件或者文件夹存在");
        else
            System.out.println("所描述的文件或者文件夹不存在");
        /**
         * 调用构造器File(String parent ,String child)
         */
        File file1 = new File("D:/develop","sddd");
        boolean result1 = file1.exists();
        System.out.println(result1);
        /**
         * 调用构造器File(File parent,String child)
         */
        File parent = new File("D:/develop");
        File file2 = new File(parent,"aa");
    }


}

 1.5 文件属性的相关方法

如下代码所示:

public class FileDemo03 {
    public static void main(String[] args) {
        File file = new File("F:/develop/lr.jpg");
        System.out.println("文件名称:"+file.getName());
        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.isHidden());
        System.out.println("是否可读:" + file.canRead());
        System.out.println("是否可写:" + file.canWrite());//可写,一定是可读文件
        System.out.println("是否可执行:" + file.canExecute());// 可执行,一定是可读文件
        System.out.println("最后一次,修改时间:"+file.lastModified());
        System.out.println("文件大小:"+file.length());
    }
}

 1.6 文件的查询、检索。

String[] list():  返回指定文件夹里的子文件或者是子目录的名字    字符串类型
File[] listFiles():   返回指定文件夹里的子文件或者是子目录的File类型

 代码如下:

public class FileDemo04 {
    public static void main(String[] args) {
        File file = new File("F:/develop");
        String[] lists =  file.list();
        for(String name : lists){
            System.out.println(name);
        }
        System.out.println("--------------------------------------------");
        File[] files = file.listFiles();
        for(File f : files){
            System.out.println(f.getName()+"   "+f.getAbsolutePath());
        }
        System.out.println("-------------------------------------------");

        //定义一个过滤器对象:匿名内部类的方式
        FilenameFilter filter = new FilenameFilter() {
            @Override
           //重写accept方法:编写过滤逻辑,那么指的就是文件夹里的每一个文件或者文件夹
//            dir就是要操作的那个文件列表(即文件夹)
            public boolean accept(File dir, String name) {
                return name.endsWith("e");
            }
        };

        lists=file.list(filter);
        for(String name : lists){
            System.out.println(name);
        }
        System.out.println("-----------------------------------------------");
        files = file.listFiles(filter);
        for(File f : files){
            System.out.println(f.getName()+"   "+f.getAbsolutePath());
        }
    }
}

1.7  文件的新建与删除

1)新建

boolean mkdir():创建文件夹,创建由此抽象路径名命名的目录

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

代码:

public class FileDemo05 {
    public static void main(String[] args) throws IOException {
        //使用File描述磁盘上的一个文件夹
        File dir = new File("F:/develop/dir1");
        if(!dir.exists()) {
            dir.mkdir();
        }
        //此时,dir1一定存在,在dir1里创建一个叫file1.txt文件
        File file = new File(dir, "file1.txt");
        if(!file.exists()) {
            file.createNewFile();
        }
    }
}

 2) 多层级创建

文件夹的多层级创建

  • mkdir(): 只能创建出一个空文件夹,并不能多层级创建。
  • mkdirs(): 可以多层级创建
  • renameTo(): 重命名,也可以进行文件的移动

 代码如下:

public class FileDemo06 {
    public static void main(String[] args) {
        File file = new File("F:/develop/image/img/i");
        if (!file.exists()) {
            file.mkdirs();
        }else{
            file.delete();
        }

        /**
         * 重命名
         */

        File file1 = new File("F:/develop/image/img/i");
        if (file1.exists()) {
            file1.renameTo(new File("F:/develop/image/img/img"));
        }
        /**
         * 使用renameTo(File file)方法来达到移动文件的作用
         *
         */
        File file2 = new File("F:/develop/image/img/img/file1.txt");
        File file3 = new File("F:/develop/image/img/file2.txt");
        if (file2.exists()) {
            file2.renameTo(file3);
        }


    }

}

3) 删除(递归删除)

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

File的删除功能
        1.   delete方法只能删除文件或者是空文件夹
        2.   delete方法如果想要删除非空文件夹,需要把里面的删除干净,然后才能被删除。

测试代码:

import java.io.File;

/**
 * File的删除功能
 * 1.   delete方法只能删除文件或者是空文件夹
 * 2.   delete方法如果想要删除非空文件夹,需要把里面的删除干净,然后才能被删除。
 */


public class FileDemo07 {
    public static void main(String[] args) {
//        File file = new File("F:/develop/image/img/file2.txt");
//        if (file.exists()) {
//            boolean flag = file.delete();
//            System.out.println(flag);
//        }
        File file = new File("F:/develop/image/img");
//        deleteFile(file);
    }

    /**
     * 使用递归算法,删除指定的文件夹
     * @param file //可能是文件夹,也可能是文件
     */
    public static void deleteFile(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files!=null ) {
                for (int i = 0; i < files.length; i++) {
                    deleteFile(files[i]);
                }
            }
        }
        file.delete();
    }
    public static void delete(File file) {
        if(!file.exists()){
            throw new RuntimeException("文件不存在。");
        }

        if (file.isDirectory()) {
            //获取文件夹里的内容,可能是文件夹,或者是文件
            File[] files = file.listFiles();
            //遍历每一个文件夹或者文件
            for(File f:files){
                //f可能是文件夹,也可能是文件
                delete(f);
            }
        }
        //if执行完,如果是文件夹,里面的内容清空了,然后自己就是空文件夹了,也能被删除了
        //如果不是文件夹,是文件也能删除。
        file.delete();
    }
}

 2. IO流

2.1 IO流的概念

        我们在编程时,除了自身定义一些数据信息外,经常还会引入外界的数据,或者是将自身的数据发送给外界。如:编程时,想读取硬盘上的某一个文件,又或者想将程序中的某些数据写入到硬盘上的一个文件里。这时,我们就要使用I/O流。

        这个“外部”范围很广,包括诸如键盘、显示器、文件、磁盘、网络、另外一个程序等。“数据”也可以是任何类型的,例如一个对象、串字符、图像、声音等。

IO流: Input Output Stream。

  • Input(输入):是指数据流入程序,通常我们读取外界数据时使用,所以输入是用来读取数据的。

  • Output(输出):是指数据从程序流出,通常我们需要写出数据到外界时使用,所以输出是用来写出数据的

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

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

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

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

1.打开一个输入流

2.读信息

3.关闭流

1.打开一个输出流

2.写信息

3.关闭流

2.2 IO流的分类

按照数据的流向分类:

  • 输入流

  • 输出流

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

  • 字节流

  • 字符流

按照流的功能分类,

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

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

 2.3 IO流的应用场景

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

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

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

2.4 IO流体系结构

 

 3. 字节流

3.1 字节流的简介

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

InputStream定义的方法

方法:

int  read()
从输入流中读取一个字节,把它转换为0-255之间的整数,并返回这一整数,如果返回-1,说明读到文件末尾(EOF)

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 readLimit)
标记此输入流中的当前位置。 

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

 OutputStream定义的方法

 void  write(int b)
向输出流写出一个字节

void  write(byte[] b)
将 b.length字节从指定的字节数组写入此输出流

void  write(byte[] b,int off, int len)
从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流

void  close()
关闭输出流

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

 3.2 常用的字节流

1)文件流

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

文件输出流

1.是OutputStream的一个子类型

2. 是一个低级流(节点流),用于连接文件和程序的。

3. 常用的构造器:

        FileOutputStream(File file) :会将原有的内容覆盖掉

        FileOutputStream(String pathname) 会将原有的内容覆盖掉

        FileOutputStream(File file,boolean append):有追加效果

        FileOutputStream(File file,boolean append): 有追加效果

4. 输出流,不会帮助我们创建不存在的文件夹,如果文件不存在,会帮助我们创建出来。(也就是只会创建文件)

 测试代码如如下:

public class FileOutputStreamDemo01 {
    public static void main(String[] args) {
        //向D:的file1.txt写东西
        FileOutputStream fos = null;
        try {
//            fos = new FileOutputStream("D:/file1.txt");
            fos = new FileOutputStream("D:/file1.txt",true);
            //写一个A
            fos.write(65);
            //将B~Z写入文件中
            for (int i = 66; i <91 ; i++) {
                fos.write(i);//注意,每写一次,都会与磁盘交互一次,次数越多,性能越差。
            }
            for (char i = 'a'; i <='z'; i++) {
                fos.write(i);
            }
            // 将"hello world"写入到文件中
            byte[] bytes =  "hello world".getBytes("UTF-8");
            fos.write(bytes,0,7);

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

}

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

FileInputStream(文件输入流):

1. 是InputStream抽象类的子类型。

2. 是一个低级流,用于连接文件和程序

3. 常用构造器

        FileInputStream(String pathname)

        FileInputStream(File file)

 测试代码如下:

public class FileInputStreamDemo01 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("D:/file1.txt");
            //读取一个字节
            int ch = fis.read();
            System.out.println((char)ch);
            //读取后面十个字节
            for (int i = 0; i < 10; i++) {
                ch = fis.read();//每读取一次,都会与磁盘交互一次。次数越多,性能越差
                System.out.println((char)ch);
            }
            //提前创建一个10个长度的byte数组
            byte[] bs =new byte[10];
            //将数据读到数组中了
            int length = -1;
            while ((length=fis.read(bs))!=-1) {
                String str = new String(bs,0,length);
                System.out.println(str);
                System.out.println(length);

            }
            System.out.println(length);


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

2)缓冲流

缓冲流设计的目的

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

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

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

字节缓冲输出流: BufferedOutputStream

1.内部维护了一个字节数组作为缓冲区(缓存): 默认值是8KB

2.当我们写数据时,是先写到缓存中的,并不是直接写到磁盘上。当缓存满的情况下,再一次性将数据写到磁盘,从而减少了与磁盘的交互次数,提高了写出效率。

3. 最后一次写入到缓存后,有可能没有装满,我们可以调用flush方法,将其强制写到磁盘上。

4. 构造器

        BufferedOutputStream(OutputStream os)

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

        BufferedOutputStream(OutputStream os,int size): size用于自定义缓冲区的大小

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

 测试代码如下:

public class BufferedOutputStreamDemo01 {
    public static void main(String[] args) {
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        try{
            fos = new FileOutputStream("D:/file2.txt",true);
            bos = new BufferedOutputStream(fos);

            //将A写入缓冲区
            bos.write('A');
            //将"hello world"写入到缓冲区
            byte[] bytes = "hello world".getBytes("UTF-8");
//            bos.write(bytes);
            bos.write(bytes,0,bytes.length);
            bos.flush();//即使没有关闭流,也会强制冲刷缓冲区。
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                /**
                 * 多个流的关闭顺序: 应该先关高级流,然后再关低级流。
                 *
                 * 不过,可以直接关闭高级流。
                 */
                //fos.close();    
                bos.close();
                

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

        注意,在关闭时,应该先关高级流,再关低级流,或者直接关闭高级流。如果创建流对象时在try的小括号里面创建,可以不关流。

BufferedInputStream

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

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

 字节缓冲输入流:

1. 是一个高级流,内部维护着一个缓冲区,默认8KB.

2. 在读取文件中的数据时,一次性尽可能的读取缓冲区大小的字节的东西

3. read方法从缓冲区获取数据。

        当缓冲区的数据全部获取完,会再次从磁盘上读取数据存储到缓冲区。

4.常用构造器

         BufferedInputStream(InputStream is)

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

         BufferedInputStream(InputStream is , int size)//缓冲区大小

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

代码演示:

public class BufferedInputStreamDemo01 {
    public static void main(String[] args) {
        BufferedInputStream bis = null;
        try {
            bis = new BufferedInputStream(
                    new FileInputStream("D:/file1.txt"),10);
            //读取一个字节
            int ch = bis.read();
            System.out.println((char)ch);
            //一次性读取多些字节
            byte[] bytes = new byte[10];
            int len = -1;
            //读到文件末尾,返回-1;
            while ((len=bis.read(bytes))!=-1) {
                String str = new String(bytes,0,len);
                System.out.println(str);
            }

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

3)数据流

DataOutputStream

        数据输出流。是字节过滤输出流(FilterOutputStream)的子类型,其扩展了一些功能,可以直接书写基本数据类型和字符串类型。

public class DataOutputDemo01 {
    public static void main(String[] args) {
        //将流的创建写到try模块小括号中,当流不再使用时,会自动关闭流对象。JDK1.8之后加的
        try(DataOutputStream dos =
                    new DataOutputStream(
                            new FileOutputStream("D:/file3.txt",true))){
            dos.writeInt(10);//4个字节
            dos.writeDouble(3.14);//8个字节
            dos.writeBoolean(true);//1个字节
            dos.writeUTF("");//注意,每写一次UTF的字符串,都会多2个字节长度。
            dos.flush();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

DataInputStream

数据输入流。是字节过滤输出流(FilterInputStream)的子类型,该流提供了一些可以直接读取基本数据类型的方法

构造方法:

        DataInputStream(InputStream is)

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class DateInputStreamDemo02 {
    public static void main(String[] args) {
        try(DataInputStream dis =new DataInputStream(new FileInputStream("D:/file3.txt"))){
            int nums = dis.readInt();
            double v = dis.readDouble();
            boolean b = dis.readBoolean();
            String s = dis.readUTF();
            System.out.println(nums);
            System.out.println(v);
            System.out.println(b);
            System.out.println(s);

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

4)对象流

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

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

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

 我们可以通过ObjectOutputStream流的方法WriteObject(Object o)实现对象序列化,通过ObjectInputStream流的方法readObject()实现对象反序列化。

ObjectOutputStream(序列化):

序列化: 内存中的对象转成字节数组,这个过程就是序列化
          目的:存储或传输。
反序列化: 字节数组转成内存中的对象,这个过程就是反序列化

小贴士: 字节数组就是字节序列的意思。

序列化构造器:

        ObjectOutputStream(OutputStream os)

 测试代码:

import java.io.*;
import java.util.Objects;

public class ObjectOutputStreamDemo01 {
    public static void main(String[] args) {
        //创建一个学生
        Student s1 = new Student("小明",18,"男");
        Student s2 = new Student("小红",18,"女");
        //使用对象输出流,将学生写出到文件中
        ObjectOutputStream oos = null;
        try{
            oos = new ObjectOutputStream(
                    new BufferedOutputStream(
                            //流的构造器的相对路径,相对的是Project文件夹。./表示项目文件夹。
                            new FileOutputStream("./student.s",true),4096));
            oos.writeObject(s1);
            oos.writeObject(s2);
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            try {
                oos.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

class Student implements Serializable,Comparable<Student> {
    public static  final long serialVersionUID = 210000000L;

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

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

    public int getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        Student student = (Student) object;
        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 this.age - o.age;
    }
}

注意:
    自定义的类型,如果想要将其对象进行存储或者传输,那么这个类必须要实现一个接口,
这个接口就是Serializable
    该接口里面什么都没有,就是一个规范,一个标识。
    serailVersionUID: 序列化版本号。
    1.  在序列化时,系统会默认给该类提供一个版本号。当反序列化时,就会检查该类的版本号与对象的版本号是否一致
        如果一致,就可以反序列化,反之,反序列化失败,报异常: 版本号不兼容

        注意: 系统提供的版本号,可能会发生变化,不同的时间点可能不同。
    2. 为了避免上述情况发生,程序员应该主动为该类型提供一个固定的版本号。
        这样序列化时和反序列化时用的都是同一个版本号,就不会报版本不兼容的异常了。
 

 ObjectInputStream

反序列化的类型:
        ObjectInputStream(InputStream is)

    知识扩展:
        1.序列化时,程序每执行一次,都会先写一个流头,如果是追加模式的,那么这个文件中可能会有多个流头
        2.反序列化时,也就是读取文件时,默认是只读取一次流头,剩下的内容全部认为是每个对象的信息。
            所以文件中如果有多个流头,可能会将除了第一个流头外,剩下的流头字节序列当成对象信息进行解析。
            会报异常:java.io.StreamCorruptedException: invalid type code : AC

 异常如下:

 代码演示:

import java.io.*;

public class ObjectInputStreamDemo02 {
    public static void main(String[] args) {
        try(ObjectInputStream ois = new ObjectInputStream(
                                        new BufferedInputStream(
                                            new FileInputStream("./student.s"),4096)
        )){
            Object obj =ois.readObject();
            System.out.println(obj);
            obj =ois.readObject();
            System.out.println(obj);
            obj =ois.readObject();
            System.out.println(obj);
        }catch (IOException|ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

transient关键字演示

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

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

package com.se.day07.aObjectStream;

import java.io.*;

public class ObjectOutputStreamDemo02 {
    public static void main(String[] args) {
        //创建一个Teacher对象
        Teacher teacher = new Teacher("张山",18,"sd","aaa");
        Teacher teacher1 = new Teacher("张1",18,"cc","sss");

        serialObject(teacher,teacher1);
//        serialObject(teacher1);
        Teacher t1 = deSerialObject();
//        Teacher t2 = deSerialObject();
        System.out.println(t1);
//        System.out.println(t2);
    }
    /**
     * 序列化对象
     */
    public static void serialObject(Object obj,Object obj2){
        ObjectOutputStream oos = null;
        try{
            oos = new ObjectOutputStream(
                    new BufferedOutputStream(
                        new FileOutputStream("./teacher.s",true),4096));
            oos.writeObject(obj);
            oos.writeObject(obj2);
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 反序列化对象
     */
    public static Teacher deSerialObject(){
        Teacher t = null;

        ObjectInput ois =null;
        try{
            ois = new ObjectInputStream(
                    new BufferedInputStream(
                        new FileInputStream("./teacher.s"),8192));
            Object obj = ois.readObject();
            if(obj instanceof Teacher){
                t = (Teacher) obj;
            }
//            t = (Teacher) ois.readObject();
//            System.out.println(t);
//            t = (Teacher) ois.readObject();
//            System.out.println(t);


        }catch(Exception e){
            e.printStackTrace();
        }finally{
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return t;
    }
}



class Teacher implements Serializable,Comparable<Teacher> {
    public static final int serialVersionUID = 1;

    private String name;
    private int age;
    private String address;
    private transient String remark;

    public Teacher(){}

    public Teacher(String name, int age, String address, String remark) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.remark = remark;
    }

    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 getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

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

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

注意:
    transient关键字:
    1.用于修饰成员变量
    2. 当序列化时,某一个属性信息不需要保存或者传输,也就是不重要的意思。那么就可以使用transient进行修饰。

 输出结果:

因为remark被transient关键字修饰,所以。传输时,并没有对其赋值。

 4.字符流

4.1 字符流简介

        Reader是字符输入流的父类,抽象类;Writer是字符输出流的父类,抽象类。字符流是以字符(char)为单位读写数据的,一次处理一个unicode字符流的底层仍然是基本的字节流

1)Reader的常用方法

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

 2)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) 写一个字符串的一部分。  

4.2 转换流

1)OutputStreamWriter

  字符输出流:
    1.抽象父类Writer
    2.书写单位,以一个char为单位
    3.底层依然是字节流
    4.基本的字符流,我们称之为转换流,因为涉及到字符与字节之间的转换所使用的编码集。
    5.在编写代码时的字符集,默认使用的是UTF-8 字母是一个字节,汉字是三个字节
    6.构造器:

        OutputStreamWriter(OutputStream os)
        OutputStreamWriter(OutputStream os, String charsetName)

代码演示: 

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class _01WriterDemo {
    public static void main(String[] args) {
        OutputStreamWriter osw = null;
        try {
//            osw = new OutputStreamWriter(
//                    new FileOutputStream("./test07/char.txt"));
            osw = new OutputStreamWriter(
                    //常用的字符集 UTF-8, ISO-8859-1  GBK  GB2312 UNICODE
                    //ISO-8859-1 : HTTP通信时使用的。
                    new FileOutputStream("./test07/char.txt"), "GBK");
            //写一个字符出去
            osw.write('中');
            //写一个字符数组出去
            char[] chs ={'国','欢','萤','你'};
            osw.write(chs);
            osw.write(chs,chs.length-3,3);
            osw.write("\n会应吗?会赢的吗?");
            osw.flush();


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

    }
}

2)InputStreamReader

 字符转换输入流:
        InputSteamReader(InputStream os)
        InputSteamReader(InputStream os,String charsetname)

代码演示:

public class _02ReaderDemo {
    public static void main(String[] args) {
        InputStreamReader isr =null;

        try {
            isr= new InputStreamReader(
                    new FileInputStream("./test07/char.txt"),"GB2312");
            for (int i=0;i<6;i++) {
                char ch = (char)isr.read();
                System.out.println("ch:"+ch);
            }
            //继续读取10个字符
            char[] chs = new char[10];
            int len = -6;
            while((len=isr.read(chs))!=-1) {
                System.out.println("chs:"+ new String(chs,0,len));
                System.out.println("len:"+len);
            }


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


    }
}

4.3缓冲字符流

1)PrintWriter

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

缓冲字符输出流
        1.一般使用PrintWriter这个子类,不使用BufferedWriter。
        2.PrintWriter这个类的构造器比较丰富。
                printWriter(File file)
                printWriter(String pathname)
                printWriter(OutputStream os)
                printWriter(OutputStream os,boolean autoFlush)
                printWriter(Writer writer)
                printWriter(Writer writer,boolean autoFlush)
        3.内部也是维护了一个字符缓冲区
        写的时候,先往字符缓存区中写,当缓存区满了,会一次性写出到文件中。
        不过,当使用了自动行刷新功能,每写一行,然后在行尾添加个换行符,就会强制写出去。
        4.除了write方法,
                还提供了丰富的print和println的各种重载方法。其中println方法在于输出目标数据后自动输出一个系统支持的换行符。若该流设置了自动行刷新,那么每当通过println方法写出的内容都会被实际写出,而不是进行缓存。 方法如下:

void println()  通过写入行分隔符字符串来终止当前行。  

void println(boolean x) 打印一个布尔值,然后终止该行。  

void println(char x) 打印一个字符,然后终止该行。  

void println(char[] x) 打印字符数组,然后终止行。  

void println(double x) 打印双精度浮点数,然后终止行。  

void println(float x) 打印一个浮点数,然后终止该行。  

void println(int x) 打印一个整数,然后终止该行。  

void println(long x) 打印一个长整型,然后终止行。  

void println(Object x) 打印一个对象,然后终止该行。  

void println(String x) 打印一个字符串,然后终止行。

 代码演示:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class _01PrintWriterDemo {
    public static void main(String[] args) {
        try(PrintWriter pw = new PrintWriter(
                new OutputStreamWriter(
                    new FileOutputStream("./test07/char.txt"),"GBK"),true)){
            //写出一个字符数组
            char[] charArr = "你好中国".toCharArray();
            pw.write(charArr);
            //调用print方法
            pw.print("床前明月光");//print不带ln的方法,没有自动行刷的效果
            pw.flush();
            //线程睡眠功能
//            Thread.sleep(10000);
            //调用println的方法,写出去"疑是地上霜"。
            //当构造器中指定了行刷新功能,也就是设置成true。此时ln的方法才会在行末添加换行符,并强制从缓存里面写出。
            pw.println("疑是地上霜");
            pw.println("举头望明月");
//            Thread.sleep(30000);

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

2)BufferedReader

BufferedReader是缓冲字符输入流,内部提供了缓冲区,可以提高读取效率。

字符缓冲流
        1.内部也提供了一个缓存区,尽可能一次性读取,存满缓存区。
        2.程序员调用读取方法,从缓存区中获取数据。当获取完数据后,缓存区再次从文件中尽可能一次性读满数据。
        3.构造器
                bufferedReader(Reader reader)
        4.常用方法
                String readLine(): 读取一行数据,只读取到换行符,但是返回的数据不含换行符。 

 代码演示:

import java.io.*;

public class _02BufferedReadDemo {
    public static void main(String[] args) {
        try(BufferedReader br = new BufferedReader(
                new InputStreamReader(
                        new FileInputStream("test07/char.txt"),"GBK")) ) {
            char ch =(char)br.read();
            System.out.println("ch: "+ch);

            //调用读取一行的方法
            String s ="";
            while((s = br.readLine()) != null) {
                System.out.println("s: "+s);
            }



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

4.4 文件字符流

相当于OutputStreamWriter和FileOutputStream合起来的功能。在创建的时候简单化了。

1)FileWriter 

FileWriter: 文件输出流
        1.相当于OutputStreamWriter和FileOutputStream合起来的功能,
                但是不能指定字符集。
        2.构造器:
                FileWriter(File file)
                FileWriter(File file ,boolean append)
                FileWriter(String pathname)
                FileWriter(String pathname,boolean append)
        3.常用方法: 就是转换流的方法

 代码演示:

public class _03FileWriterDemo {
    public static void main(String[] args) {
        try(FileWriter fw = new FileWriter(new File("./test07/char.txt"))){
            //写出
            fw.write("会赢的吗");
            fw.write("会赢的啊");
//            append方法:追加到文字的后面。
            fw.append("背景");
            fw.append("伤害");

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

2)FileReader

 文件输入流:FileReader
    1. 相当于InputStreamReader和FileInputStream合起来的功能,但是不能指定字符集
    2.构造器:
        FileReader(File file)
        FileReader(String pathname)
    3.常用方法,也适合转换流的方法一致。

 代码演示:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class _04FileReaderDemo {
    public static void main(String[] args) {
        try(FileReader fr = new FileReader("./test07/char.txt")){
            int ch = fr.read();
            System.out.println((char)ch);

            char[] chs = new char[100];
            int len = -1;
            while((len = fr.read(chs))!=-1){
                String str = new String(chs,0,len);
                System.out.println(str);
            }


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

5. 系统类

System: 系统类,里面封装了一些本地方法和静态属性
    静态属性:
        PrintStream out : 标准输出流,默认目的地是控制台console
        PrintStream err : 标准错误输出流,默认目的地是控制台console
        InputStream in : 标准输入流,默认数据源是控制台console

测试代码

import java.io.*;
import java.util.Scanner;

public class _01SystemDemo {
    public static void main(String[] args) throws Exception {
        testSystemIn();

    }

    public static void testSystemIn() throws Exception{
        //将system.in临时保存到一个变量中
        InputStream is = System.in;
        FileInputStream fis = new FileInputStream("./test07/char.txt");
        //将数据源从控制台改为指定文件
        System.setIn(fis);
        //使用扫描类
        Scanner sc = new Scanner(System.in);
        //与迭代器类似,先问是否有下一行。
        while(sc.hasNext()){
            String s = sc.nextLine();
            System.out.println(s);
        }


    }

    /**
     * 测试system.out
     * @throws Exception
     */
    public static void aaa() throws Exception {
        /*
        System.out : 返回一个标准输出流对象
        调用println方法: 即调用的是PrintStream的方法,将数据打印到控制台。
      */
        System.out.println("Hello World");
        /*
            修改out这个流的目的地
            1.将默认的目的地,临时保存起来。

         */
        PrintStream ps =System.out;
        //使用IO流,来重新定位
        PrintStream pw = new PrintStream("test07/char.txt");
        //将上述的流对象,赋值给System的out属性
        System.setOut(pw);
        System.out.println("你好!世界,哒哒哒哒哒哒哒哒。");
        System.setOut(ps);
        System.out.println("asdad");
    }
}

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值