freyhe

导航

11.IO流

11.File类的使用

1.File类的理解

1.File类:一个文件或一个文件目录(俗称:文件夹)的抽象表示形式

2.File类声明在java.io包下

3.File类能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。

​ 如果需要访问文件内容本身(到写入或读取文件内容),则需要使用输入/输出流。

4.File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点"

5.想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,

​ 但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。

2.File的实例化

2.1 常用构造器
File(String filePath)
File(String parentPath,String childPath)
File(File parentFile,String childPath)

2.2 路径的分类
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径

说明:
IDEA中:
如果大家开发使用JUnit中的单元测试方法测试,相对路径即为当前Module下。
如果大家使用main()测试,相对路径即为当前的Project下。
Eclipse中:
不管使用单元测试方法还是使用main()测试,相对路径都是当前的Project下。

2.3 路径分隔符
windows和DOS系统默认使用“\”来表示
UNIX和URL使用“/”来表示

3.File类的常用方法

File类的获取功能

public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。 public long lastModified() :获取最后一次的修改时间,毫秒值
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组

File类的重命名功能

public boolean renameTo(File dest):把文件重命名为指定的文件路径

File类的判断功能

public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏

File类的创建功能

public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。 
public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建(创建多级目录)
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。

File类的删除功能

public boolean delete():删除文件或者文件夹
删除注意事项:
Java中的删除不走回收站。 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录(只能删空目录/文件)

4.路径分隔符

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

  • 路径分隔符和系统有关:

    ​ windows和DOS系统默认使用“\”来表示

    ​ UNIX和URL使用“/”来表示

  • Java程序支持跨平台(不同操作系统)运行,因此路径分隔符要慎用。

  • 为了解决这个隐患,File类提供了一个常量:

    public static final String separator ---能根据操作系统,动态的提供分隔符

举例:

File file1 = new File("d:\\atguigu\\info.txt");
File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt");
File file3 = new File("d:/atguigu");

2.IO流概述

​ IO流操作是相对于计算机的内存来进行操作,写入内存即为Input,从内存读取到本地即为Output

1.Java IO原理

  1. I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。
  2. Java程序中,对于数据的输入/输出操作以 “流(stream)” 的方式进行。
  3. java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

2.流的分类

1.按照操作数据单位分类:字节流(1byte即8bit)、字符流(2byte即16bit)

2.按照数据的流向分类:输入流、输出流

3.按照流的角色分类:节点流、处理流

3.IO流包含哪些类

