> 文档中心 > 【Java基础系列教程】第二十一章 Java IO流详解_节点流、缓冲流、转换流、对象序列化、数据流等

【Java基础系列教程】第二十一章 Java IO流详解_节点流、缓冲流、转换流、对象序列化、数据流等


一、File文件操作

1.1 File类概述

        存储在变量、数组和对象中的数据是暂时的,当程序终止时他们就会丢失。为了能够永久的保存程序中创建的数据,需要将他们存储到硬盘或光盘的文件中(持久化数据:永久的保存程序中创建的数据 - 文件、数据库)。
    
        这些文件可以移动、传送、亦可以被其他程序使用。将数据存储在文件中,所以我们需要学习一个和文件有密切关系的类,叫做File类,File类声明在java.io包下。File类:文件和文件目录路径的抽象表示形式,与平台无关。

        File类关心的是在磁盘上文件的存储,File类的一个对象,描述的是一个文件或文件夹(文件夹也可以称为目录)。

        想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。File对象可以作为参数传递给流的构造器。

        该类的出现是对文件系统的中的文件以及文件夹进行对象的封装,可以通过对象的思想来操作文件以及文件夹(比如:新建、删除、重命名、修改时间、文件大小等方法),但是并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。

        可以用面向对象的处理问题,通过该对象的方法,可以得到文件或文件夹的信息,方便了对文件与文件夹的属性信息进行操作。

1.2 File类初体验

        检验指定路径下是否存在指定的目录或者文件。

