Java IO流 (Input&Output)

IO的核心组成分为6个部分
一个普通类(File) 、一个接口(Serializable)、四个抽象类(InputStream、OutputStream、Reader、Writer)

一、File类

File类是在整个java.io包里面唯一一个与文件本身有关的操作类, 与文件本身有关指的是这个类可以进行操作文件的路径指派,可以创建或者删除文件,以及还可以获取文件相关的信息内容,在使用File类的时候可以采用如下的构造方法进行实例化

File(String pathname)

      通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。(设置要操作文件的完整路径,要考虑路径分隔符)

File(File parent, String child)

      根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。(设置要操作文件的父目录与子文件路径)

在使用File类指派操作文件的时候该文件的路径有可能不存在,只要不进行各种信息的获取操作,实际上是不会有任何问题,只是表示一个要操作的文件路径。

例:实例化File类对象

public class TestDemo_01 {
    public static void main(String[] args) {
        File file =new File("d:\\test.txt");
        System.out.println(file);
    }

}

执行结果:
d:\test.txt

File类常用方法:

boolean createNewFile() 
          当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
          (创建一个新的文件) 
boolean delete() 
          删除此抽象路径名表示的文件或目录。 (删除文件)
boolean exists() 
          测试此抽象路径名表示的文件或目录是否存在。  

下面基于给定的File类中的方法实现文件的创建与删除,在创建和删除之前进行判断,如果文件不存在则创建,存在则删除。

例:

public class TestDemo_01 {
    public static void main(String[] args) {
        File file = new File("d:\\test.txt");
        if (file.exists()) {
            System.out.println("文件存在执行删除操作");
            file.delete();

        } else {
            try {
                System.out.println("文件不存在执行创建操作");
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

}

执行结果:文件不存在执行创建操作
一、Java IO流  之File类_子目录
此时虽然可以实现文件的创建与删除,但是在使用File类的时候需要注意两个问题:

***问题一:***当前的程序是进行了磁盘文件的处理操作。
由于java程序距底层接口很远 操作存在延迟 重复提交创建删除代码可能出现文件已存在的情况
一、Java IO流  之File类_当前路径_02

问题二:Java本身采用的是跨平台的编程模式,那么这种情况下就必须考虑各个系统的路径问题。
·windows系统的路径分隔符 " \ " ;
·Unix、类Unix(Linux、MacOS、AX)路径分隔符为“/”;

但是在编写路径分隔符的时候如果每一次都编写大量的“\”或者是“/”进行转义处理的时候如果每一次都编写大量的“\”或者是“//”进行转义处理则会显得 非常的麻烦,所以在File类中提供有一个常量 (最早的命名规范和现代的命名是有区别的):
public static final String separator
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 separatorChar

早期的习惯是在进行路径分割符编写的时候都应该使用“File.separator”进行分隔符的定义

例:正确的路径编写。

File file=new File("d:"+File. separator+"demo. txt");

在以后所编写的代码之中,所有路径分隔符尽量都通过常量来进行定义。

文件目录的操作

当前的程序是直接在“D”盘的根目录下进行了文件的创建,但是在很多的情况下有可能需在一些子目录之中进行文件的创建,那么在java.io包里面,必须保证有父目录的情况下才可以创建子文件。

如果要想进行目录的创建,则需要使用到File类中的如下几个方法:

boolean mkdir() 
          创建此抽象路径名指定的目录。  (创建单级目录)
 boolean mkdirs() 
          创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 (创建多级目录,多个子目录可以同时创建)
  String getParent() 
          返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。 (获取父路径信息)
 File getParentFile() 
          返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。 (获取父路径的File类对象)

例:进行父目录的创建

public class TestDemo_01 {
    public static void main(String[] args) {
        File file = new File("d:"+File.separator+"ZZ"+File.separator+"hello"+File.separator+"test.txt");

       if(!file.getParentFile().exists()){ //如果父路径不存在
        file.getParentFile().mkdirs(); //创建所有的父路径

       }


        if (file.exists()) {
            System.out.println("文件存在执行删除操作");
            file.delete();
        } else {
            try {
                System.out.println("文件不存在执行创建操作");
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

}

执行结果:文件不存在执行创建操作
一、Java IO流  之File类_路径名_03
合理的父目录操作 (多线程优化)

一、Java IO流  之File类_子目录_04
虽然这个时候可以正常的实现了目录下的文件创建,但是如果每一次执行的时候都需要进行判断,那么就会影响程序的性能,所以最好的做法是让这个判断操作只执行一次。

对于当前的环境下可以考虑使用静态代码块的模式来完成,静态代码块的执行优先于主方法执行。

例:修改当前的目录创建操作。

public class TestDemo_01 {
 private static   File file = new File("d:"+File.separator+"ZZ"+File.separator+"hello"+File.separator+"test.txt");

 static {   //静态代码块只执行一次
     if(!file.getParentFile().exists()){ //如果父路径不存在
         file.getParentFile().mkdirs(); //创建所有的父路径
     }
 }
 
    public static void main(String[] args) {

        if (file.exists()) {
            System.out.println("文件存在执行删除操作");
            file.delete();
        } else {
            try {
                System.out.println("文件不存在执行创建操作");
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

}

执行结果:文件不存在执行创建操作
一、Java IO流  之File类_路径名_03
在以后编写代码的过程之中,需要考虑各种目录的创建时机,所有代码的性能都是一点一点提升的。

获取文件信息

在File类里面除了提供有基本的文件与目录的创建之外,也提供有一些其它的操作信息,这些操作的方法如下:

boolean canExecute() 
          测试应用程序是否可以执行此抽象路径名表示的文件。 (是否可执行)
 boolean canRead() 
          测试应用程序是否可以读取此抽象路径名表示的文件。 (是否可读)
 boolean canWrite() 
          测试应用程序是否可以修改此抽象路径名表示的文件。  (是否可写)
 File getAbsoluteFile() 
          返回此抽象路径名的绝对路径名形式。  (获取文件绝对路径实例)
 String getName() 
          返回由此抽象路径名表示的文件或目录的名称。(获取文件或目录名称   **相对名 和相对路径** )
 boolean isDirectory() 
          测试此抽象路径名表示的文件是否是一个目录。 
boolean isFile() 
          测试此抽象路径名表示的文件是否是一个标准文件
long lastModified() 
          返回此抽象路径名表示的文件最后一次被修改的时间。  (需要格式化)
 long length() 
          返回由此抽象路径名表示的文件的长度。         (需要格式化)

例:

import java.io.File;
import java.text.SimpleDateFormat;

public class TestDemo_01 {
 private static   File file = new File("d:"+File.separator+"ZZ"+File.separator+"hello"+File.separator+"test.txt");

 static {   //静态代码块只执行一次
     if(!file.getParentFile().exists()){ //如果父路径不存在
         file.getParentFile().mkdirs(); //创建所有的父路径
     }
 }

    public static void main(String[] args) {


        if (file.exists()) {
            System.out.println("[文件是否能执行?]"+file.canExecute());
            System.out.println("[文件是否能读?]"+file.canRead());
            System.out.println("[文件是否能写?]"+file.canWrite());
            System.out.println("[获取文件绝对路径]"+file.getAbsoluteFile());
            System.out.println("[获取文件或目录名称]"+file.getName());
            System.out.println("[当前路径是否是文件]"+file.isFile());
            System.out.println("[当前路径是否是目录]"+file.isDirectory());
            System.out.println("[当前文件最后一次被修改的日期是]"+file.lastModified());
            System.out.println("[格式化后的日期是]"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(file.lastModified()));
                                                       		         //年  月 日  时 分 秒  毫秒
            System.out.println("[获取文件大小]"+file.length());
            System.out.println(String.format("[格式化文件大小]%5.2f",file.length()/1024/1024.0)+"MB");
            //%5.2f 代表数据总长度为5位  小数点后面四舍五入保留2位   如果整数部分大于三位则自动在最前面补缺占位
        }
    }

}

执行结果:
[文件是否能执行?]true
[文件是否能读?]true
[文件是否能写?]true
[获取文件绝对路径]d:\ZZ\hello\test.txt
[获取文件或目录名称]test.txt
[当前路径是否是文件]true
[当前路径是否是目录]false
[当前文件最后一次被修改的日期是]1575724712793
[格式化后的日期是]2019-12-07 21:18:32.793
[获取文件大小]3802531
[格式化文件大小] 3.63MB (该文件已被提前手动写入内容方便读取文件大小进行演示)

注意:File类之中的返回的日期long就是毫秒数、而文件大小返回的就是字节个数,一定要使用long数据类型。

获取文件目录信息

使用File可以实现文件或者目录的操作,但是在很多时候有可能会有一些子目录存在。

现在有个需求: 要求列出一个目录内的所有子目录以及这些子目录中所包含的文件
此时就需要使用File类中的一个重要方法

String[] list() 
          返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。 (描述的是子路径的信息(不包含父目录))
 File[] listFiles() 
          返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。(列出所有的路径信息(返回File对象数组)) 

观察两个列出方法的区别
例:观察list()方法的使用

import java.io.File;
import java.util.Arrays;

public class TestDemo_01 {

    public static void main(String[] args) {

        File file=new File("d:"+File.separator);
        System.out.println(Arrays.toString(file.list()));

    }

}

执行结果: [$RECYCLE.BIN, dev, Download] (这里只取几个进行比较)

此时只是列出了当前路径下的所有的子路径的信息,子路径可能是一个文件或者是一个目录,不存在父路径。

例:观察listFiles()方法的使用

import java.io.File;
import java.util.Arrays;

public class TestDemo_01 {

    public static void main(String[] args) {

        File file=new File("d:"+File.separator);
        System.out.println(Arrays.toString(file.listFiles()));

    }

}

执行结果: [d:$RECYCLE.BIN, d:\dev, d:\Download] (这里只取几个进行比较)

以当前的程序为例,因为一个目录下还可能会包含有若干级的子目录,既然要想全部列出,那么就需要获取完整的路径,所以此时肯定使用listFiles()方法列出会更方便。

例: 使用递归的形式实现路径的列出。

package cn.kinggm.file;

import java.io.File;


public class TestDemo_01 {



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

        File file=new File("d:"+File.separator);
       listDir(file);

    }
    public static void listDir(File file){
        if(file.isDirectory()) {            //当前路径是个目录
            File result[] = file.listFiles(); //列出目录组成
            if (result != null) {    //确定已经列出了内容
                for (int i = 0; i < result.length; i++) {
                    listDir(result[i]);
                }

            }
            System.out.println(file);

        }
    }

}

注意:
如果此时你执行的操作不是一个输出,而是一个删除 则会删除所有文件 (慎重)

文件更名

在使用File类的时候还有一项功能比较常用,实现文件的更名处理

boolean renameTo(File dest) 
          重新命名此抽象路径名表示的文件。 (可以为文件对象设置一个更改路径的信息。)

例: 文件更名

package cn.kinggm.file;

import java.io.File;


public class TestDemo_01 {

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

        File oldFile=new File("d:"+File.separator+"test.txt");
        File newFile=new File("d:"+File.separator+"zzTest.txt");

        oldFile.renameTo(newFile);

    }


}

执行结果
一、Java IO流  之File类_当前路径_06

这种更名的处理操作结合一些实际的开发是非常有用处的,现在假设有如下一个场景。
现有一个叫"zz_info"的目录
存了所有的相关日志的信息定义,而在个目录里面,由于初期的设计失误,导致该目录下的文件存放出现了问题,路径名称不统一。

例: 先创建文件名长度不同的文件若干

package cn.kinggm.file;

import javafx.scene.chart.PieChart;

import java.io.File;
import java.text.SimpleDateFormat;

public class TestDemo_01 {



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

        File fileDir=new File("d:"+File.separator+"zz_info");
        for (int i = 0; i < 100; i++) {
//假设中间还有许多的文件内容的输出处理,所有的文件数据不是空
            new File(fileDir,"log-"+getTimestamp()+"-"+i+".log").createNewFile();

        }
        
    }

    public static String getTimestamp(){
        //生成时间戳
        return new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new java.util.Date());
        
    }

}

执行结果:
一、Java IO流  之File类_分隔符_07

原始打算让所有的日志文件的名称的长度全部相同,但是由于初期代码设计的朱误缺少了文件名的补0操作,要求将所有的文件名称进行更名,保持相同的长度。
此时目录之中还可能会存在有一些其它的文件的信息。

实现:

package cn.kinggm.file;

import java.io.File;

class DirRenameUtil {
    private int max = 0;//保存最大长度
    private String maxFileName=null; //保存最大长度文件名称
    private int sequenceLength=0;  //整体序列的长度


    public DirRenameUtil(File file){

            this.init(file);
            this.rename(file);

    }

    public void init(File file){ //初始化处理
        if (file.isDirectory()) {            //是否为目录
            File[] result = file.listFiles(); // 列出所有的子路径
            for (int i = 0; i < result.length; i++) {
                init(result[i]);

            }

        } else {
            if (file.isFile()) {//进行文件修改
                if (file.getName().matches("log\\-\\d{17}\\-\\d+\\.log")) {    //要修改的路径信息 正则表达式
                    this.getMaxLength(file.getName());
                    this.getFileSequenceLength();

                }
            }
        }

    }


    public void rename(File file) { //需要考虑子目录问题
        if (file.isDirectory()) {            //是否为目录
            File[] result = file.listFiles(); // 列出所有的子路径
            for (int i = 0; i < result.length; i++) {

                rename(result[i]);
            }

        } else {
            if (file.isFile()) {//进行文件修改
                if (file.getName().matches("log\\-\\d{17}\\-\\d+\\.log")) {    //要修改的路径信息 正则表达式


                    String oldFile =file.getName().substring(file.getName().lastIndexOf("-")+1,
                                     file.getName().lastIndexOf("."));


                    String newFileName = file.getName().substring(0,file.getName().lastIndexOf("-")+1)+
                                        this.getNewFileName(oldFile)+
                                        file.getName().substring(file.getName().lastIndexOf("."));

                    File newFile = new File("d:"+File.separator+"zz_info"+File.separator,newFileName);
                    file.renameTo(newFile);
                }
            }
        }



    }


    public String getNewFileName(String oldName ){  //补0 得到新文件名
        StringBuffer buffer=new StringBuffer(oldName);
        while (buffer.length()<this.sequenceLength){
            buffer.insert(0,0); //补0

        }

        return buffer.toString();

    }


    public int getMaxLength(String fileName) {
        if (this.max < fileName.length() ){
            this.max=fileName.length();     //获取文件名最大长度
            this.maxFileName=fileName;

        }
        return max;

    }

    public void getFileSequenceLength(){ //获取截取部分最大长度   即文件编号最大长度
        this.sequenceLength=this.maxFileName.substring(this.maxFileName.lastIndexOf("-")+1,
                            this.maxFileName.lastIndexOf(".")).length();

    }


}


public class TestDemo_01 {

    public static void main(String[] args){

        File fileDir = new File("d:" + File.separator + "zz_info");
         new DirRenameUtil(fileDir);

    }

}

执行结果:
一、Java IO流  之File类_路径名_08