抽象基类 节点流(或文件流) 缓冲流(处理流的一种)
InputStream FileInputStream (read(byte[] buffer)) BufferedInputStream (read(byte[] buffer)) / read()
OutputStream FileOutputStream (write(byte[] buffer,0,len) BufferedOutputStream (write(byte[] buffer,0,len) / flush()
Reader FileReader (read(char[] cbuf)) BufferedReader (read(char[] cbuf) / readLine())
Writer FileWriter (write(char[] cbuf,0,len) BufferedWriter (write(char[] cbuf,0,len) / flush()

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

    最基本的是四个抽象类,InputStream、OutputStream、Reader、Writer。
    最基本的方法也就是read()和write() 方法,其他流都是上面这四类流的子类,方法也是通过这两类方法衍生而成的。

    而且大部分的 IO 源码都是 native 标志的,也就是说源码都是 C/C++ 写的。

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

    image-20220111221040296
image-20220111221351145

3.节点流(或文件流)

1.FileReader/FileWriter

1.1 FileReader的使用

​ 将day09下的hello.txt文件内容读入程序中,并输出到控制台

说明:

  1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
  2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
  3. 读入的文件一定要存在,否则就会报FileNotFoundException。
//对read()操作升级:使用read的重载方法
@Test
public void testFileReader1()  {
    FileReader fr = null;
    try {
        //1.File类的实例化
        File file = new File("hello.txt");

        //2.FileReader流的实例化
        fr = new FileReader(file);

        //3.读入的操作
        //read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
        char[] cbuf = new char[5];
        int len;
        while((len = fr.read(cbuf)) != -1){
           
           //错误的写法
           //for(int i = 0;i < cbuf.length;i++){
           //System.out.print(cbuf[i]);
           //实际值:helloworld123->打印结果:helloworld123ld(ld是上次cbuf搬运的残留,只把123覆盖给了cbuf的前3位)
                         }
           //正确的写法
            for(int i = 0;i < len;i++){
                System.out.print(cbuf[i]);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fr != null){
            //4.资源的关闭
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

1.2 FileWriter的使用

从内存中写出数据到硬盘的文件里。

说明:

  1. 输出操作,对应的File可以不存在的。并不会报异常
  2. File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
    File对应的硬盘中的文件如果存在:
    如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原文件的覆盖
    如果流使用的构造器是:FileWriter(file,true):不会对原文件覆盖,而是在原文件基础上追加内容
@Test
public void testFileWriter() {
    FileWriter fw = null;
    try {
        //1.提供File类的对象,指明写出到的文件
        File file = new File("hello1.txt");
        //2.提供FileWriter的对象,用于数据的写出
        fw = new FileWriter(file,false);

        //3.写出的操作
        fw.write("I have a dream!\n");
        fw.write("you need to have a dream!");
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.流资源的关闭
        if(fw != null){

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

1.3 文本文件的复制:

@Test
public void testFileReaderFileWriter() {
    FileReader fr = null;
    FileWriter fw = null;
    try {
        //1.创建File类的对象,指明读入和写出的文件
        File srcFile = new File("hello.txt");
        File destFile = new File("hello2.txt");
        //不能使用字符流来处理图片等字节数据
        //            File srcFile = new File("爱情与友情.jpg");
        //            File destFile = new File("爱情与友情1.jpg");
        //2.创建输入流和输出流的对象
        fr = new FileReader(srcFile);
        fw = new FileWriter(destFile);
        //3.数据的读入和写出操作
        char[] cbuf = new char[5];
        int len;//记录每次读入到cbuf数组中的字符的个数
        while((len = fr.read(cbuf)) != -1){
            //每次写出len个字符
            fw.write(cbuf,0,len);

        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流资源
        //方式一:
        //            try {
        //                if(fw != null)
        //                    fw.close();
        //            } catch (IOException e) {
        //                e.printStackTrace();
        //            }finally{
        //                try {
        //                    if(fr != null)
        //                        fr.close();
        //                } catch (IOException e) {
        //                    e.printStackTrace();
        //                }
        //            }
        //方式二:
        try {
            if(fw != null)
                fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            if(fr != null)
                fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.FileInputStream / FileOutputStream

  1. 对于文本文件(.txt, .java, .c, .cpp),使用字符流处理
  2. 对于非文本文件(.jpg, .mp3, .mp4, .avi, .doc, .ppt, ...),使用字节流处理
 /* 实现对图片的复制操作*/
@Test
public void testFileInputOutputStream()  {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        //1.造文件
        File srcFile = new File("爱情与友情.jpg");
        File destFile = new File("爱情与友情2.jpg");

        //2.造流
        fis = new FileInputStream(srcFile);
        fos = new FileOutputStream(destFile);

        //3.复制的过程
        byte[] buffer = new byte[5];
        int len;
        while((len = fis.read(buffer)) != -1){
            fos.write(buffer,0,len);
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fos != null){
            //4.关闭流
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fis != null){
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

【注意】相对路径在IDEA和Eclipse中使用的区别?
IDEA:
如果使用单元测试方法,相对路径基于当前的Module的。
如果使用main()测试,相对路径基于当前Project的。

​ Eclipse:
​ 单元测试方法还是main(),相对路径都是基于当前Project的。

4.缓冲流的使用

1.缓冲流涉及到的类:

  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter

2.作用

作用:提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区。默认情况下是8kb(即8192b)

public class BufferedReader extends Reader {
    private char cb[]; //默认构造方法中 cb = new char[defaultCharBufferSize];
    private static int defaultCharBufferSize = 8192;
}

3.典型代码

3.1 使用BufferedInputStream和BufferedOutputStream:处理非文本文件

//实现文件复制的方法
public void copyFileWithBuffered(String srcPath,String destPath){
    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;

    try {
        //1.造文件
        File srcFile = new File(srcPath);
        File destFile = new File(destPath);
        //2.造流
        //2.1 造节点流
        FileInputStream fis = new FileInputStream((srcFile));
        FileOutputStream fos = new FileOutputStream(destFile);
        //2.2 造缓冲流
        bis = new BufferedInputStream(fis);
        bos = new BufferedOutputStream(fos);

        //3.复制的细节:读取、写入
        byte[] buffer = new byte[1024];
        int len;
        while((len = bis.read(buffer)) != -1){
            bos.write(buffer,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.资源关闭
        //要求:先关闭外层的流,再关闭内层的流
        if(bos != null){
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(bis != null){
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
//        fos.close();
//        fis.close();
    }
}

3.2 使用BufferedReader和BufferedWriter:处理文本文件

    @Test
    public void testBufferedReaderBufferedWriter(){
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            //创建文件和相应的流
            br = new BufferedReader(new FileReader(new File("dbcp.txt")));
            bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));

            //读写操作
            //方式一:使用char[]数组
//            char[] cbuf = new char[1024];
//            int len;
//            while((len = br.read(cbuf)) != -1){
//                bw.write(cbuf,0,len);
//    //            bw.flush();
//            }
            //方式二:使用String
            String data;
            while((data = br.readLine()) != null){
                //方法一:
//                bw.write(data + "\n");//data中不包含换行符
                //方法二:
                bw.write(data);//data中不包含换行符
                bw.newLine();//提供换行的操作
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if(bw != null){

                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.流的关闭

FileReader fileReader = new FileReader(new File("dbcp.txt"));
FileWriter fileWriter = new FileWriter(new File("dbcp1.txt"));

BufferedReader buReader = new BufferedReader(fileReader );
BufferedWriter buffWriter = new BufferedWriter(fileWriter );

两个注意点

1.关闭外层流(buReader、buffWriter)的同时,内层流(fileReader、fileWriter)也会自动的进行关闭。关于内层流的关闭,我们可以省略.

2.先关闭外层的流,再关闭内层的流(因为1,内层流的关闭省略不用写)

5.转换流的使用

1.转换流涉及到的类

属于字符流(都是处理流)

InputStreamReader:将一个字节的输入流转换为字符的输入流
解码:字节、字节数组 --->字符数组、字符串

OutputStreamWriter:将一个字符的输出流转换为字节的输出流
编码:字符数组、字符串 ---> 字节、字节数组

说明:编码决定了解码的方式

2.作用:

提供字节流与字符流之间的转换

3.图示

image-20220112215931952

4.典型代码

/*
此时处理异常的话,仍然应该使用try-catch-finally
InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
 */
@Test
public void test1() throws IOException {

    FileInputStream fis = new FileInputStream("dbcp.txt");
//        InputStreamReader isr = new InputStreamReader(fis);//使用系统默认的字符集
    //参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
    InputStreamReader isr = new InputStreamReader(fis,"UTF-8");//使用系统默认的字符集

    char[] cbuf = new char[20];
    int len;
    while((len = isr.read(cbuf)) != -1){
        String str = new String(cbuf,0,len);
        System.out.print(str);
    }

    isr.close();

}

/*
此时处理异常的话,仍然应该使用try-catch-finally

综合使用InputStreamReader和OutputStreamWriter
 */
@Test
public void test2() throws Exception {
    //1.造文件、造流
    File file1 = new File("dbcp.txt");
    File file2 = new File("dbcp_gbk.txt");

    FileInputStream fis = new FileInputStream(file1);
    FileOutputStream fos = new FileOutputStream(file2);

    InputStreamReader isr = new InputStreamReader(fis,"utf-8");
    OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");

    //2.读写过程
    char[] cbuf = new char[20];
    int len;
    while((len = isr.read(cbuf)) != -1){
        osw.write(cbuf,0,len);
    }
    //3.关闭资源
    isr.close();
    osw.close();
}

5.说明 & 常见字符集、编码

文件编码的方式(比如:GBK),决定了解析时使用的字符集(也只能是GBK)。

ASCIIUnicode是字符集,Unicode是基于ASCII扩展出来的编码;而GBKUTF8是编码规则。

ANSI编码,通常指的是平台的默认编码,例如英文操作系统中是ISO-8859-1,中文系统是GBK

Unicode字符集只是定义了字符的集合和唯一编号,Unicode编码,则是对UTF-8UCS-2/UTF-16等具体编码方案的统称而已,并不是具体的编码方案。

1.常见的编码表

ASCII:美国标准信息交换码 用一个字节的7位可以表示。
ISO8859-1:拉丁码表。欧洲码表 用一个字节的8位表示。
GB2312:中国的中文编码表 最多两个字节编码所有字符
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
Unicode:国际标准码,融合了目前人类使用的所字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

2.对后面学习的启示

客户端/浏览器端 <----> 后台(java,GO,Python,Node.js,php) <----> 数据库

要求前前后后使用的字符集都要统一:UTF-8.

  • 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

    计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。

    常见字符集有ASCII字符集、GB字符集、Unicode字符集等。

  1. 字符集:一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point)

    int a = (int)'你';   // 这里是获取到字符 你  所对应的 Unicode编码
    System.out.println(a);
    
  2. 编码规则:将「码位」转换为字节序列的规则s(编码/解码 可以理解为 加密/解密 的过程)。

    String x = "你";
    System.out.println(Arrays.toString(x.getBytes()));
    
image-20220116220129540

可见,当指定了编码,它所对应的字符集自然就指定了,所以编码才是我们最终要关心的。

  • ASCII字符集:

    • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
    • 基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。ASCII的扩展字符集使用8位(bits)表示一个字符,共256字符,方便支持欧洲常用字符。
  • ISO-8859-1字符集:

    • 拉丁码表,别名Latin-1,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
    • ISO-8859-1使用单字节编码,兼容ASCII编码。
  • GBxxx字符集:

    • GB就是国标的意思,是为了显示中文而设计的一套字符集。
    • GB2312:简体中文码表。一个小于127的字符的意义与原来相同。但两个大于127的字符连在一起时,就表示一个汉字,这样大约可以组合了包含7000多个简体汉字,此外数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
    • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等。
    • GB18030:最新的中文码表。收录汉字70244个,采用多字节编码,每个字可以由1个、2个或4个字节组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
  • Unicode字符集:

    • Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。

    • 它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。

    • UTF-8编码,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。所以,我们开发Web应用,也要使用UTF-8编码。它使用一至四个字节为每个字符编码,编码规则:

      1. 128个US-ASCII字符,只需一个字节编码。

      2. 拉丁文等字符,需要二个字节编码。

      3. 大部分常用字(含中文),使用三个字节编码。

      4. 其他极少使用的Unicode辅助字符,使用四字节编码。

        Unicode范围(十六进制) UTF8编码方式(二进制)
        0000~007F(即:0000 0000-0000 007F) 00000000
        0080~07FF(即:0000 0080-0000 07FF) 11000000 10000000
        0800~FFFF(即:0000 0800-0000 FFFF) 111000000 10000000 10000000
        10000~10FFFF (即:0001 0000-0010 FFFF) 11110000 10000000 10000000 10000000
image-20220116222349559

举例:

image-20220116222412472

6.其它的流的使用

1.标准的输入输出流

System.in:标准的输入流,默认从键盘输入
System.out:标准的输出流,默认从控制台输出

修改默认的输入和输出行为:
System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。

//Contain the methods for reading int, double, float, boolean, short, byte and
 // string values from the keyboard
//从键盘读取int, double, float, boolean, short, byte和字符串值的方法
public class MyInput {
    // Read a string from the keyboard
    public static String readString() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        // Declare and initialize the string
        String string = "";

        // Get the string from the keyboard
        try {
            string = br.readLine();

        } catch (IOException ex) {
            System.out.println(ex);
        }

        // Return the string obtained from the keyboard
        return string;
    }

    // Read an int value from the keyboard
    public static int readInt() {
        return Integer.parseInt(readString());
    }

    // Read a double value from the keyboard
    public static double readDouble() {
        return Double.parseDouble(readString());
    }

    // Read a byte value from the keyboard
    public static double readByte() {
        return Byte.parseByte(readString());
    }

    // Read a short value from the keyboard
    public static double readShort() {
        return Short.parseShort(readString());
    }

    // Read a long value from the keyboard
    public static double readLong() {
        return Long.parseLong(readString());
    }

    // Read a float value from the keyboard
    public static double readFloat() {
        return Float.parseFloat(readString());
    }
}

2.打印流:

PrintStream 和PrintWriter
说明:
提供了一系列重载的print()和println()方法,用于多种数据类型的输出
System.out返回的是PrintStream的实例

修改默认的输入和输出行为:
System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。

代码示例:将ASCII字符输出到D盘test.txt文件中

@Test
public void test2() {
    PrintStream ps = null;
    try {
        FileOutputStream fos = new FileOutputStream(new File("D:\\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();
        }
    }

}

3.数据流:

DataInputStream 和 DataOutputStream
作用:
用于读取或写出基本数据类型的变量或字符串

示例代码:

/*
将内存中的字符串、基本数据类型的变量写出到文件中。
注意:处理异常的话,仍然应该使用try-catch-finally.
 */
@Test
public void test3() throws IOException {
    //1.
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
    //2.
    dos.writeUTF("刘建辰");
    dos.flush();//刷新操作,将内存中的数据写入文件
    dos.writeInt(23);
    dos.flush();
    dos.writeBoolean(true);
    dos.flush();
    //3.
    dos.close();


}
/*
将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!
 */
@Test
public void test4() throws IOException {
    //1.
    DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
    //2.
    String name = dis.readUTF();
    int age = dis.readInt();
    boolean isMale = dis.readBoolean();

    System.out.println("name = " + name);
    System.out.println("age = " + age);
    System.out.println("isMale = " + isMale);

    //3.
    dis.close();

}

7.对象流的使用

1.对象流:

ObjectInputStream 和 ObjectOutputStream

2.作用:

ObjectOutputStream:内存中的对象--->存储中的文件、通过网络传输出去:序列化过程(编码)
ObjectInputStream:存储中的文件、通过网络接收过来 --->内存中的对象:反序列化过程(解码)

3.对象的序列化机制:

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘
上,或通过网络将这种二进制流传输到另一个网络节点。//当其它程序获取了这种二进制流,就可以恢复成原来的
Java对象

4.序列化代码实现:

/*
序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
使用ObjectOutputStream实现
 */
@Test
public void testObjectOutputStream(){
    ObjectOutputStream oos = null;

    try {
        //1.
        oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
        //2.
        oos.writeObject(new String("我爱北京天安门"));
        oos.flush();//刷新操作

        oos.writeObject(new Person("王铭",23));
        oos.flush();

        oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
        oos.flush();

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(oos != null){
            //3.
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

}

/*
反序列化:将磁盘文件中的对象还原为内存中的一个java对象
使用ObjectInputStream来实现
 */
@Test
public void testObjectInputStream(){
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(new FileInputStream("object.dat"));

        Object obj = ois.readObject();
        String str = (String) obj;

        Person p = (Person) ois.readObject();
        Person p1 = (Person) ois.readObject();

        System.out.println(str);
        System.out.println(p);
        System.out.println(p1);

    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        if(ois != null){
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

5.实现序列化的对象所属的类需要满足:

  1. 需要实现接口:Serializable/Externalizable

  2. 当前类提供一个全局常量:serialVersionUID

    serialVersionUID:可以理解为当前类的一个版本号,如果不显示声明,jvm也会自动生成,当前类字段等被修改时,自动生成的序列号也会被修改,则修改前序号化保持的文件想再次反序化为此类时会报错:(生成文本时的版本号与当前类版本号不一致即版本不兼容)

    java.io.InvalidClassException: com.atguigu.java.Person; local class incompatible: stream classdesc serialVersionUID = 475463534532, local class serialVersionUID = 3037084547681601652
    
  3. 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)

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

6.Externalizable与Serializable的异同

Externalizable接口继承自java.io.Serializable接口。

实现java.io.Serializable即可获得对类的对象的序列化功能。

而Externalizable可以通过writeExternal()readExternal()方法可以指定序列化哪些属性。

writeExternal()readExternal()这两个方法会在序列化和反序列化还原的过程中被自动调用

  1. 序列化内容
    Externalizable自定义序列化可以控制序列化的过程和决定哪些属性不被序列化

    Externalizable默认全部属性都不自动序列化,需要手动指明需要序列化的属性

  2. Serializable序列化时不会调用默认的构造器,而Externalizable序列化时会调用默认构造器

  3. 使用Externalizable时,必须按照写入时的确切顺序读取所有字段状态。否则会产生异常。例如,如果更改ExternalizableDemo类中的number和name属性的读取顺序,则将抛出java.io.EOFException。而Serializable接口没有这个要求。

7.使用对象流实现深拷贝

深浅拷贝详见 JavaSE笔记:07.Java常用类 String、System、时间API、Object、比较器、Math等

Student s1 = new Student(name:"张三", age:19, person:null);
// 序列化
ByteArrayOutputstream baos = new ByteArrayoutputstream();
ObjectOutputstream oos = new Objectoutputstream(baos);
oos.writeObject(s1);//将Person对象写入到ByteArrayOutputstream对象中
oos.flush();
// 反序列化
ByteArrayInputstream bai = new ByteArrayInputstream(baos.toByteArray());
ObjectInputstream ois new ObjectInputstream(bai);
Student s2 = (Student)ois.readobject();//从 ByteArrayOutputstream对象中读出Person对象,此时是个深拷贝出的对象
//修改s2的属性
s2.setName("李四");
s2.setAge(20);
s2.getPerson(new Person(name:"zhangsan",age:18));

System.out.println("s1:"+s1.tostring());// 在s2的属性被修改后,s1还是原址,并未被改变,所以是深拷贝

8.RandomAccessFile的使用

1.随机(任意)存取文件流:RandomAccessFile

2.使用说明:

1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口

2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流

3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。

如果写出到的文件存在,则会对原文件内容进行覆盖。(默认从头覆盖

4.可以通过相关的操作,实现RandomAccessFile“插入”数据的效果。seek(int pos) 即:从指定下吧pos开始进行覆盖操作 详见下面代码

3.典型代码

构造器说明

public RandomAccessFile(File file, String mode) 
public RandomAccessFile(String name, String mode) 
  • 创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式

    r: 以只读方式打开

    rw:打开以便读取和写入

    rwd:打开以便读取和写入;同步文件内容的更新

    rws:打开以便读取和写入;同步文件内容和元数据的更新

JDK1.6上面的每次write数据时,“rw”模式:数据不会立即写道硬盘中;“rwd”模式:数据立即写入硬盘、如果写数据过程发生异常,“rwd”模式中已经被write的数据保存到硬盘上,而“rw”模式则全部丢失

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();
            }

        }
    }
}

从指定下标位置开始进行覆盖操作

默认从指定的下标位置开始进行覆盖

@Test
public void test2() throws IOException {
    RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");//hellow.txt现在内容为 0123456
    raf1.seek(3);//将指针调到角标为3的位置 
    raf1.write("xyz".getBytes());//
    raf1.close(); // 现在输出为 012xyz6(默认从指定的下标位置开始进行覆盖)
}

从指定下标位置开始进行插入操作(ByteArrayOutputStream)

1.保存指定下标到末尾的数据


/*
使用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中的数据写入到文件中(上一步写操作,指针会跑到最后写入的“z”的位置,此时接着写入之前保存的数据即可)
    raf1.write(builder.toString().getBytes());

    raf1.close();
}

//将上面方法StringBuilder替换为ByteArrayOutputStream
@Test
public void test4() throws IOException {

    RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");

    raf1.seek(3);//将指针调到角标为3的位置
    //保存指针3后面的所有数据到ByteArrayOutputStream中
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] bytes = new byte[1024];
    int len = 0;
    while ((len = raf1.read(bytes)) > 0) {
        baos.write(bytes, 0, len);
    }
	//调回指针,写入“xyz”(前面读操作完成后,指针跟着读完成一起跑到流的最末尾,所以要重新调回指针)
    raf1.seek(3);
    raf1.write("xyz".getBytes());

   //将StringBuilder中的数据写入到文件中(上一步写操作,指针会跑到最后写入的“z”的位置,此时接着写入之前保存的数据即可)
    raf1.write(baos.toByteArray());

    raf1.close();
}

4.RandomAccessFile实现多线程断点下载

用过下载工具的朋友们都知道,下载前都会建立两个临时文件:一个是与被下载文件大小相同的空文件,另一个是记录文件指针的位置文件;

每次暂停的时候,都会保存上一次的指针,然后断点下载的时候,会继续从上一次的地方下载,从而实现断点下载或上传的功能

工具类代码--多线程断点下载

public class DownUtil {
    // 定义下载资源的路径
    private String path;
    // 指定所下载的文件的保存位置
    private String targetFile;
    // 定义需要使用多少线程下载资源
    private int threadNum;
    // 定义下载的线程对象
    private DownThread[] threads;
    // 定义下载的文件的总大小
    private int fileSize;

    public DownUtil(String path, String targetFile, int threadNum) {
        this.path = path;
        this.threadNum = threadNum;
        // 初始化threads数组
        threads = new DownThread[threadNum];
        this.targetFile = targetFile;
    }

    public void download() throws Exception {
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(5 * 1000);
        conn.setRequestMethod("GET");
        conn.setRequestProperty(
                "Accept",
                "image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
                        + "application/x-shockwave-flash, application/xaml+xml, "
                        + "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
                        + "application/x-ms-application, application/vnd.ms-excel, "
                        + "application/vnd.ms-powerpoint, application/msword, */*");
        conn.setRequestProperty("Accept-Language", "zh-CN");
        conn.setRequestProperty("Charset", "UTF-8");
        conn.setRequestProperty("Connection", "Keep-Alive");
        // 得到文件大小
        fileSize = conn.getContentLength();
        conn.disconnect();
        int currentPartSize = fileSize / threadNum + 1;//这里不必一定要加1,不加1也可以
        RandomAccessFile file = new RandomAccessFile(targetFile, "rw");
        // 设置本地文件的大小
        file.setLength(fileSize);
        file.close();
        for (int i = 0; i < threadNum; i++) {
            // 计算每条线程的下载的开始位置
            int startPos = i * currentPartSize;
            // 每个线程使用一个RandomAccessFile进行下载
            RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw");
            // 定位该线程的下载位置
            currentPart.seek(startPos);
            // 创建下载线程
            threads[i] = new DownThread(startPos, currentPartSize, currentPart);
            // 启动下载线程
            threads[i].start();
        }
    }

    // 获取下载的完成百分比
    public double getCompleteRate() {
        // 统计多条线程已经下载的总大小
        int sumSize = 0;
        for (int i = 0; i < threadNum; i++) {
            sumSize += threads[i].length;
        }
        // 返回已经完成的百分比
        return sumSize * 1.0 / fileSize;
    }

    private class DownThread extends Thread {
        // 当前线程的下载位置
        private int startPos;
        // 定义当前线程负责下载的文件大小
        private int currentPartSize;
        // 当前线程需要下载的文件块
        private RandomAccessFile currentPart;
        // 定义已经该线程已下载的字节数
        public int length;

        public DownThread(int startPos, int currentPartSize, RandomAccessFile currentPart) {
            this.startPos = startPos;
            this.currentPartSize = currentPartSize;
            this.currentPart = currentPart;
        }

        @Override
        public void run() {
            try {
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestMethod("GET");
                conn.setRequestProperty(
                        "Accept",
                        "image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
                                + "application/x-shockwave-flash, application/xaml+xml, "
                                + "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
                                + "application/x-ms-application, application/vnd.ms-excel, "
                                + "application/vnd.ms-powerpoint, application/msword, */*");
                conn.setRequestProperty("Accept-Language", "zh-CN");
                conn.setRequestProperty("Charset", "UTF-8");
                InputStream inStream = conn.getInputStream();
                // 跳过startPos个字节,表明该线程只下载自己负责哪部分文件。
                inStream.skip(this.startPos);
                byte[] buffer = new byte[1024];
                int hasRead = 0;
                // 读取网络数据,并写入本地文件
                while (length < currentPartSize
                        && (hasRead = inStream.read(buffer)) != -1) {
                    currentPart.write(buffer, 0, hasRead);
                    // 累计该线程下载的总大小
                    length += hasRead;
                }
                currentPart.close();
                inStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

测试上面的工具类

public class DownUtilTest {

    public static void main(String args[]) throws Exception {
        final DownUtil downUtil = new DownUtil("https://mirrors.cnnic.cn/apache/tomcat/tomcat-10/v10.0.14/bin/apache-tomcat-10.0.14-windows-x64.zip", "apache-tomcat-10.0.14-windows-x64.zip", 3);

        downUtil.download();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(downUtil.getCompleteRate()<1){
                    System.out.println("已完成:"+downUtil.getCompleteRate());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

9.Path、Paths、Files的使用

1.NIO的使用说明:

Java NIO (New IO,Non-Blocking IO)是从Java 1.4版本开始引入的一套新的IO API,可以替代标准的Java IO AP

NIO与原来的IO同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的(IO是面向流的)、基于通道的IO操作

NIO将以更加高效的方式进行文件的读写操作。(但NIO不够好用,并没有广泛使用)

随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2(使用较多)

Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。

  • java.nio.channels.Channel
    • FileChannel:处理本地文件
    • SocketChannel:TCP网络编程的客户端的Channel
    • ServerSocketChannel:TCP网络编程的服务器端的Channel
    • DatagramChannel:UDP网络编程中发送端和接收端的Channel

2.Path的使用 ---jdk7提供

2.1 Path的说明:

​ Path替换原有的File类。

​ 原因:File类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息

​ Path可以看成是File类的升级版本,实际引用的资源也可以不存在

2.2 Path如何实例化:

Paths 类提供的静态 get() 方法用来获取 Path 对象:

static Path get(String first, String … more) // 用于将多个字符串串连成路径
static Path get(URI uri) // 返回指定uri对应的Path路径
//eg: Path path = Paths.get("index.html");

2.3 Path常用方法:

String toString() : 返回调用 Path 对象的字符串表示形式
boolean startsWith(String path) : 判断是否以 path 路径开始
boolean endsWith(String path) : 判断是否以 path 路径结束
boolean isAbsolute() : 判断是否是绝对路径
Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
Path getRoot() :返回调用 Path 对象的根路径
Path getFileName() : 返回与调用 Path 对象关联的文件名
int getNameCount() : 返回Path 根目录后面元素的数量
Path getName(int idx) : 返回指定索引位置 idx 的路径名称
Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
File toFile(): 将Path转化为File类的对象

Path示例代码

public class PathTest {

    //如何使用Paths实例化Path
    @Test
    public void test1() {
        Path path1 = Paths.get("d:\\nio\\hello.txt");//new File(String filepath)

        Path path2 = Paths.get("d:\\", "nio\\hello.txt");//new File(String parent,String filename);

        System.out.println(path1);
        System.out.println(path2);

        Path path3 = Paths.get("d:\\", "nio");
        System.out.println(path3);
    }

    //Path中的常用方法
    @Test
    public void test2() {
        Path path1 = Paths.get("d:\\", "nio\\nio1\\nio2\\hello.txt");
        Path path2 = Paths.get("hello.txt");

//		String toString() : 返回调用 Path 对象的字符串表示形式
        System.out.println(path1);

//		boolean startsWith(String path) : 判断是否以 path 路径开始
        System.out.println(path1.startsWith("d:\\nio"));
//		boolean endsWith(String path) : 判断是否以 path 路径结束
        System.out.println(path1.endsWith("hello.txt"));
//		boolean isAbsolute() : 判断是否是绝对路径
        System.out.println(path1.isAbsolute() + "~");
        System.out.println(path2.isAbsolute() + "~");
//		Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
        System.out.println(path1.getParent());
        System.out.println(path2.getParent());
//		Path getRoot() :返回调用 Path 对象的根路径
        System.out.println(path1.getRoot());
        System.out.println(path2.getRoot());
//		Path getFileName() : 返回与调用 Path 对象关联的文件名
        System.out.println(path1.getFileName() + "~");
        System.out.println(path2.getFileName() + "~");
//		int getNameCount() : 返回Path 根目录后面元素的数量
//		Path getName(int idx) : 返回指定索引位置 idx 的路径名称
        for (int i = 0; i < path1.getNameCount(); i++) {
            System.out.println(path1.getName(i) + "*****");
        }

//		Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
        System.out.println(path1.toAbsolutePath());
        System.out.println(path2.toAbsolutePath());
//		Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
        Path path3 = Paths.get("d:\\", "nio");
        Path path4 = Paths.get("nioo\\hi.txt");
        path3 = path3.resolve(path4);
        System.out.println(path3);

//		File toFile(): 将Path转化为File类的对象
        File file = path1.toFile();//Path--->File的转换

        Path newPath = file.toPath();//File--->Path的转换

    }
}

3.Files工具类 ---jdk7提供

3.1 Files作用:

操作文件或文件目录的工具类

3.2常用方法:

一般常用的方法

Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除
Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
long size(Path path) : 返回 path 指定文件的大小

用于判断的方法

boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
boolean isHidden(Path path) : 判断是否是隐藏文件
boolean isReadable(Path path) : 判断文件是否可读
boolean isWritable(Path path) : 判断文件是否可写
boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在

用于操作内容的方法

SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
DirectoryStream<Path> newDirectoryStream(Path path) : 打开 path 指定的目录
InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象

Files示例代码

public class FilesTest {

	@Test
	public void test1() throws IOException{
		Path path1 = Paths.get("d:\\nio", "hello.txt");
		Path path2 = Paths.get("atguigu.txt");
		
//		Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
		//要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。
//		Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);
		
//		Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
		//要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
		Path path3 = Paths.get("d:\\nio\\nio1");
//		Files.createDirectory(path3);
		
//		Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
		//要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
		Path path4 = Paths.get("d:\\nio\\hi.txt");
//		Files.createFile(path4);
		
//		void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
//		Files.delete(path4);
		
//		void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
		Files.deleteIfExists(path3);
		
//		Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
		//要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
//		Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);
		
//		long size(Path path) : 返回 path 指定文件的大小
		long size = Files.size(path2);
		System.out.println(size);

	}

	@Test
	public void test2() throws IOException{
		Path path1 = Paths.get("d:\\nio", "hello.txt");
		Path path2 = Paths.get("atguigu.txt");
//		boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
		System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));

//		boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
		//不要求此path对应的物理文件存在。
		System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));

//		boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

//		boolean isHidden(Path path) : 判断是否是隐藏文件
		//要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
//		System.out.println(Files.isHidden(path1));

//		boolean isReadable(Path path) : 判断文件是否可读
		System.out.println(Files.isReadable(path1));
//		boolean isWritable(Path path) : 判断文件是否可写
		System.out.println(Files.isWritable(path1));
//		boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
		System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
	}

	/**
	 * StandardOpenOption.READ:表示对应的Channel是可读的。
	 * StandardOpenOption.WRITE:表示对应的Channel是可写的。
	 * StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
	 * StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
	 *
	 * @author shkstart 邮箱:shkstart@126.com
	 * @throws IOException
	 */
	@Test
	public void test3() throws IOException{
		Path path1 = Paths.get("d:\\nio", "hello.txt");

//		InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
		InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);

//		OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
		OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);


//		SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
		SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

//		DirectoryStream<Path>  newDirectoryStream(Path path) : 打开 path 指定的目录
		Path path2 = Paths.get("e:\\teach");
		DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
		Iterator<Path> iterator = directoryStream.iterator();
		while(iterator.hasNext()){
			System.out.println(iterator.next());
		}
	}
}

10.第三方jar包实现IO操作

1.复制文件 commons-io-2.5.jar

commons-io包详细操作见下面

public class FileUtilsTest {

    public static void main(String[] args) {
        File srcFile = new File("D:\\AAA1.jpg");
        File destFile = new File("D:\\AAA2.jpg");

        try {
            FileUtils.copyFile(srcFile,destFile);//FileUtils是commons-io包中的工具类
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.文件的上传和下载


  • 文件的上传和下载在web应用中是非常常用,也是非常有用的功能。

    • 例如:发送电子邮件时可以同过上传附件发送文件,OA系统中可以通过上传文件来提交公文,社交网站通过上传图片来自定义头像等等。
    • 例如:下载实际上只要资源放在用户可访问的目录中用户就可以直接通过地址下载,但是一些资源是存放到数据库中的,还有一些资源需要一定权限才能下载,这里就需要我们通过Servlet来完成下载的功能。
  • 可以说上传和下载是每一个web应用都需要具有的一个功能,所以需要我们掌握。

1.文件的上传

1.1 文件上传的步骤

文件的上传主要分成两个步骤:

  1. 用户在页面中选择要上传的文件,然后将请求提交到Servlet

  2. Servlet收到请求,解析用户上传的文件,然后将文件存储到服务器

1.2 创建上传文件的表单

  1. 创建一个form表单
<form action="" method="post" enctype="multipart/form-data">
	<input type="file" name="file" /><br /><br />
	<input type="submit" value="上传" />
</form>
  • 文件上传的表单和之前的表单类似,但有以下内容需要注意:

    • 表单的method属性必须为post
    • 表单的enctype属性必须为multipart/form-data
    • 上传文件的控件是input,type属性为file
  • 该表单打开后是如下效果:

    • IE

      1558975331009

    • Chrome

      1558975309963

    • 火狐

      1558975370024

  1. 编写Servelet

    • 页面的表单控件创建好以后,选中文件点击上传按钮请求将会提交到指定的Servlet来处理。

    • 注意:这里不能再像以前的Servlet中那样,通过request.getParamter()来获取请求参数了,当enctype="multipart/form-data" 时,再使用getParamter()获取到内容永远为空。因为浏览器发送请求的方式已经改变。

    • 既然以前的方法不能使用了,这里我们必须要引入一个新的工具来解析请求中的参数和文件,这个工具就是commons-fileupload。

1.3 commons-fileupload

  • commons-fileupload是Apache开发的一款专门用来处理上传的工具,它的作用就是可以从request对象中解析出,用户发送的请求参数和上传文件的流。

  • commons-fileupload包依赖commons-io,两个包需要同时导入。

  • 核心类:

    1. DiskFileItemFactory

      1. 工厂类,用于创建ServletFileUpload,设置缓存等

      2. 该类一般直接使用构造器直接创建实例

      3. 方法:

        • public void setSizeThreshold(int sizeThreshold):用于设置缓存文件的大小(默认值10kb)

        • public void setRepository(File repository):用于设置缓存文件位置(默认系统缓存目录)

    2. ServletFileUpload

      1. 该类用于解析request对象从而获取用户发送的请求参数(包括普通参数和文件参数)

      2. 该类需要调用有参构造器创建实例,构造器中需要一个DiskFileItemFactory作为参数

      3. 方法:

        • public List parseRequest(HttpServletRequest request):解析request对象,获取请求参数,返回的是一个List,List中保存的是一个FileItem对象,一个对象代表一个请求参数。

        • public void setFileSizeMax(long fileSizeMax):设置单个文件的大小限制,单位为B。如果上传文件超出限制,会在parseRequest()抛出异常FileSizeLimitExceededException。

        • public void setSizeMax(long sizeMax):限制请求内容的总大小,单位为B。如果上传文件超出限制,会在parseRequest()抛出异常SizeLimitExceededException。

    3. FileItem

      1. 该类用于封装用户发送的参数和文件,也就是用户发送来的信息将会被封装成一个FileItem对象,我们通过该对象获取请求参数或上传文件的信息。

      2. 该类不用我们手动创建,由ServletFileItem解析request后返回。

      3. 方法:

        • String getFieldName():获取表单项的名字,也就是input当中的name属性的值。

        • String getName():获取上传的文件名,普通的请求参数为null。

        • String getString(String encoding):获取内容,encoding参数需要指定一个字符集。

          ​ ① 若为文件,将文件的流转换为字符串。

          ​ ② 若为请求参数,则获取请求参数的value。

        • boolean isFormField():判断当前的FileItem封装的是普通请求参数,还是一个文件。

          ​ ① 如果为普通参数返回:true

          ​ ② 如果为文件参数返回:false

        • String getContentType():获取上传文件的MIME类型

        • long getSize():获取内容的大小

        • write():将文件上传到服务器

  • 示例代码:创建一个Servlet并在doPost()方法中编写如下代码

    //创建工厂类
    DiskFileItemFactory factory = new DiskFileItemFactory();
    //创建请求解析器
    ServletFileUpload fileUpload = new ServletFileUpload(factory);
    //设置上传单个文件的的大小
    fileUpload.setFileSizeMax(1024*1024*3);
    //设置上传总文件的大小
    fileUpload.setSizeMax(1024*1024*3*10);
    //设置响应内容的编码
    response.setContentType("text/html;charset=utf-8");
    try {
    	//解析请求信息,获取FileItem的集合
    	List<FileItem> items = fileUpload.parseRequest(request);
    	//遍历集合
    	for (FileItem fileItem : items) {
    		//如果是普通的表单项
    		if(fileItem.isFormField()){
    		    //获取参数名
    		    String fieldName = fileItem.getFieldName();
    		    //获取参数值
    		    String value = fileItem.getString("utf-8");
    		    System.out.println(fieldName+" = "+value);
    	        //如果是文件表单项
    	    }else{
    		    //获取文件名
    		    String fileName = fileItem.getName();
    		    //获取上传路径
    		    String realPath = getServletContext().getRealPath("/WEB-INF/upload");
    		    //检查upload文件夹是否存在,如果不存在则创建
    		    File f = new File(realPath);
    		    if(!f.exists()){
    			    f.mkdir();
    		    };
    		    //为避免重名生成一个uuid作为文件名的前缀
    		    String prefix = UUID.randomUUID().toString().replace("-", "");
    		    //将文件写入到服务器中
    		    fileItem.write(new File(realPath+"/"+prefix+"_"+fileName));
    		    //清楚文件缓存
    		    fileItem.delete();
    	    }
    }
    } catch (Exception e) {
    	if(e instanceof SizeLimitExceededException){
    		//文件总大小超出限制
    		response.getWriter().print("上传文件的总大小不能超过30M");
    	}else if(e instanceof FileSizeLimitExceededException){
    		//单个文件大小超出限制
    		response.getWriter().print("上传单个文件的大小不能超过3M");
    	}
    } 
    response.getWriter().print("上传成功");
    
    

2.文件的下载

2.1 使用说明

  • 文件下载最直接的方法就是把文件直接放到服务器的目录中,用户直接访问该文件就可以直接下载。

  • 但是实际上这种方式并不一定好用,比如我们在服务器上直接放置一个MP3文件,然后通过浏览器访问该文件的地址,如果是IE浏览器可能就会弹出下载窗口,而如果是FireFox和Chrome则有可能直接播放。再有就是有一些文件我们是不希望用户可以直接访问到的,这是我们就要通过Servlet来完成下载功能。

  • 下载文件的关键是几点:

    1. 服务器以一个流的形式将文件发送给浏览器。

    2. 发送流的同时还需要设置几个响应头,来告诉浏览器下载的信息。

      • 具体响应头如下:
        • Content-Type
          • 下载文件的MIME类型
          • 可以通过servletContext. getMimeType(String file)获取
          • 也可以直接手动指定
          • 使用response.setContentType(String type);
          • 响应头样式:Content-Type: audio/mpeg
        • Content-Disposition
          • 下载文件的名字,主要作用是提供一个默认的用户名
          • 通过response.setHeader("Content-Disposition", disposition)设置
          • 响应头样式:Content-Disposition: attachment; filename=xxx.mp3
        • Content-Length
          • 下载文件的长度,用于设置文件的长处(不必须)
          • 通过response. setContentLength(int len)设置。
          • 设置后样式:Content-Length: 3140995
    3. 接下来需要以输入流的形式读入硬盘上的文件

      • FileInputStream is = new FileInputStream(file);
      • 这个流就是我们一会要发送给浏览器的内容
    4. 通过response获取一个输出流,并将文件(输入流)通过该流发送给浏览器

      • 获取输出流:ServletOutputStream out = response.getOutputStream();

      • 通过输出流向浏览器发送文件(不要忘了关闭输入流)

        byte[] b = new byte[1024];
        int len = 0;
        while((len=is.read(b))> 0){
        	out.write(b, 0, len);
        }
        is.close();
        
        

2.2 步骤演示

  • 一下步骤都是在同一个Servlet的doGet()方法中编写的

  • 我所下载的文件是放在WEB-INF下mp3文件夹中的文件

  • 具体步骤

    1. 获取文件的流:

      String realPath = getServletContext().getRealPath("/WEB-INF/mp3/中国话.mp3");
      //获取文件的File对象
      File file = new File(realPath);
      //获取文件的输入流
      FileInputStream is = new FileInputStream(file);
      
      
    2. 获取头信息:

      //获取文件的MIME信息
      String contentType = getServletContext().getMimeType(realPath);
      //设置下载文件的名字
      String filename = "zhongguohua.mp3";
      //创建Content-Disposition信息
      String disposition = "attachment; filename="+ filename ;
      //获取文件长度
      long size = file.length();
      
      
    3. 设置头信息

      //设置Content-Type
      response.setContentType(contentType);
      //设置Content-Disposition
      response.setHeader("Content-Disposition", disposition);
      //设置文件长度
      response.setContentLength((int)size);
      
      
    4. 发送文件

      //通过response获取输出流,用于向浏览器输出内容
      ServletOutputStream out = response.getOutputStream();
      //将文件输入流通过输出流输出
      byte[] b = new byte[1024];
      int len = 0;
      while((len=is.read(b))> 0){
      	out.write(b, 0, len);
      }
      //最后不要忘记关闭输入流,输出流由Tomcat自己处理,我们不用手动关闭
      is.close();
      
      

2.3 乱码

  • 至此实际上文件下载的主要功能都已经完成。但是还有一个问题我们这里没有体现出来,因为目前我们的文件名使用的是纯英文的,没有乱码问题。这里如果我们要使用中文文件名的话,毫无疑问会出现乱码问题。

  • 解决此问题的方法很简单,在获取文件名之后为文件名进行编码:

filename = java.net.URLEncoder.encode(filename,"utf-8");
  • 但是注意这里火狐浏览器比较特殊,因为他默认是以BASE64解码的,所以这块如果需要考虑火狐的问题的话还需要特殊处理一下。

    1. 先要获取客户端信息(通过获取请求头中的User-Agent信息)
    //获取客户端信息
    String ua = request.getHeader("User-Agent");
    
    
    1. 然后判断浏览器版本,做不同的处理(通过判断头信息中是否包含Firefox字符串来判断浏览器版本)
    //判断客户端是否为火狐
    if(ua.contains("Firefox")){
    	//若为火狐使用BASE64编码
    	filename = "=?utf-8?B?"+new BASE64Encoder()
    .encode(filename.getBytes("utf-8"))+"?=";
    }else{
    	//否则使用UTF-8
    	filename = URLEncoder.encode(filename,"utf-8");
    }
    
    
    1. 谁问跟谁急
    String string = new String("你好.jpg".getBytes("gbk"), "iso8859-1");
    

commons-io简介:

java io操作是开发中比较常用的技术,但是如果每次都使用原生的IO流来操作会显得比较繁琐。

Common IO 是一个工具库,用来帮助开发IO功能, 它包括6个主要部分

​ Utility classes – 工具类,包括一些静态方法来执行常用任务
​ Input – 输入,InputStream 和 Reader 实现
​ Output – 输出,OutputStream 和 Writer 实现
​ Filters – 过滤器,多种文件过滤器实现(定义了 IOFileFilter接口,同时继承了 FileFilter 和 FilenameFilter 接口)
​ Comparators – 比较器,用于文件比较的多种java.util.Comparatot实现
​ File Monitor–文件监控

添加maven依赖:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

一、工具类:

IOUtils 包含一些工具类,用于处理读,写和拷贝,这些方法基于 InputStream, OutputStream, Reader 和 Writer工作.

FileUtils 包含一些工具类,它们基于File对象工作,包括读,写,拷贝和比较文件

FilenameUtils包含一些工具类,它们基于文件名工作而不是File对象。这个类旨在 在Unix和Windows环境下保持一致,帮助在两个环境下过渡(如从开发环境到生成环境)

FileSystemUtils包含一些工具类,基于文件系统访问功能不被JDK支持。目前,只有一个方法就是得到驱动器空余空间。注意这使用命令行而不是 native code。

EndianUtils 包含静态方法来交换Java基本类型和流的字节序

SwappedDataInputStream实现了DataInput接口。可以从文件中读取非本地字节序。

IOUtils的使用:

IOUtils.copy(InputStream input, OutputStream output) // 此方法有多个重载方法,满足不同的输入输出流

IOUtils.copy(InputStream input, OutputStream output, int bufferSize)

IOUtils.copy(InputStream input, Writer output, String inputEncoding)

IOUtils.copy(Reader input, Writer output)

IOUtils.copy(Reader input, OutputStream output, String outputEncoding)

// 这个方法适合拷贝较大的数据流,比如2G以上

IOUtils.copyLarge(Reader input, Writer output) // 默认会用1024*4的buffer来读取

IOUtils.copyLarge(Reader input, Writer output, char[] buffer)

IOUtils.toInputStream(String input, String encoding) // 通过文本获取输入流 , 可以指定编码格式

IOUtils.toInputStream(String input, Charset encoding)

IOUtils.toBufferedInputStream(InputStream input) // 获取一个缓冲输入流,默认缓冲大小 1KB

IOUtils.toBufferedInputStream(InputStream input, int size) // 获取一个指定缓冲流的大小的输入流

IOUtils.toBufferedReader(Reader reader) // 获取一个字符缓冲流

IOUtils.toBufferedReader(Reader reader, int size)

// 获取缓冲流

IOUtils.buffer(InputStream inputStream)

IOUtils.buffer(OutputStream outputStream)

IOUtils.buffer(Reader reader)

IOUtils.buffer(Writer writer)

// 将输入流转换成字符串

IOUtils.toString(Reader input)

IOUtils.toString(byte[] input, String encoding)

IOUtils.toString(InputStream input, Charset encoding)

IOUtils.toString(InputStream input, String encoding)

IOUtils.toString(URI uri, String encoding)

IOUtils.toString(URL url, String encoding)

// 将输入流转换成字符数组

IOUtils.toByteArray(InputStream input)

IOUtils.toByteArray(InputStream input, int size)

IOUtils.toByteArray(URI uri)

IOUtils.toByteArray(URL url)

IOUtils.toByteArray(URLConnection urlConn)

IOUtils.toByteArray(Reader input, String encoding)

// 字符串读写

IOUtils.readLines(Reader input)

IOUtils.readLines(InputStream input, Charset encoding)

IOUtils.readLines(InputStream input, String encoding)

IOUtils.writeLines(Collection<?> lines, String lineEnding, Writer writer)

IOUtils.writeLines(Collection<?> lines, String lineEnding, OutputStream output, Charset encoding)

IOUtils.writeLines(Collection<?> lines, String lineEnding, OutputStream output, String encoding)

// 从一个流中读取内容

IOUtils.read(InputStream input, byte[] buffer)

IOUtils.read(InputStream input, byte[] buffer, int offset, int length) IOUtils.read(Reader input, char[] buffer)

IOUtils.read(Reader input, char[] buffer, int offset, int length)

// 把数据写入到输出流中

IOUtils.write(byte[] data, OutputStream output)

IOUtils.write(byte[] data, Writer output, Charset encoding)

IOUtils.write(byte[] data, Writer output, String encoding)

IOUtils.write(char[] data, Writer output)

IOUtils.write(char[] data, OutputStream output, Charset encoding) IOUtils.write(char[] data, OutputStream output, String encoding)

IOUtils.write(String data, Writer output)

IOUtils.write(CharSequence data, Writer output)

// 从一个流中读取内容,如果读取的长度不够,就会抛出异常

IOUtils.readFully(InputStream input, int length)

IOUtils.readFully(InputStream input, byte[] buffer)

IOUtils.readFully(InputStream input, byte[] buffer, int offset, int length) IOUtils.readFully(Reader input, char[] buffer)

IOUtils.readFully(Reader input, char[] buffer, int offset, int length)

IOUtils.contentEquals(InputStream input1, InputStream input2) // 比较两个流是否相等

IOUtils.contentEquals(Reader input1, Reader input2)

IOUtils.contentEqualsIgnoreEOL(Reader input1, Reader input2) // 比较两个流,忽略换行符

IOUtils.skip(InputStream input, long toSkip) // 跳过指定长度的流

IOUtils.skip(Reader input, long toSkip)

IOUtils.skipFully(InputStream input, long toSkip) // 如果忽略的长度大于现有的长度,就会抛出异常

IOUtils.skipFully(Reader input, long toSkip)

// 读取流,返回迭代器

IOUtils.lineIterator(Reader reader)

IOUtils.lineIterator(InputStream input, Charset encoding)

IOUtils.lineIterator(InputStream input, String encoding)

// 关闭流

IOUtils.close(URLConnection conn)

// 其他的关闭方法推荐使用 Closeable.close()

FileUtils的使用:

// 复制文件夹

FileUtils.copyDirectory(File srcDir, File destDir) // 复制文件夹(文件夹里面的文件内容也会复制)

FileUtils.copyDirectory(File srcDir, File destDir, FileFilter filter) // 复制文件夹,带有文件过滤功能

FileUtils.copyDirectoryToDirectory(File srcDir, File destDir) // 以子目录的形式将文件夹复制到到另一个文件夹下

// 复制文件

FileUtils.copyFile(File srcFile, File destFile) // 复制文件

FileUtils.copyFile(File input, OutputStream output) // 复制文件到输出流

FileUtils.copyFileToDirectory(File srcFile, File destDir) // 复制文件到一个指定的目录

FileUtils.copyInputStreamToFile(InputStream source, File destination) // 把输入流里面的内容复制到指定文件

FileUtils.copyURLToFile(URL source, File destination) // 把URL 里面内容复制到文件(可以下载文件)

FileUtils.copyURLToFile(URL source, File destination, int connectionTimeout, int readTimeout)

// 把字符串写入文件

FileUtils.writeStringToFile(File file, String data, String encoding)

FileUtils.writeStringToFile(File file, String data, String encoding, boolean append)

// 把字节数组写入文件

FileUtils.writeByteArrayToFile(File file, byte[] data)

FileUtils.writeByteArrayToFile(File file, byte[] data, boolean append) FileUtils.writeByteArrayToFile(File file, byte[] data, int off, int len) FileUtils.writeByteArrayToFile(File file, byte[] data, int off, int len, boolean append)

// 把集合里面的内容写入文件

// encoding:文件编码,lineEnding:每行以什么结尾

FileUtils.writeLines(File file, Collection<?> lines)

FileUtils.writeLines(File file, Collection<?> lines, boolean append)

FileUtils.writeLines(File file, Collection<?> lines, String lineEnding)

FileUtils.writeLines(File file, Collection<?> lines, String lineEnding, boolean append)

FileUtils.writeLines(File file, String encoding, Collection<?> lines)

FileUtils.writeLines(File file, String encoding, Collection<?> lines, boolean append)

FileUtils.writeLines(File file, String encoding, Collection<?> lines, String lineEnding)

FileUtils.writeLines(File file, String encoding, Collection<?> lines, String lineEnding, boolean append)

// 往文件里面写内容

FileUtils.write(File file, CharSequence data, Charset encoding)

FileUtils.write(File file, CharSequence data, Charset encoding, boolean append)

FileUtils.write(File file, CharSequence data, String encoding)

FileUtils.write(File file, CharSequence data, String encoding, boolean append)

// 文件移动

FileUtils.moveDirectory(File srcDir, File destDir) // 文件夹在内的所有文件都将移动FileUtils.moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) // 以子文件夹的形式移动到另外一个文件下

FileUtils.moveFile(File srcFile, File destFile) // 移动文件

FileUtils.moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) // 以子文件的形式移动到另外一个文件夹下

FileUtils.moveToDirectory(File src, File destDir, boolean createDestDir) // 移动文件或者目录到指定的文件夹内

// 清空和删除文件夹

FileUtils.deleteDirectory(File directory) // 删除文件夹,包括文件夹和文件夹里面所有的文件

FileUtils.cleanDirectory(File directory) // 清空文件夹里面的所有的内容

FileUtils.forceDelete(File file) // 删除,会抛出异常

FileUtils.deleteQuietly(File file) // 删除,不会抛出异常

// 创建文件夹

FileUtils.forceMkdir(File directory) // 创建文件夹(可创建多级)

FileUtils.forceMkdirParent(File file) // 创建文件的父级目录

// 获取文件输入/输出流

FileUtils.openInputStream(File file)

FileUtils.openOutputStream(File file)

// 读取文件

FileUtils.readFileToByteArray(File file) // 把文件读取到字节数组

FileUtils.readFileToString(File file, Charset encoding) // 把文件读取成字符串

FileUtils.readFileToString(File file, String encoding)

FileUtils.readLines(File file, Charset encoding) // 把文件读取成字符串集合

FileUtils.readLines(File file, String encoding)

// 测试两个文件的修改时间

FileUtils.isFileNewer(File file, Date date)

FileUtils.isFileNewer(File file, File reference)

FileUtils.isFileNewer(File file, long timeMillis)

FileUtils.isFileOlder(File file, Date date)

FileUtils.isFileOlder(File file, File reference)

FileUtils.isFileOlder(File file, long timeMillis)

// 文件/文件夹的迭代

FileUtils.iterateFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)

FileUtils.iterateFiles(File directory, String[] extensions, boolean recursive)

FileUtils.iterateFilesAndDirs(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)

FileUtils.lineIterator(File file)

FileUtils.lineIterator(File file, String encoding)

FileUtils.listFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)

FileUtils.listFiles(File directory, String[] extensions, boolean recursive)

FileUtils.listFilesAndDirs(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)

// 其他

FileUtils.isSymlink(File file) // 判断是否是符号链接

FileUtils.directoryContains(File directory, File child) // 判断文件夹内是否包含某个文件或者文件夹

FileUtils.sizeOf(File file) // 获取文件或者文件夹的大小

FileUtils.getTempDirectory()// 获取临时目录文件

FileUtils.getTempDirectoryPath()// 获取临时目录路径

FileUtils.getUserDirectory()// 获取用户目录文件

FileUtils.getUserDirectoryPath()// 获取用户目录路径

FileUtils.touch(File file) // 创建文件

FileUtils.contentEquals(File file1, File file2) // 比较两个文件内容是否相同

FilenameUtils的使用

FilenameUtils.concat(String basePath, String fullFilenameToAdd) // 合并目录和文件名为文件全路径

FilenameUtils.getBaseName(String filename) // 去除目录和后缀后的文件名

FilenameUtils.getExtension(String filename) // 获取文件的后缀

FilenameUtils.getFullPath(String filename) // 获取文件的目录

FilenameUtils.getName(String filename) // 获取文件名

FilenameUtils.getPath(String filename) // 去除盘符后的路径

FilenameUtils.getPrefix(String filename) // 盘符

FilenameUtils.indexOfExtension(String filename) // 获取最后一个.的位置

FilenameUtils.indexOfLastSeparator(String filename) // 获取最后一个/的位置

FilenameUtils.normalize(String filename) // 获取当前系统格式化路径

FilenameUtils.removeExtension(String filename) // 移除文件的扩展名

FilenameUtils.separatorsToSystem(String path) // 转换分隔符为当前系统分隔符

FilenameUtils.separatorsToUnix(String path) // 转换分隔符为linux系统分隔符

FilenameUtils.separatorsToWindows(String path) // 转换分隔符为windows系统分隔符

FilenameUtils.equals(String filename1, String filename2) // 判断文件路径是否相同,非格式化

FilenameUtils.equalsNormalized(String filename1, String filename2) // 判断文件路径是否相同,格式化

FilenameUtils.directoryContains(String canonicalParent, String canonicalChild) // 判断目录下是否包含指定文件或目录

FilenameUtils.isExtension(String filename, String extension) // 判断文件扩展名是否包含在指定集合(数组、字符串)中

FilenameUtils.wildcardMatch(String filename, String wildcardMatcher) // 判断文件扩展名是否和指定规则匹配

FileSystemUtils的使用:

// 仅有这一个方法,及其重载方法

FileSystemUtils.freeSpaceKb() throws IOException

//推荐使用

java.nio.file.FileStore.getUsableSpace()

二、输入、输出

在org.apache.commons.io.input包下有许多InputStrem类的实现,我们来测试一个最实用的类,TeeInputStream,将InputStream以及OutputStream作为参数传入其中,自动实现将输入流的数据读取到输出流中。

@Test
public void test() throws IOException {
 
    // XmlStreamReader
    File xml = new File("E:\\test\\data\\io\\test.xml");
    XmlStreamReader xmlReader = new XmlStreamReader(xml);
    System.out.println("XML encoding: " + xmlReader.getEncoding());
 
    String INPUT = "This should go to the output.";
 
    // TeeInputStream
    ByteArrayInputStream in = new ByteArrayInputStream(INPUT.getBytes("US-ASCII"));
    ByteArrayOutputStream out = new ByteArrayOutputStream();
 
    TeeInputStream tee = new TeeInputStream(in, out, true);
    tee.read(new byte[INPUT.length()]);
 
    System.out.println("Output stream: " + out.toString());
}

同样,在 org.apache.commons.io.output包中同样有OutputStream类的实现,这里介绍TeeOutputStream,它可以将输出流进行分流,换句话说我们可以用一个输入流将数据分别读入到两个不同的输出流。

@Test
public void test8() throws IOException {
    String INPUT = "This should go to the output.";
 
    // TeeOutputStream
    ByteArrayInputStream in = new ByteArrayInputStream(INPUT.getBytes("US-ASCII"));
    ByteArrayOutputStream out1 = new ByteArrayOutputStream();
    ByteArrayOutputStream out2 = new ByteArrayOutputStream();
 
    TeeOutputStream teeOut = new TeeOutputStream(out1, out2);
    TeeInputStream teeIn = new TeeInputStream(in, teeOut, true);
    teeIn.read(new byte[INPUT.length()]);
 
    System.out.println("Output stream 1: " + out1.toString());
    System.out.println("Output stream 2: " + out2.toString());
} 

三、Filters过滤器

1.基本功能过滤器

类型:
FileFileFilter 仅接受文件

DirectoryFilter 仅接受目录

名称:

PrefixFileFilter 基于前缀(不带路径的文件名)

SuffixFileFilter 基于后缀(不带路径的文件名)

NameFileFilter 基于文件名称(不带路径的文件名)

WildcardFileFilter 基于通配符(不带路径的文件名)
RegexFileFilter 基于正则表达式

时间:

AgeFileFilter 基于最后修改时间

MagicNumberFileFileter 基于Magic Number

大小:
EmptyFileFilter 基于文件或目录是否为空

SizeFileFilter 基于文件尺寸

隐藏属性:
HiddenFileFilter 基于文件或目录是否隐藏
读写属性:
CanReadFileFilter 基于是否可读
CanWriteFileFilter 基于是否可写入

DelegateFileFilter 将普通的FileFilter和FilenameFilter包装成IOFileFilter

2.逻辑关系过滤器

AndFileFilter 基于AND逻辑运算
OrFileFilter 基于OR逻辑运算
NotFileFilter 基于NOT逻辑运算

TrueFileFilter 不进行过滤
FalseFileFilter 过滤所有文件及目录

工具类:FileFilterUtils

提供一些工厂方法用于生成各类文件过滤器

提供一些静态方法用于对指定的File集合进行过滤

FileFilterUtils.ageFileFilter(Date cutoffDate)

FileFilterUtils.and(IOFileFilter... filters)

FileFilterUtils.asFileFilter(FileFilter filter)

FileFilterUtils.directoryFileFilter()

FileFilterUtils.falseFileFilter()

FileFilterUtils.fileFileFilter()

FileFilterUtils.filter(IOFileFilter filter, File... files)

FileFilterUtils.filterList(IOFileFilter filter, File... files)

FileFilterUtils.filterSet(IOFileFilter filter, File... files)

FileFilterUtils.nameFileFilter(String name)

FileFilterUtils.notFileFilter(IOFileFilter filter)

FileFilterUtils.or(IOFileFilter... filters)

FileFilterUtils.prefixFileFilter(String prefix)

FileFilterUtils.sizeFileFilter(long threshold)

FileFilterUtils.suffixFileFilter(String suffix)

FileFilterUtils.trueFileFilter()

@Test
public void test() throws IOException {
   String PARENT_DIR = "E:\\test\\data\\io\\filter";

   File dir = new File(PARENT_DIR);

   String[] acceptedNames = { "test", "testTxt.txt" };

   // 匹配文件名
   for (String file : dir.list(new NameFileFilter(acceptedNames, IOCase.INSENSITIVE))) {
       System.out.println("File found, named: " + file);
   }

   System.out.println("=========================================");

   // 根据通配符匹配
   for (String file : dir.list(new WildcardFileFilter("*est*"))) {
       System.out.println("Wildcard file found, named: " + file);
   }

   System.out.println("=========================================");

   // 匹配前缀
   for (String file : dir.list(new PrefixFileFilter("test"))) {
       System.out.println("Prefix file found, named: " + file);
   }

   System.out.println("=========================================");

   // 匹配后缀
   for (String file : dir.list(new SuffixFileFilter(".txt"))) {
       System.out.println("Suffix file found, named: " + file);
   }

   System.out.println("=========================================");

   // 逻辑或
   for (String file : dir
           .list(new OrFileFilter(new WildcardFileFilter("*est*"), new SuffixFileFilter(".txt")))) {
       System.out.println("Or file found, named: " + file);
   }

   System.out.println("=========================================");

   // 逻辑与
   for (String file : dir.list(new AndFileFilter(new WildcardFileFilter("*est*"),
           new NotFileFilter(new SuffixFileFilter(".txt"))))) {
       System.out.println("And/Not file found, named: " + file);
   }
}

四、io Comparators比较器

org.apache.commons.io.comparator包下的类:

​ CompositeFileComparator

​ DefaultFileComparator

​ DirectoryFileComparator

​ ExtensionFileComparator

​ LastModifiedFileComparator

​ NameFileComparator

​ PathFileComparator

​ SizeFileComparator

都有着四个方法:

​ compare(File file1, File file2)

​ sort(File... files)

​ sort(List files)

​ toString()

@Test
public void test() {
    String dir = "E:\\test\\data\\io\\comparator";
 
    // NameFileComparator 按名称
    File dirFile = new File(dir);
    NameFileComparator comparator = new NameFileComparator(IOCase.SENSITIVE);
    File[] files = comparator.sort(dirFile.listFiles());
 
    System.out.println("\nSorted by name files in parent directory: ");
    for (File f : files) {
        System.out.println("t" + f.getAbsolutePath());
    }
 
    // SizeFileComparator 按大小
    SizeFileComparator sizeComparator = new SizeFileComparator(true);
    File[] sizeFiles = sizeComparator.sort(dirFile.listFiles());
 
    System.out.println("\nSorted by size files in parent directory: ");
    for (File f : sizeFiles) {
        System.out.println(f.getName() + " with size (kb): " + f.length());
    }
 
    // LastModifiedFileComparator 按修改时间
    LastModifiedFileComparator lastModified = new LastModifiedFileComparator();
    File[] lastModifiedFiles = lastModified.sort(dirFile.listFiles());
 
    System.out.println("\nSorted by last modified files in parent directory: ");
    for (File f : lastModifiedFiles) {
        Date modified = new Date(f.lastModified());
        System.out.println(f.getName() + " last modified on: " + modified);
    }
}

五、Monitor文件监控

原理:

由文件监控类FileAlterationMonitor中的线程按指定的间隔不停的扫描文件观察器FileAlterationObserver,如果有文件的变化,则根据相关的文件比较器,判断文件时新增,还是删除,还是更改。(默认为1000毫秒执行一次扫描)

使用方法:

1、创建一个FileAlterationObserver对象,传入一个要监控的目录,这个对象会观察这些变化。

2、通过调用addListener()方法,为observer对象添加一个 FileAlterationListener对象。你可以通过很多种方式来创建,继承适配器类或者实现接口或者使用匿名内部类,实现所需要的监控方法。

3、创建一个FileAlterationMonitor 对象,将已经创建好的observer对象添加其中并且传入时间间隔参数(单位是毫秒)。

4、调用start()方法即可开启监视器,如果你想停止监视器,调用stop()方法即可。

// 继承FileAlterationListenerAdaptor适配器类或者是实现FileAlterationListener接口
class MyFileListener extends FileAlterationListenerAdaptor {
    private Log log = LogFactory.getLog(MyFileListener.class);
 
    // 文件创建
    @Override
    public void onFileCreate(File file) {
        log.info("[新建]:" + file.getAbsolutePath());
    }
 
    // 文件修改
    @Override
    public void onFileChange(File file) {
        log.info("[修改]:" + file.getAbsolutePath());
    }
 
    // 文件删除
    @Override
    public void onFileDelete(File file) {
        log.info("[删除]:" + file.getAbsolutePath());
    }
 
    // 目录创建
    @Override
    public void onDirectoryCreate(File directory) {
        log.info("[新建]:" + directory.getAbsolutePath());
    }
 
    // 目录修改
    @Override
    public void onDirectoryChange(File directory) {
        log.info("[修改]:" + directory.getAbsolutePath());
    }
 
    // 目录删除
    @Override
    public void onDirectoryDelete(File directory) {
        log.info("[删除]:" + directory.getAbsolutePath());
    }
 
    @Override
    public void onStart(FileAlterationObserver observer) {
        super.onStart(observer);
    }
 
    @Override
    public void onStop(FileAlterationObserver observer) {
        super.onStop(observer);
    }
}
public static void main(String[] args) throws Exception {
    String rootDir = "E:\\test\\data\\io\\monitor";
 
    // 创建一个文件观察器用于处理文件的格式
    // FileAlterationObserver _observer = new FileAlterationObserver(rootDir,
    // FileFilterUtils.and(FileFilterUtils.fileFileFilter(),
    // FileFilterUtils.suffixFileFilter(".txt")), null);// 过滤文件格式
    FileAlterationObserver observer = new FileAlterationObserver(rootDir);
 
    observer.addListener(new MyFileListener()); // 设置文件变化监听器
 
    FileAlterationMonitor monitor = new FileAlterationMonitor(5000, observer);// 创建文件变化监听器,间隔5秒
 
    monitor.start();// 开始监控
}

posted on 2022-03-06 21:45  freyhe  阅读(91)  评论(0编辑  收藏  举报