/** 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,* 但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。* D:\io -> \在java里面表示转义符*/@Testpublic void test1() {    // 想要通过一个File对象来表示d:/io文件夹和d:/io/hello.txt文件    File file1 = new File("d:\\io");    File file2 = new File("d:\\io\\hello.txt");    File file3 = new File("d:\\hello\\测试.txt");    // 判断文件或目录是否存在    System.out.println("file1对象对应的文件夹是否存在:" + file1.exists()); // true    System.out.println("file2对象对应的文件是否存在:" + file2.exists()); // true    System.out.println("file3对象对应的文件是否存在:" + file3.exists()); // false    System.out.println("-----------------------------------------");    System.out.println("file1对象对应的是文件夹吗?" + file1.isDirectory()); // true    System.out.println("file1对象对应的是文件吗?" + file1.isFile()); // false    System.out.println("-----------------------------------------");    System.out.println("file2对象对应的是文件夹吗?" + file2.isDirectory()); // false    System.out.println("file2对象对应的是文件吗?" + file2.isFile()); // true    System.out.println("-----------------------------------------");    System.out.println("file3对象对应的是文件夹吗?" + file3.isDirectory()); // false    System.out.println("file3对象对应的是文件吗?" + file3.isFile()); // false    System.out.println("-----------------------------------------");    System.out.println("file2对象的内容长度:" + file2.length());    System.out.println("file2对象的最后修改时间:" + new Date(file2.lastModified()));}

        结论:
                1、一个File对象可以代表一个文件夹或文件。
                2、一个File对象也可以代表一个不存在的文件夹或文件。
                3、构建一个File对象,不会在机器上创建一个文件。

1.3 File类字段

        static String pathSeparator    与系统有关的路径分隔符,为了方便,它被表示为一个字符串。 

        static char pathSeparatorChar    与系统有关的路径分隔符。 

        static String separator    与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。 

        static char separatorChar    与系统有关的默认名称分隔符。 

@Testpublic void test2() {    // static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串。    System.out.println("与系统有关的路径分隔符字符串:" + File.pathSeparator); // ;    // static char pathSeparatorChar    与系统有关的路径分隔符。    System.out.println("与系统有关的路径分隔符字符:" + File.pathSeparatorChar); // ;    // static String separator  与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。    System.out.println("与系统有关的默认名称分隔符字符串:" + File.separator); // \    // static char separatorChar    系统有关的默认名称分隔符。    System.out.println("与系统有关的默认名称分隔符字符:" + File.separatorChar); // \}

1.3.1 路径

        路径就是文件或文件夹所在的位置。

分割符字符

        路径中的每级目录之间用一个名称分隔符隔开。

        windows和DOS系统默认使用“\”来表示,UNIX和URL使用“/”来表示,这是历史原因,不解释。

        Java程序支持跨平台运行,因此路径分隔符要慎用。专业的做法是使用File.separatorChar/separator,这个值就会根据系统得到的相应的分割符。
                例:new File("c:" + File.separatorChar + "a.txt");

        注意,如果是使用\,则需要进行转义,写为\\才可以,如果是两个\,则写为\\\\。

1.3.2 绝对路径与相对路径

        绝对路径:是一个固定的路径,从盘符开始;    
                对于UNIX平台,绝对路径名的前缀是"/",相对路径名没有前缀。
                对于Windows平台,绝对路径名的前缀由驱动器号和一个":"组成,例"c:\\...",相对路径没有盘符前缀。

        相对路径:是相对于某个位置开始;
                相对路径是指相对于某位置的路径,是指相对于当前目录。
                在执行Java程序时,相对路径为执行java命令时当前所在的目录。

实验:

        在不同的路径下执行java命令运行以下程序,并观察输出结果。
                File file = new File("a.txt");
                System.out.println(file.getAbsolutePath());

        相对路径:
                File file = new File("src/a.txt");

@Testpublic void test3() {    /*     * 路径就是文件或文件夹所在的位置。     * 我们在写路径的时候,可以直接写\即可,为什么还要定义相关字段     *  1、\ 在Java里面表示的是转义符,如果我们想要使用\,必须写\\     *  2、在Window和DOS里面,都是使用\表示名称分隔符;但是Unix和Linux都是使用/     *      其实在Window里面已经可以识别/     *  3、Java程序支持跨平台运行,因此路径分隔符要慎用。专业的做法是使用File.separatorChar/separator,这个值就会根据系统得到的相应的分割符。     *     */    File file1 = new File("d:\\io");    // 推荐下面的两种写法,不推荐上述的写法    File file2 = new File("d:" + File.separator + "io");    File file3 = new File("d:/io");    System.out.println(file3.exists());}/** 绝对路径:是一个固定的路径,从盘符开始;* 相对路径:是相对于某个位置开始;* 我们在实际编码的时候,用相对路径还是绝对路径?*  对于应用程序内部的文件我们应该写相对路径;*      db.properties就是我们应用程序内部的文件,我们推荐写相对路径,不要写绝对路径;*   绝对路径: D:\idea_workspace\javabase_46\day37\config\db.properties*   相对路径: config\db.properties*      我们的项目开发完成之后,需要部署到服务器,服务器的目录和你的本地目录就不一样,那么文件就读取不到了;*  对于应用程序外部的文件我们应该写绝地路径;*      比如静态资源服务器(用来存储图片、视频、音频)的服务器;*      代码部署在A服务器上,但是用户上上传的图片、音频和视频可能在服务器B上;*/@Testpublic void test4() {    // d:/io/hello.txt 绝对路径    // http://192.168.92.173:80/abc 绝对路径    File file1 = new File("d:/io/hello.txt");    // config/db.properties 相对路径    // 在Java中,相对路径:相对于项目的路径;(注意事项)    File file2 = new File("db.properties");    System.out.println("file2是否存在:" + file2.exists()); // false    System.out.println(file2.getAbsolutePath());    System.out.println("--------------------------");    File file3 = new File("config/db.properties");    System.out.println("file3是否存在:" + file3.exists()); // true    System.out.println(file3.getAbsolutePath());}

1.4 File类构造函数

        File(File parent, String child)    根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。 

        File(String pathname)    通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 
                以pathname为路径创建File对象,可以是绝对路径或者相对路径。

        File(String parent, String child)    根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。 

        File(URI uri)    通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。 

        说明:
                如果指定的路径不存在(没有这个文件或是文件夹),不会抛异常,这时file.exists()返回false。
                创建File对象需要导包, import java.io.File。
                File对象没有无参数构造,创建对象需要传参。
                File类的对象,既可以代表文件也可以代表文件夹。

示例代码:

@Testpublic void test5() {    // File(String pathname)    通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。    // pathname可以是相对路径,也可以是绝对路径    File file1 = new File("d:/io");    System.out.println("file1对应的文件夹是否存在:" + file1.exists());    System.out.println("------------------------------------");    // File(File parent, String child)  根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。    File file2 = new File(file1, "hello.txt"); // d:/io  hello.txt ->d:/io/hello.txt    System.out.println("file2对应的文件夹是否存在:" + file2.exists());    System.out.println("------------------------------------");    // File(String parent, String child)    根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。    File file3 = new File("d:/io", "hello.txt");    System.out.println("file3对应的文件夹是否存在:" + file3.exists());}

1.5 File类中常用的方法

1.5.1 创建

        boolean createNewFile()        当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。 

        static File createTempFile(String prefix, String suffix)    在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。  

        static File createTempFile(String prefix, String suffix, File directory)     在指定的目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。  

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

        boolean mkdirs()    创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。

        boolean renameTo(File dest)        重新命名此抽象路径名表示的文件。

        注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。

示例代码:

@Testpublic void test6() throws IOException {    File file1 = new File("d:/abc");    // boolean createNewFile()  当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。    // file1.createNewFile();  // 在d盘下面创建的是abc文件,只是这个文件没有扩展名,不会给你创建文件夹;    file1 = new File("d:/io/new.txt");    boolean isNew = file1.createNewFile();    if (isNew) { System.out.println("文件创建成功!");    } else { System.out.println("文件创建失败!");    }    // static File createTempFile(String prefix, String suffix)在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。    File tempFile1 = File.createTempFile("Java", ".yyds");    System.out.println(tempFile1); // File类重写了toString,会把文件的路径给反馈出来    // static File createTempFile(String prefix, String suffix, File directory) 在指定的目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。    File directory = new File("d:/io");    File tempFile2 = File.createTempFile("Java", ".yyds", directory);    System.out.println(tempFile2);    // boolean mkdir()创建此抽象路径名指定的目录。    /*File file2 = new File("d:/测试1"); boolean isMk1 = file2.mkdir(); if(isMk1){     System.out.println("文件夹创建成功!"); }else{     System.out.println("文件夹创建失败!"); }*/    // 只能创建一级目录,如果有多级目录,创建失败    File file3 = new File("d:/测试2/abc");    // boolean isMk2 = file3.mkdir();    // boolean mkdirs()创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。    boolean isMk2 = file3.mkdirs();    if (isMk2) { System.out.println("文件夹创建成功!");    } else { System.out.println("文件夹创建失败!");    }    // boolean renameTo(File dest)重新命名此抽象路径名表示的文件。    // 情况一: 如果原文件对象和参数文件对象位于同一级目录,那么就是改名操作;    /*File filex = new File("d:/io/hello.txt"); File filey = new File("d:/io/HelloWorld.txt"); filex.renameTo(filey);*/    // 情况二: 如果原文件对象和参数文件对象不位于同一级目录,那么是文件移动操作;    File filey = new File("d:/io/HelloWorld.txt");    File filez = new File("d:/测试2/pp.txt");    filey.renameTo(filez);}

1.5.2 删除

        boolean delete()    删除此抽象路径名表示的文件或目录。 

        void deleteOnExit()        在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 

        删除注意事项:
                Java中的删除不走回收站。
                要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。

示例代码:

@Testpublic void test7() throws InterruptedException {    // xx.txt不存在,删除失败    File file1 = new File("d:/io/xx.txt");    // a.txt存在,存在可以删除    file1 = new File("d:/io/a.txt"); // 文件被删除,不走回收站    //  boolean delete()删除此抽象路径名表示的文件或目录。    boolean isDel = file1.delete();    if (isDel) { System.out.println("文件删除成功!");    } else { System.out.println("文件删除失败!");    }    // io目录下面不为空,删除失败    file1 = new File("d:/io");    System.out.println("是否删除了io目录:" + file1.delete());    // void deleteOnExit()在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。    file1 = new File("d:/io/b.txt");    file1.deleteOnExit();    System.out.println("deleteOnExit方法之后的语句");    Thread.sleep(3000);}// 删除一个文件下夹所有的文件夹   File delfile=new File(str1);   File[] files=delfile.listFiles();   for(int i=0;i<files.length;i++){if(files[i].isDirectory()){    files[i].delete();}   }

1.5.3 判断

        boolean canExecute()    测试应用程序是否可以执行此抽象路径名表示的文件。 

        boolean canRead()    测试应用程序是否可以读取此抽象路径名表示的文件。 

        boolean canWrite()         测试应用程序是否可以修改由此抽象路径名表示的文件。  

        int compareTo(File pathname)     按字母顺序比较两个抽象路径名。  

        boolean equals(Object obj)        测试此抽象路径名与给定对象是否相等。 

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

        boolean isAbsolute()     测试此抽象路径名是否为绝对路径名。

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

        boolean isFile()    测试此抽象路径名表示的文件是否是一个标准文件。 

        boolean isHidden()    测试此抽象路径名指定的文件是否是一个隐藏文件。

示例代码:

@Testpublic void test8() {    File file1 = new File("d:/io/hello.txt"); // 存在该文件    File file2 = new File("d:/io/world.txt"); // 存在该文件,但是该文件只读    File file3 = new File("d:/io/new.txt"); // 存在该文件,但是该文件隐藏    File file4 = new File("d:/io/haha.txt"); // 不存在该文件    // boolean canExecute()测试应用程序是否可以执行此抽象路径名表示的文件。只要文件存在,都是可以执行的    System.out.println("file1是否可执行:" + file1.canExecute());// true    System.out.println("file2是否可执行:" + file2.canExecute());// true    System.out.println("file3是否可执行:" + file3.canExecute());// true    System.out.println("file4是否可执行:" + file4.canExecute());// false    System.out.println("--------------------------------------");    // boolean canRead()测试应用程序是否可以读取此抽象路径名表示的文件。只要文件存在,都是可读    System.out.println("file1是否可读:" + file1.canRead());// true    System.out.println("file2是否可读:" + file2.canRead());// true    System.out.println("file3是否可读:" + file3.canRead());// true    System.out.println("file4是否可读:" + file4.canRead());// false    System.out.println("--------------------------------------");    // boolean canWrite() 测试应用程序是否可以修改由此抽象路径名表示的文件。只要文件存在和非只读,都是可以写的    System.out.println("file1是否可写:" + file1.canWrite());// true    System.out.println("file2是否可写:" + file2.canWrite());// false    System.out.println("file3是否可写:" + file3.canWrite());// true    System.out.println("file4是否可写:" + file4.canWrite());// false    System.out.println("--------------------------------------");    // int compareTo(File pathname) 按字母顺序比较两个抽象路径名。    // boolean equals(Object obj)测试此抽象路径名与给定对象是否相等。    // boolean exists() 测试此抽象路径名表示的文件或目录是否存在。    System.out.println("file1是否存在:" + file1.exists());// true    System.out.println("file2是否存在:" + file2.exists());// true    System.out.println("file3是否存在:" + file3.exists());// true    System.out.println("file4是否存在:" + file4.exists());// false    System.out.println("--------------------------------------");    // boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。    System.out.println("file1是否为绝对路径名:" + file1.isAbsolute());// true    System.out.println("file2是否为绝对路径名:" + file2.isAbsolute());// true    System.out.println("file3是否为绝对路径名:" + file3.isAbsolute());// true    System.out.println("file4是否为绝对路径名:" + file4.isAbsolute());// true    File file5 = new File("config/db.properties");    System.out.println("file5是否为绝对路径名:" + file4.isAbsolute());// false    System.out.println("--------------------------------------");    // boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。    // boolean isFile()测试此抽象路径名表示的文件是否是一个标准文件。    // boolean isHidden()测试此抽象路径名指定的文件是否是一个隐藏文件。    System.out.println("file1是否是隐藏文件:" + file1.isHidden());// false    System.out.println("file2是否是隐藏文件:" + file2.isHidden());// false    System.out.println("file3是否是隐藏文件:" + file3.isHidden());// true    System.out.println("file4是否是隐藏文件:" + file4.isHidden());// false}

1.5.4 获取

    File getAbsoluteFile()返回此抽象路径名的绝对路径名形式。    String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。File getCanonicalFile()返回此抽象路径名的规范形式。 String getCanonicalPath()返回此抽象路径名的规范路径名字符串。     long getFreeSpace() 返回此抽象路径名指定的分区中未分配的字节数。    String getName()返回由此抽象路径名表示的文件或目录的名称。    String getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。    File getParentFile() 返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。    String getPath()将此抽象路径名转换为一个路径名字符串。    long getTotalSpace()返回此抽象路径名指定的分区大小。    long getUsableSpace()返回此抽象路径名指定的分区上可用于此虚拟机的字节数。    long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。    long length()返回由此抽象路径名表示的文件的长度。    String[] list()返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。    String[] list(FilenameFilter filter)返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。    File[] listFiles()返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。    File[] listFiles(FileFilter filter)返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。    File[] listFiles(FilenameFilter filter)返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录。    static File[] listRoots()列出可用的文件系统根。
@Testpublic void test9() {    // 相对路径的写法    File file1 = new File("config/db.properties");    System.out.println("file1的抽象路径表现形式:" + file1);    // File getAbsoluteFile()   返回此抽象路径名的绝对路径名形式。    File file2 = file1.getAbsoluteFile(); // 获取file1的绝对路径,然后通过绝对路径构建了一个对象;    System.out.println("file2的抽象路径表现形式:" + file2);    // equals() 比较的是两个对象的抽象路径名;    System.out.println(file1.equals(file2)); // false    // String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串。    System.out.println("file1的绝对路径:" + file1.getAbsolutePath());    // long getFreeSpace() 返回此抽象路径名指定的分区中未分配的字节数    System.out.println("file1所在分区中未分配的字节数:" + file1.getFreeSpace());    // String getName()返回由此抽象路径名表示的文件或目录的名称。    System.out.println("file1的文件或文件名:" + file1.getName()); // db.properties    System.out.println("file2的文件或文件名:" + file2.getName()); // db.properties    // String getParent()   返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。    System.out.println("file1的父路径:" + file1.getParent()); // config    System.out.println("file2的父路径:" + file2.getParent()); // config    // File getParentFile() 返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。    // String getPath()将此抽象路径名转换为一个路径名字符串。    System.out.println("file1路径名字符串:" + file1.getPath());    System.out.println("file2路径名字符串:" + file2.getPath());    // long getTotalSpace()返回此抽象路径名指定的分区大小。    System.out.println("file1所在分区的大小:" + file1.getTotalSpace());    // long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。    // long length()返回由此抽象路径名表示的文件的长度。    System.out.println("file1的长度:" + file1.length());}@Testpublic void test10() {    File file = new File("d:/io");    // String[] list()返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。    // 遍历一: 打印当前目录的所有文件和文件夹    /*String[] list = file.list(); for (String str : list) {     System.out.println(str); }*/    System.out.println(file + "下面的所有的子文件和文件夹:");    traversalFiles("d:/io");    System.out.println("------------------------------");    System.out.println("io文件夹下面的所有txt文件:");    String[] list = file.list();    for (String str : list) { if (str.endsWith(".txt")) {     System.out.println(str); }    }    System.out.println("------------------------------");    System.out.println("io文件夹下面的所有txt文件:");    // String[] list(FilenameFilter filter)返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。    String[] list1 = file.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) {     return name.endsWith(".txt"); }    });    System.out.println(Arrays.asList(list1));    // File[] listFiles()返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。    // 和list方法类似,但是返回值是File数组    // static File[] listRoots()列出可用的文件系统根。    File[] files = File.listRoots();    System.out.println(Arrays.asList(files));}/*     * 遍历所有的文件     */public void traversalFiles(String pathName) {    // 先判定参数路径为null的问题    if (pathName == null) { System.out.println("路径名不能为null"); return;    }    File file = new File(pathName);    // 文件是否存在 和 不能为null    if (file.exists()) { if (file.isDirectory()) {     System.out.println(file.getName() + "是文件夹");     // 获取指定文件夹下面的所有的文件String     /*String[] list = file.list();  for (String str : list) {      traversalFiles(file.getAbsolutePath()+"/"+str);  }*/     File[] list = file.listFiles();     for (File childFile : list) {  traversalFiles(childFile.getAbsolutePath());     } } else {     System.out.println("\t" + file.getName()); }    } else { System.out.println("文件路径不存在!");    }}

练习题:

        判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称。

import java.io.File;import java.io.FileFilter;import java.io.FilenameFilter;import org.junit.Test;public class FindJPGFileTest {@Testpublic void test1(){File srcFile = new File("d:\\code");String[] fileNames = srcFile.list();for(String fileName : fileNames){if(fileName.endsWith(".jpg")){System.out.println(fileName);}}}@Testpublic void test2(){File srcFile = new File("d:\\code");File[] listFiles = srcFile.listFiles();for(File file : listFiles){if(file.getName().endsWith(".jpg")){System.out.println(file.getAbsolutePath());}}}/* * File类提供了两个文件过滤器方法 * public String[] list(FilenameFilter filter) * public File[] listFiles(FileFilter filter) */@Testpublic void test3(){File srcFile = new File("d:\\code");File[] subFiles = srcFile.listFiles(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {return name.endsWith(".jpg");}});for(File file : subFiles){System.out.println(file.getAbsolutePath());}}}

1.5.5 修改

        boolean setExecutable(boolean executable)    设置此抽象路径名所有者执行权限的一个便捷方法。 

        boolean setExecutable(boolean executable, boolean ownerOnly)    设置此抽象路径名的所有者或所有用户的执行权限。 

        boolean setLastModified(long time)        设置此抽象路径名指定的文件或目录的最后一次修改时间。

        boolean setReadable(boolean readable)    设置此抽象路径名所有者读权限的一个便捷方法。 

        boolean setReadable(boolean readable, boolean ownerOnly)    设置此抽象路径名的所有者或所有用户的读权限。 

        boolean setReadOnly()     标记此抽象路径名指定的文件或目录,从而只能对其进行读操作。 

        boolean setWritable(boolean writable)     设置此抽象路径名所有者写权限的一个便捷方法。 

        boolean setWritable(boolean writable, boolean ownerOnly)    设置此抽象路径名的所有者或所有用户的写权限。 

1.5.6 示例代码

递归遍历文件夹

/*** 递归遍历文件夹* @param path*/public static void traversalFiles(String path) {    //创建文件对象    File file = new File(path);    //判断文件是否存在    if(file.exists()) { if(file.isFile()) {     System.out.println(file.getName()); }else if(file.isDirectory()) {     System.out.println(file.getName());     //遍历     File[] files = file.listFiles();     if(files == null) {  return;     }     for(File childrenFile:files) {  //递归遍历  traversalFiles(childrenFile.getAbsolutePath());     } }    }}

过滤指定文件

/*** 递归遍历文件夹,过滤指定文件* @param path*/public static void traversalFiles(String path,String fileType) {    //创建文件对象    File file = new File(path);    //判断文件是否存在    if(file.exists()) { if(file.isFile() ) {     if(file.getName().endsWith(fileType)) {  System.out.println(file.getAbsolutePath());     } }else if(file.isDirectory()) {     //System.out.println(file.getName());     //遍历     File[] files = file.listFiles();     if(files == null) {  return;     }     for(File childrenFile:files) {  //递归遍历  traversalFiles(childrenFile.getAbsolutePath(),fileType);     } }    }}

二、IO流的理解

2.1 IO概述

        流(stream)的概念源于UNIX中管道(pipe)的概念,在UNIX中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。

        一个流,必有源端和目的端,它们可以是计算机内存的某些区域,也可以是磁盘文件,甚至可以是Internet上的某个URL。

        流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序列或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。

        用来进行输入输出操作的流就称为IO流。

便于理解,这么定义流: 流就是一个管道里面有流水,这个管道连接了文件和程序。

        I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输,如读/写文件,网络通讯等。

        Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。

        java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

        输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
    
        输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

2.2 IO流分类

        Java中的流,可以从不同的角度进行分类。

2.2.1 输入流和输出流

        按数据流的流向不同分为:输入流,输出流
                此输入、输出是相对于我们写的代码程序而言;
                输入流:从别的地方(本地文件,网络上的资源等)获取资源 输入到 我们的程序中; (读取)
                输出流:从我们的程序中 输出到 别的地方(本地文件), 将一个字符串保存到本地文件中,就需要使用输出流。(写出)
                流的方向是重要的,用户可以从输入流中读取信息,但不能写它。相反,对输出流,只能往输出流写,而不能读它。实际上,流的源端和目的端可简单地看成是字节/字符的生产者和消费者,对输入流,可不必关心它的源端是什么,只要简单地从流中读数据,而对输出流,也可不知道它的目的端,只是简单地往流中写数据。形象的比喻——水流,文件======程序,文件和程序之间连接一个管道,水流就在之间形成了,自然也就出现了方向:可以流进,也可以流出。

        输入和输出都是从程序的角度来说的,例如:
                System.out;        系统输出流,在控制台显示输出某句话,把内容写在控制台。
                System.in;        系统输入流,从控制台输入内容,写在程序中去。

2.2.2 字节流和字符流

        按处理数据单位不同分为:字节流、字符流
                字节流:每次读取(写出)一个字节,当传输的资源文件有中文时,就会出现乱码。
                字符流:每次读取(写出)一个字符,有中文时,使用该流就可以正确传输显示中文。
    
        字节流和字符流的原理是相同的,只不过处理的单位不同而已。后缀是Stream是字节流,而后缀是Reader,Writer是字符流。

        字符流的由来:Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。

        字节流和字符流的区别:
                读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
                处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
                字节流:一次读入或读出是8位二进制。
                字符流:一次读入或读出是一个或者多个字节。

        设备上的数据无论是图片或者视频,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。

        结论:只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流。    

2.2.3 节点流和处理流

    按流的功能的不同分为:节点流,处理流。
        节点流:直接与数据源相连,读入或读出。
            直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。
        处理流:是对一个已存在的节点流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装;
            与节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。

2.3 IO流体系

        Jdk提供的流继承了四大类:
                InputStream(字节输入流),OutputStream(字节输出流)。
                Reader(字符输入流),Writer(字符输出流)。

        Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。

        由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

        4个基本的抽象流类型,所有的流都继承这四个。
                                  输入流              输出流
                字节流  InputStream    OutputStream
                字符流  Reader            Writer

        在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable。掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了。

        主要的类如下:
                1、File(文件特征与管理):File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。  

                2、InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。

                3、OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。

                4、Reader(文件格式操作):抽象类,基于字符的输入操作。

                5、Writer(文件格式操作):抽象类,基于字符的输出操作。

2.3.1 InputStream & Reader

        InputStream 和 Reader 是所有输入流的基类。
    
        InputStream(典型实现:FileInputStream)
                int read()
                int read(byte[] b)
                int read(byte[] b, int off, int len)
        
        Reader(典型实现:FileReader)
                int read()
                int read(char [] c)
                int read(char [] c, int off, int len)

        程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源。
    
        FileInputStream 从文件系统中的某个文件中获得输入字节。FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader。

InputStream

int read()从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。int read(byte[] b)从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。int read(byte[] b, int off, int len)将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1。void close() throws IOException关闭此输入流并释放与该流关联的所有系统资源。

Reader

int read()读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1。int read(char[] cbuf)将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。int read(char[] cbuf, int off, int len)将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。void close() throws IOException关闭此输入流并释放与该流关联的所有系统资源。

2.3.2 OutputStream & Writer

OutputStream 和 Writer 是所有输出流的基类。OutputStream(典型实现:FileOutputStream)void write(int b)  void write(byte[] b)  void write(byte[] b, int off, int len)  void flush()  void close()  Writer(典型实现:FileWriter)void write(int c)  void write(char[] cbuf)  void write(char[] cbuf, int off, int len)  void write(String str)  void write(String str, int off, int len)  void flush()  void close()  因为字符流直接以字符作为操作单位,所以 Writer 可以用字符串来替换字符数组,即以 String 对象作为参数;FileOutputStream 从文件系统中的某个文件中获得输出字节。FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter。

OutputStream

void write(int b)将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。      void write(byte[] b)将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。    void write(byte[] b, int off, int len)将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。    void flush()刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。    void close()关闭此输出流并释放与该流关联的所有系统资源。

Writer

void write(int c)写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码。    void write(char[] cbuf)写入字符数组。    void write(char[] cbuf, int off, int len)写入字符数组的某一部分。从off开始,写入len个字符。    void write(String str)写入字符串。    void write(String str, int off, int len)写入字符串的某一部分。    void flush()刷新该流的缓冲,则立即将它们写入预期目标。    void close()关闭此输出流并释放与该流关联的所有系统资源。

2.3.3 IO流特性

        1、先进先出,最先写入输出流的数据最先被输入流读取到。

        2、顺序存取,可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。

        3、只读或只写,每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。

2.4 总结流的分类

        看上面的几个分类,可能对于初次学io的同学会感觉到有些混乱,那什么时候用字节流,什么时候该用输出流呢?其实非常简单,举一个例子就学会了;

        1、首先自己要知道是选择输入流还是输出流,这就要根据自己的情况而定,如果你想从程序写东西到别的地方,那么就选择输出流,反之用输入流。 【读取数据-输入流,写入数据-输出流】 【这个读写都是针对于程序来说】

        2、然后考虑你传输数据时,是选择使用字节流传输还是字符流,也就是每次传1个字节还是2个字节,有中文肯定就选择字符流了。

        3、前面两步就可以选出一个合适的节点流了,比如字节输入流inputStream,如果要在此基础上增强功能,那么就在处理流中选择一个合适的即可。

三、节点流(或文件流)

        节点流:直接与数据源相连,读入或读出。

3.1 字节流

        字节流:每次读取(写出)一个字节,当传输的资源文件有中文时,就会出现乱码。

3.1.1 FileInputStream

        public class FileInputStream extends InputStream

        FileInputStream 从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。 

        FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。 

构造函数:

        FileInputStream(File file)    通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。

        FileInputStream(FileDescriptor fdObj)    通过使用文件描述符 fdObj 创建一个 FileInputStream,该文件描述符表示到文件系统中某个实际文件的现有连接。 
          
        FileInputStream(String name)    通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。 

成员方法:

int available()返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。 void close()关闭此文件输入流并释放与此流有关的所有系统资源。 int read()从此输入流中读取一个数据字节。 int read(byte[] b)从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。 int read(byte[] b, int off, int len)从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。 long skip(long n)从输入流中跳过并丢弃 n 个字节的数据。 

示例代码1:

import java.io.File;import java.io.FileInputStream;public class FileInputStreamTest01 {    public static void main(String[] args) throws Exception { //1、把要操作硬盘上的文件,需要把文件转化成对象来操作 //a.txt的内容如下:abcdefghigklmn File file = new File("src/a.txt"); //2、调用FileInputStream构造函数把file类传递进去 FileInputStream fis = new FileInputStream(file); //构造函数不仅可以传递file类,也可以传递String类型的地址 //FileInputStream fis1=new FileInputStream("src/a.txt"); //3、调用read()方法读取字节 int x = fis.read();//从此输入流中读取一个数据字节,返回值是int //这里对应的是97,指的是ascii System.out.println(x);//97 //我们可以使用强转的方式转换为字符 System.out.println((char) x);//a System.out.println("--------------------------"); //如果想要读取所有,那么只需要使用循环读取即可 //read()当读取到末尾的时候,返回值是-1 while (x != -1) {     char ch = (char) x;     System.out.print(ch);     //继续读取     x = fis.read();//d } //4、close()关闭此文件输入流并释放与此流有关的所有系统资源 fis.close();    }}

示例代码2:

import java.io.File;import java.io.FileInputStream;public class FileInputStreamTest02 {    public static void main(String[] args) throws Exception { //1、把要操作硬盘上的文件,需要把文件转化成对象来操作 //a.txt的内容如下:abcdefghigklmn File file = new File("src/a.txt"); //2、调用FileInputStream构造函数把file类传递进去 FileInputStream fis = new FileInputStream(file); //3、read(byte[] b)从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。 //定义一个byte数组   读取数据的缓冲区   返回值 读到多少个 byte b[] = new byte[10]; //读一次 int len = fis.read(b);//  读取出来的长度是10 while (len != -1) {     //把读出来的数组中的所有字符,转成一个字符串     String str = new String(b, 0, len);//读多少,转换多少     System.out.print(str);     len = fis.read(b); } //4、close()关闭此文件输入流并释放与此流有关的所有系统资源 fis.close();    }}

3.1.2 FileOutputStream

        public class FileOutputStream extends OutputStream
    
        文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。文件是否可用或能否可以被创建取决于基础平台。特别是某些平台一次只允许一个 FileOutputStream(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。 

        FileOutputStream 用于写入诸如图像数据之类的原始字节的流。要写入字符流,请考虑使用 FileWriter。 

构造函数:

        FileOutputStream(File file)        创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 
    
        FileOutputStream(File file, boolean append)        创建一个向指定 File 对象表示的文件中写入数据的文件输出流。 
          
        FileOutputStream(FileDescriptor fdObj)    创建一个向指定文件描述符处写入数据的输出文件流,该文件描述符表示一个到文件系统中的某个实际文件的现有连接。 
          
        FileOutputStream(String name)    创建一个向具有指定名称的文件中写入数据的输出文件流。 
    
        FileOutputStream(String name, boolean append)    创建一个向具有指定 name 的文件中写入数据的输出文件流。 

成员方法:

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

        void write(int b)    将指定字节写入此文件输出流。 

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

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

示例代码:

import java.io.FileOutputStream;public class FileOutputStreamTest01 {    public static void main(String[] args) throws Exception { //输入流,要从指定的文件读取内容到内存中,如果文件不存在,则报错 //输出流,要从内存中写内容到指定文件,如果文件不存在,则创建一个新的文件 FileOutputStream fos = new FileOutputStream("src/b.txt", true); //write(int b) 将指定字节写入此文件输出流。 fos.write('a'); //输出完毕之后,如果src下面有b.txt,那么就会向里面写入a,如果没有,则创建b.txt,并且写入内容 String str = "abcdefg"; //把字符串转换为数组 byte b[] = str.getBytes(); //write(byte[] b) 将 b.length 个字节从指定 byte 数组写入此文件输出流中。 fos.write(b); //close() 关闭此文件输出流并释放与此流有关的所有系统资源。 fos.close(); //流关闭之后,再次写入内容的时候,会把之前的内容给覆盖掉,那么是因为调用构造函数没传递true参数    }}

3.1.3 字节流案例

1、在d盘下面创建一个test.txt文件,并读取里面的内容输出在控制台;

import java.io.File;import java.io.FileInputStream;public class Test_01 {    public static void main(String[] args) throws Exception { //1、把test.txt文件封装为file类 File file = new File("D:/test.txt"); //2、定义字节流 FileInputStream fis = new FileInputStream(file); //3、创建byte数组,byte数组的长度和文件内容长度一致,那么就一次读取完了 byte b[] = new byte[(int) file.length()]; int len = fis.read(b); //把读取的内容转换为字符串 String str = new String(b); System.out.println(str); //4、释放资源 fis.close();    }}

2、从控制台任意输入内容,直到输入end结束,把输入的内容追加到test.txt文件里面;

import java.io.File;import java.io.FileOutputStream;import java.util.Scanner;public class Test_02 {    public static void main(String[] args) throws Exception { //1、把test.txt文件封装为file类 File file = new File("D:/test.txt"); //2、定义字节流输出流 FileOutputStream fos = new FileOutputStream(file, true); //从控制台输入内容 Scanner sc = new Scanner(System.in); while (true) {     String str = sc.next();     if (str.equals("end")) {  break;     }     //在我们流中,要是想换行,     str = str + "\r";     byte b[] = str.getBytes();     fos.write(b); } //4、释放资源 fos.close();    }}

3、把d盘下面的1.jpg图片,复制为e盘下面的2.jpg;

        1、使用输入流读取文件。
        2、使用输出流输出读取到的内容。

import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;public class Test_03 {public static void main(String[] args) throws IOException {//FileInputStream 从文件系统中的某个文件中获得输入字节。try {//new FileInputStream(new File(""));输入流,用来读取数据FileInputStream is = new FileInputStream("D:/1.jpg");//FileOutputStream  文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。FileOutputStream os = new FileOutputStream("D:/2.jpg");//read(byte[] b) 从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。byte b[] = new byte[1024];int length = is.read(b); //实际读取到的数量while(length != -1){//写出去os.write(b, 0, length);//继续读length = is.read(b);}//关闭流的原则:后打开的先关闭os.close();is.close();} catch (FileNotFoundException e) {// TODO Auto-generated catch blockSystem.out.println("文件找不到!");e.printStackTrace();}}}

3.2 字符流

        字符流:每次读取(写出)一个字符,有中文时,使用该流就可以正确传输显示中文。

        字符输入流 FileReader;
        字符输出流 FileWriter;

        字符流的操作和字节流一致,但是我们推荐在使用读取中文内容的时候使用字符流,因为字节流是一个个读取,那么在读取过程中,可能把一个字符拆分为两个字节来读取,可能会出现一定问题。

3.2.1 FileReader

        FileReader 用来读取字符文件的便捷类。通常用来读取和中文相关的内容;

        public class FileReader extends InputStreamReader
                InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。 
    
        public class InputStreamReader extends Reader
                Reader 用于读取字符流的抽象类。
    
        我们从这样的体系结构可以看出来,这就是我们之前说的,字符流的底层还是来进行的字节读取而已;

构造函数:

        FileReader(File file)    在给定从中读取数据的 File 的情况下创建一个新 FileReader。 

        FileReader(FileDescriptor fd)    在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。 

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

成员方法:

        FileReader类没有定义相关成员方法,都是从InputStreamReader里面继承而来的方法;

        void close()    关闭该流并释放与之关联的所有资源。 

        String getEncoding()    返回此流使用的字符编码的名称。 

        int read()    读取单个字符。 

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

        int read(char[] b, int off, int len)    从此输入流中将最多 len 个字节的数据读入一个 char 数组中。

3.2.2 FileWriter

        FileWriter 用来写入字符文件的便捷类。
    
        public class FileWriter extends OutputStreamWriter
                OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。 

        public class OutputStreamWriter extends Writer

构造函数:

        FileWriter(File file)    根据给定的 File 对象构造一个 FileWriter 对象。

        FileWriter(File file, boolean append)    根据给定的 File 对象构造一个 FileWriter 对象。 

        FileWriter(String fileName)        根据给定的文件名构造一个 FileWriter 对象。 

        FileWriter(String fileName, boolean append)        根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。 

成员方法:

        FileWriter类没有定义相关成员方法,都是从OutputStreamWriter里面继承而来的方法;

        void close()    关闭此流,但要先刷新它。 

        void flush()    刷新该流的缓冲。 

        String getEncoding()    返回此流使用的字符编码的名称。 

        void write(int c)    写入单个字符。 

        void write(String str)    写入字符串

        void write(String str, int off, int len)    写入字符串的某一部分。 

        void write(char[] cbuf)        写入字符数组

        void write(char[] cbuf, int off, int len)    写入字符数组的某一部分。 

        我们使用字符流,就是用来处理文字相关的文档;

3.3 节点流总结

        读取文件:
                1、建立一个流对象,将已存在的一个文件加载进流。
                2、创建一个临时存放数据的数组。
                3、调用流对象的读取方法将流中的数据读入到数组中。
                4、关闭资源。

        写入文件:
                1、创建流对象,建立数据存放文件。
                2、调用流对象的写入方法,将数据写入流。
                3、关闭流资源,并将流中的数据清空到文件中。

        注意点:
                定义文件路径时,注意:可以用“/”或者“\\”。
                在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖。
                如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容。
                在读取文件时,必须保证该文件已存在,否则报异常。
                字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt;
                字符流操作字符,只能操作普通文本文件。最常见的文本文件:.txt,.java,.c等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

四、缓冲流

        为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。
    
        public class BufferedInputStream extends FilterInputStream {
                private static int DEFAULT_BUFFER_SIZE = 8192;
        }

        缓冲流要“套接”在相应的节点流之上,不能独立存在,必须依托于另一个流(节点流)。

        根据数据操作单位可以把缓冲流分为:
                BufferedInputStream 缓冲字节输入流;                                                                       BufferedOutputStream 缓冲字节输出流;
        
                BufferedReader    缓冲字符输入流;
                BufferedWriter    缓冲字符输出流;

        写出数据时,先写入到其内部的缓冲区中,当缓冲区满了,会进行一次真实的写操作。

4.1 缓冲流读和写的原理

        当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区。
    
        读取数据时,一次性尽可能多的读取字节,并缓存起来,这样实际上就是从缓冲区中读取数据,所以读取效率高。当缓冲区内容都读取完毕后,该流会再次尽可能多的读取字节缓存起来,等待再次被读取。

        向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,才会把缓冲区中的数据一次性写到文件里。使用方法flush()可以强制将缓冲区的内容全部写入输出流。
    
        关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。

        flush()方法的使用:手动将buffer中内容写入文件。

        如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。

4.2 缓冲流新增方法

        BufferedInputStream、BufferedOutputStream 没有新增的方法;
    
        BufferedReader新增方法:
                String readLine()    读取一个文本行。 

        BufferedWriter新增方法:
                void newLine()    写入一个行分隔符。 

4.3 示例代码

import java.io.*;public class BufferedTest {    public static void main(String[] args) { BufferedReader br = null; BufferedWriter bw = null; try {     // 创建缓冲流对象:它是处理流,是对节点流的包装     br = new BufferedReader(new FileReader("d:\\io\\source.txt"));     bw = new BufferedWriter(new FileWriter("d:\\io\\dest.txt"));   String str;     while ((str = br.readLine()) != null) { // 一次读取字符文本文件的一行字符  bw.write(str); // 一次写入一行字符串  bw.newLine(); // 写入行分隔符     }   bw.flush(); // 刷新缓冲区 } catch (IOException e) {     e.printStackTrace(); } finally {     // 关闭IO流对象     try {  if (bw != null) {      bw.close(); // 关闭过滤流时,会自动关闭它所包装的底层节点流  }     } catch (IOException e) {  e.printStackTrace();     }     try {  if (br != null) {      br.close();  }     } catch (IOException e) {  e.printStackTrace();     } }    }}

五、转换流

        当我们的文件里面含有中文英文数字时,我们使用字节流将文件内容在内存中显示,英文和数字显示正常,而中文却却显示乱码。这时候我们可以使用转换流将其转化为字符流显示在内存中。

        转换流:InputStreamReader 、OutputStreamWriter提供了在字节流和字符流之间的转换。
                InputStreamReader:将InputStream转换为Reader
                OutputStreamWriter:将Writer转换为OutputStream

        转换流的作用,文本文件在硬盘中以字节流的形式存储时,通过InputStreamReader读取后转化为字符流给程序处理,程序处理的字符流通过OutputStreamWriter转换为字节流保存。

        转换流的特点:
                其是字符流和字节流之间的桥梁。
                可对读取到的字节数据经过指定编码转换成字符。
                可对读取到的字符数据经过指定编码转换成字节。

        何时使用转换流?
                当字节和字符之间有转换动作时;
                流操作的数据需要编码或解码时;

        InputStreamReader、OutputStreamWriter 要InputStream或OutputStream作为参数,实现从字节流到字符流的转换。

        作用:提供字节流与字符流之间的转换,很多时候我们使用转换流来处理文件乱码问题,实现编码和解码的功能。
                解码:字节,字节数组 –》 字符,字符数组
                编码:字符,字符数组 –》 字节,字节数组

5.1 InputStreamReader

        InputStreamReader实现将字节的输入流按指定字符集转换为字符的输入流。

        需要和InputStream“套接”。

        构造器:
                InputStreamReader(InputStream in)    创建一个使用默认字符集的 InputStreamReader。

                InputStreamReader(InputStream in, String charsetName)     创建使用指定字符集的 InputStreamReader。

示例代码:

import java.io.*;public class InputStreamReaderTest {    public static void main(String[] args) { InputStreamReader isr = null; try {     FileInputStream fis = new FileInputStream(new File("d:/io/hello_gbk.txt"));     // 使用系统默认的字符集(utf-8),发现乱码     // isr = new InputStreamReader(fis);     // 参数二指明字符集具体使用哪个字符集取决于文件.txt保存时使用哪个字符集     isr = new InputStreamReader(fis, "gbk");     char[] cbuf = new char[1024];     int len;     while ((len = isr.read(cbuf)) != -1) {  for (int i = 0; i < len; i++) {      System.out.print(cbuf[i]);  }     } } catch (FileNotFoundException e) {     e.printStackTrace(); } catch (UnsupportedEncodingException e) {     e.printStackTrace(); } catch (IOException e) {     e.printStackTrace(); } finally {     try {  if (isr != null)      isr.close();     } catch (IOException e) {  e.printStackTrace();     } }    }}

        字符集:utf-8,gbk…

        当一个文本文件时以gbk字符集存储时,我们使用utf-8字符集去转换会查看到乱码,所以要清楚看查看文本内容,要转换为文件保存时使用的字符集。

5.2 OutputStreamWriter

        OutputStreamWriter实现将字符的输出流按指定字符集转换为字节的输出流。

        需要和OutputStream“套接”。
    
        构造器:
                OutputStreamWriter(OutputStream out)    创建使用默认字符编码的 OutputStreamWriter。    

                OutputStreamWriter(OutputStream out, String charsetName)    创建使用指定字符集的 OutputStreamWriter。

        接下来,我们使用InputStreamReader和OutputStreamWriter将一个文本文件(使用的是utf-8的字符集),复制成一个使用gbk字符的的文本文件:

import java.io.*;public class InputStreamReaderAndWriterTest {    public static void main(String[] args) { InputStreamReader isr = null; OutputStreamWriter osw = null; try {     File file1 = new File("d:/io/hello_utf8.txt");     File file2 = new File("d:/io/hello_gbk.txt");     FileInputStream fis = new FileInputStream(file1);     FileOutputStream fos = new FileOutputStream(file2);     isr = new InputStreamReader(fis);     osw = new OutputStreamWriter(fos, "gbk");     char[] cbuf = new char[1024];     int len;     while ((len = isr.read(cbuf)) != -1) {  for (int i = 0; i < len; i++) {      osw.write(cbuf[i]);  }     } } catch (IOException e) {     e.printStackTrace(); } finally {     try {  if (isr != null)      isr.close();     } catch (IOException e) {  e.printStackTrace();     }     try {  if (osw != null)      osw.close();     } catch (IOException e) {  e.printStackTrace();     } }    }}

        InputStreamReader是读入操作,所以要使用读入文本文件相对应的字符集。
        OutputStreamWriter是写入操作,所以第二参数是要使用的字符集去写入一个文本文件。

六、标准输入、输出流

        标准I/O:Java程序可通过命令行参数与外界进行简短的信息交换,同时,也规定了与标准输入、输出设备,如键盘、显示器进行信息交换的方式。而通过文件可以与外界进行任意数据形式的信息交换。

        System.in和System.out分别代表了系统标准的输入和输出设备。

        默认输入设备是:键盘,输出设备是:显示器。

        System.in的类型是InputStream。

        System.out的类型是PrintStream,其是OutputStream的子类/FilterOutputStream 的子类。

        重定向:通过System类的setIn,setOut方法对默认设备进行改变。

例 题

        从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;public class StandardIOTest {    public static void main(String[] args) {  System.out.println("请输入信息(退出输入e或exit):"); // 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String s = null; try {     while ((s = br.readLine()) != null) { // 读取用户输入的一行数据 --> 阻塞程序  if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {      System.out.println("安全退出!!");      break;  }  // 将读取到的整行字符串转成大写输出  System.out.println("-->:" + s.toUpperCase());  System.out.println("继续输入信息:");     } } catch (IOException e) {     e.printStackTrace(); } finally {     try {  if (br != null) {      br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流  }     } catch (IOException e) {  e.printStackTrace();     } }    }}
完成如下题目要求:public class Test {     public static void main(String[] args) {  int a = 10;  int b = 10;  // 需要在method方法被调用之后,仅打印出 a = 100,b = 200,请写出method方法的代码  method(a,b);  System.out.println("a = " + a);  System.out.println("b = " + b);     }     // 代码编写处 }
public static void method(int a,int b) {    System.out.print("a=100,b=200");    System.exit(0);}public static void method(int a,int b) {    PrintStream stream = new PrintStream(System.out){ @Override public void println(String x) {     if ("a = 10".equals(x)) {  x = "a=100";     } else if ("b = 10".equals(x)) {  x = "b=200";     }     super.println(x); }    };    System.setOut(stream);}

七、打印流

        实现将基本数据类型的数据格式转化为字符串输出。

        打印流:PrintStream和PrintWriter。
                提供了一系列重载的print()和println()方法,用于多种数据类型的输出。
                PrintStream和PrintWriter的输出不会抛出IOException异常。
                PrintStream和PrintWriter有自动flush功能。
                PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
                System.out返回的是PrintStream的实例。

PrintStream ps = null;try {    FileOutputStream fos = new FileOutputStream(new File("D:\\IO\\text.txt"));    // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)    ps = new PrintStream(fos, true);    if (ps != null) {// 把标准输出流(控制台输出)改成文件 System.setOut(ps);    }    for (int i = 0; i <= 255; i++) { // 输出ASCII字符 System.out.print((char) i); if (i % 50 == 0) { // 每50个数据一行     System.out.println(); // 换行 }     }} catch (FileNotFoundException e) {    e.printStackTrace();} finally {    if (ps != null) { ps.close();    } }

八、数据流

        为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。

        数据流有两个类:(用于读取和写出基本数据类型、String类的数据)
                DataInputStream 和 DataOutputStream。
                分别“套接”在 InputStream 和 OutputStream 子类的流上。

        DataInputStream中的方法:
                boolean readBoolean()        byte readByte()
                char readChar()                float readFloat()
                double readDouble()            short readShort()
                long readLong()                int readInt()
                String readUTF()            void readFully(byte[] b)

        DataOutputStream中的方法:
                将上述的方法的read改为相应的write即可。

DataOutputStream dos = null;try { // 创建连接到指定文件的数据输出流对象    dos = new DataOutputStream(new FileOutputStream("destData.txt"));    dos.writeUTF("我爱北京天安门"); // 写UTF字符串    dos.writeBoolean(false); // 写入布尔值    dos.writeLong(1234567890L); // 写入长整数    System.out.println("写文件成功!");} catch (IOException e) {    e.printStackTrace();} finally { // 关闭流对象    try { if (dos != null) {     // 关闭过滤流时,会自动关闭它包装的底层节点流     dos.close(); }    } catch (IOException e) { e.printStackTrace();    } }
DataInputStream dis = null;try {    dis = new DataInputStream(new FileInputStream("destData.txt"));    String info = dis.readUTF();    boolean flag = dis.readBoolean();    long time = dis.readLong();    System.out.println(info);    System.out.println(flag);    System.out.println(time);} catch (Exception e) {    e.printStackTrace();} finally {    if (dis != null) { try {     dis.close(); } catch (IOException e) {     e.printStackTrace(); }     } }

九、序列化-对象流

        ObjectInputStream和ObjectOutputSteam,用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

        序列化:用ObjectOutputStream类保存基本类型数据或对象的机制。

        反序列化:用ObjectInputStream类读取基本类型数据或对象的机制。

        ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。

9.1 序列化概述

        对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
                1、序列化:指允许把堆内存中的Java对象数据转换成平台无关的二进制流,从而允许把这种二进制流持久地存储到磁盘文件中,或通过网络将这种二进制流传输到另一个网络节点(网络传输)。这个过程称为序列化,通常是指将数据结构或对象转化成字节序列的过程。该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
                    即将对象转化为二进制,用于保存,或者网络传输。
                2、反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生成的字节序列转换成数据结构或者对象的过程。
                    与序列化相反,将二进制转化成对象。

        序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。

        序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是JavaEE 平台的基础。

        如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常。
                Serializable:类通过实现 java.io.Serializable 接口以启用其序列化功能(推荐)。
                Externalizable:Externalizable 实例类的唯一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。
    
        通常建议:程序创建的每个JavaBean类都实现Serializeable接口。

Serializable接口面试题:

        谈谈你对java.io.Serializable接口的理解,我们知道它用于序列化,是空方法接口,还有其它认识吗?

        实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。

        由于大部分作为参数的类如String、Integer等都实现了java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更灵活。

9.2 序列化实现的方式

        如果需要将某个对象保存到磁盘上或者通过网络传输,那么这个类应该实现Serializable接口;
                Serializable接口是一个标记接口,不用实现任何方法。一旦实现了此接口,该类的对象就是可序列化的。

        凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
                private static final long serialVersionUID;
                serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
                如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。

        简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

        在Java的OutputStream类下面的子类 ObjectOutputStream 类就有对应的 writeObject(Object object) 其中要求对应的object实现了java的序列化的接口。
    
        在使用tomcat开发JavaEE相关项目的时候,我们关闭tomcat后,相应的session中的对象就存储在了硬盘上,如果我们想要在tomcat重启的时候能够从tomcat上面读取对应session中的内容,那么保存在session中的内容就必须实现相关的序列化操作,还有jdbc加载驱动用的就是反序列化,将字符串变为对象;

9.2.1 普通序列化

        若某个类实现了 Serializable 接口,该类的对象就是可序列化的:

        序列化步骤:
                步骤一:创建一个ObjectOutputStream输出流;
                步骤二:调用ObjectOutputStream对象的writeObject(对象)方法输出可序列化对象。
                注意写出一次,操作flush()一次。

import org.junit.Test;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class WriteObject {    /*     * 使用ObjectOutputStream实现对象序列化     *  1、序列号字符串对象     *  2、序列号自定义对象     */    @Test    public void testObjectOutputStream01() throws IOException { // 创建一个ObjectOutputStream输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.dat")); // 将字符串对象序列化到文件 oos.writeObject(new String("我爱北京天安门")); oos.flush();//刷新操作 // 将自定义对象序列化到文件:如果Person没有实现Serializable接口,则抛出java.io.NotSerializableException异常 Person person = new Person("张三", 23); oos.writeObject(person); oos.flush(); oos.close();    }}import java.io.Serializable;/** * Person需要满足如下的要求,方可序列化 * 1.需要实现接口:Serializable * 2.当前类提供一个全局常量:serialVersionUID - 后面演示 * 3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性 * 也必须是可序列化的。(默认情况下,基本数据类型可序列化) *  * 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量 */public class Person implements Serializable {    private String name;    private int age;    public Person() {    }    public Person(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 "Person{" +  "name='" + name + '\'' +  ", age=" + age +  '}';    }}

9.2.2 反序列化

        反序列化步骤:
                步骤一:创建一个ObjectInputStream输入流;
                步骤二:调用ObjectInputStream对象的readObject()得到序列化的对象。
    
        强调:如果某个类的属性不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的Field 的类也不能序列化。

        我们将上面序列化到object.dat的字符串和person对象反序列化回来。

import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectInputStream;public class ReadObject {    public static void main(String[] args) throws IOException, ClassNotFoundException { //创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.dat")); //反序列化字符串对象 Object obj = ois.readObject(); String str = (String) obj; //反序列化Person对象 Person p = (Person) ois.readObject(); System.out.println(str); // 我爱北京天安门 System.out.println(p);// Person{name='张三', age=23}  ois.close();    }}import java.io.Serializable;public class Person implements Serializable {    private String name;    private int age;    public Person() { System.out.println("反序列化,你调用我了吗? -- 无参构造函数");    }    public Person(String name, int age) { System.out.println("反序列化,你调用我了吗? -- 有参构造函数"); 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 "Person{" +  "name='" + name + '\'' +  ", age=" + age +  '}';    }}

        输出结果告诉我们,反序列化并不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。

9.2.3 成员是引用的序列化

        如果一个可序列化的类的成员不是基本类型,也不是String类型,那这个引用类型也必须是可序列化的;否则,会导致此类不能序列化。

        看例子,我们新增一个Teacher类。将Person去掉实现Serializable接口代码。

public class Person {    private String name;    private int age;    public Person(){     }    public Person(String name, int age) { this.name = name; this.age = age;    } // 略    @Override    public String toString() { return "Person{" +  "name='" + name + '\'' +  ", age=" + age +  '}';    }}import java.io.FileOutputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class Teacher implements Serializable {    private String name;    private Person person;    public Teacher(){} public Teacher(String name, Person person) { this.name = name; this.person = person;    } public String getName() { return name;    }    public void setName(String name) { this.name = name;    }    public Person getPerson() { return person;    }    public void setPerson(Person person) { this.person = person;    }    public static void main(String[] args) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/teacher.txt")); Person person = new Person("路飞", 20); Teacher teacher = new Teacher("雷利", person); oos.writeObject(teacher);    }}

         我们看到程序直接报错,因为Person类的对象是不可序列化的,这导致了Teacher的对象不可序列化;只需要把Person实现序列化接口即可;

9.2.4 同一对象序列化多次的机制

        同一对象序列化多次,会将这个对象序列化多次吗?答案是否定的。

import java.io.FileOutputStream;import java.io.ObjectOutputStream;public class WriteTeacher {    public static void main(String[] args) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.dat")); Person person = new Person("路飞", 20); Teacher t1 = new Teacher("雷利", person); Teacher t2 = new Teacher("红发香克斯", person); //依次将4个对象写入输入流 oos.writeObject(t1); oos.writeObject(t2); oos.writeObject(person); oos.writeObject(t2);    }}

        依次将t1、t2、person、t2对象序列化到文件teacher.dat文件中。

        注意:反序列化的顺序与序列化时的顺序一致。

        注意:多次序列化最好提供序列化ID(否则反序列化会有问题),我们让IDEA自动给生成即可;File->Settings

 

import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.ObjectInputStream;public class ReadTeacher {    public static void main(String[] args) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/teacher.txt")); Teacher t1 = (Teacher) ois.readObject(); Teacher t2 = (Teacher) ois.readObject(); Person p = (Person) ois.readObject(); Teacher t3 = (Teacher) ois.readObject(); System.out.println(t1 == t2); System.out.println(t1.getPerson() == p); System.out.println(t2.getPerson() == p); System.out.println(t2 == t3); System.out.println(t1.getPerson() == t2.getPerson());    }}// 输出结果// false// true// true// true// true

        从输出结果可以看出,Java序列化同一对象,并不会将此对象序列化多次得到多个对象。

9.3 Java序列化算法

        所有保存到磁盘的对象都有一个序列化编码号;

        当程序试图序列化一个对象时,会先检查此对象是否已经序列化过,只有此对象从未(在此虚拟机)被序列化过,才会将此对象序列化为字节序列输出。

        如果此对象已经序列化过,则直接输出编号即可。

图示上述序列化过程。

9.4 序列化版本号serialVersionUID

        我们知道,反序列化必须拥有class文件,但随着项目的升级,class文件也会升级,序列化怎么保证升级前后的兼容性呢?

        java序列化提供了一个private static final long serialVersionUID 的序列化版本号,只有版本号相同,即使更改了序列化属性,对象也可以正确被反序列化回来。

import org.junit.Test;import java.io.*;public class WriteObject {    /*     * 如果我们没有对Person添加序列号ID,Person序列号之后,Person类发生了改变,那么反序列化失败     * 如果对Person添加序列号ID,Person序列号之后,Person类发生了改变,那么也可以反序列化成功     */    @Test    public void testObjectOutputStream01() throws IOException { // 创建一个ObjectOutputStream输出流 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.dat")); // 将自定义对象序列化到文件 Person person = new Person("张三", 23); oos.writeObject(person); oos.flush(); oos.close();    }    @Test    public void testObjectInputStream01() throws IOException, ClassNotFoundException { //创建一个ObjectInputStream输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.dat")); //反序列化Person对象 Person p = (Person) ois.readObject(); System.out.println(p);// Person{name='张三', age=23, sex=0} ois.close();    }}

        如果反序列化使用的class的版本号与序列化时使用的不一致,反序列化会报InvalidClassException异常。

        序列化版本号可自由指定,如果不指定,JVM会根据类信息自己计算一个版本号,这样随着class的升级,就无法正确反序列化;不指定版本号另一个明显隐患是,不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化。

        建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。

9.5 transient关键字

        以上序列化和反序列化实现了的对象序列化,但是可以发现,操作时是将整个对象的所有属性序列化,那么transient关键字可以将某些内容不需要保存,就可以通过transient关键字来定义;

        private transient string title;

        此时title属性无法被序列化;

十、随机存取文件流

        RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。

        RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件。
                支持只访问文件的部分内容。
                可以向已存在的文件后追加内容。

        RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。

        RandomAccessFile 类对象可以自由移动记录指针:
                long getFilePointer():获取文件记录指针的当前位置。
                void seek(long pos):将文件记录指针定位到 pos 位置。

构造器和访问模式:

构造器:public RandomAccessFile(File file, String mode)创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。public RandomAccessFile(String name, String mode)创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:r: 以只读方式打开;rw:打开以便读取和写入;rwd:打开以便读取和写入;同步文件内容的更新;rws:打开以便读取和写入;同步文件内容和元数据的更新;如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则不会创建。我们可以用RandomAccessFile这个类,来实现一个多线程断点下载的功能,用过下载工具的朋友们都知道,下载前都会建立两个临时文件,一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件,每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能,有兴趣的可以自己实现下。

示例代码:

import org.junit.Test;import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;/** * RandomAccessFile的使用 * 1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口 * 2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流 * * 3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。 *   如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖) * * 4. 可以通过相关的操作,实现RandomAccessFile“插入”数据的效果 */public class RandomAccessFileTest {    // RandomAccessFile实现文件读写    @Test    public void test1() { RandomAccessFile raf1 = null; RandomAccessFile raf2 = null; try {     //1.     raf1 = new RandomAccessFile(new File("爱情与友情.jpg"),"r");     raf2 = new RandomAccessFile(new File("爱情与友情1.jpg"),"rw");     //2.     byte[] buffer = new byte[1024];     int len;     while((len = raf1.read(buffer)) != -1){  raf2.write(buffer,0,len);     } } catch (IOException e) {     e.printStackTrace(); } finally {     //3.     if(raf1 != null){  try {      raf1.close();  } catch (IOException e) {      e.printStackTrace();  }     }     if(raf2 != null){  try {      raf2.close();  } catch (IOException e) {      e.printStackTrace();  }     } }    }    // RandomAccessFile写入操作    @Test    public void test2() throws IOException { RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw"); raf1.seek(3);//将指针调到角标为3的位置 raf1.write("xyz".getBytes());// raf1.close();    }    /*    使用RandomAccessFile实现数据的插入效果     */    @Test    public void test3() throws IOException { RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw"); raf1.seek(3);//将指针调到角标为3的位置 //保存指针3后面的所有数据到StringBuilder中 StringBuilder builder = new StringBuilder((int) new File("hello.txt").length()); byte[] buffer = new byte[20]; int len; while((len = raf1.read(buffer)) != -1){     builder.append(new String(buffer,0,len)) ; } //调回指针,写入“xyz” raf1.seek(3); raf1.write("xyz".getBytes()); //将StringBuilder中的数据写入到文件中 raf1.write(builder.toString().getBytes()); raf1.close();    }}

RandomAccessFile实现插入操作示例代码2:

RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");raf1.seek(5);//方式一://StringBuilder info = new StringBuilder((int) file.length());//byte[] buffer = new byte[10];//int len;//while((len = raf1.read(buffer)) != -1){info += new String(buffer,0,len);//info.append(new String(buffer,0,len));//}//方式二:ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[10];int len;while((len = raf1.read(buffer)) != -1){    baos.write(buffer, 0, len);}raf1.seek(5);raf1.write("xyz".getBytes());raf1.write(baos.toString().getBytes());baos.close();raf1.close();

流的基本应用小节

        流是用来处理数据的。

        处理数据时,一定要先明确数据源,与数据目的地。
                数据源可以是文件,可以是键盘。
                数据目的地可以是文件、显示器或者其他设备。

        而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理、转换处理等。

十一、第三方jar实现文件读写

        1、引入commons-io-2.5.jar。
    
        2、通过FileUtils实现文件读写。

import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;public class FileUtilsTest {    public static void main(String[] args) { File srcFile = new File("爱情与友情.jpg"); File destFile = new File("爱情与友情2.jpg"); try {     FileUtils.copyFile(srcFile,destFile); } catch (IOException e) {     e.printStackTrace(); }    }}

字体下载