目录

一、文件的定义

二、文件路径

三、文件的类型

四、JAVA中如何操作文件

4.1 在文件系统的层面上来操作文件

 4.1.1 get相关方法

 4.1.2 普通文件的创建和删除

 4.1.3 观察deleteOnExit的现象

4.1.4 创建目录

4.1.5 文件重命名

4.2 操作文件的内容,即操作文件里保存的数据

4.2.1利用InputStream/FileInputStream读文件 

4.2.2 利用Scanner进行字符读取

4.2.3 利用Reader/FileReader读取文件

4.2.4 利用OutputStreamWriter进行字符写入

五、示例

例子1:扫描指定目录,并找到包含指定字符的所有普通文件,并询问用户是否要删除该文件

例子2:普通文件的复制

例子3:扫描指定目录,找到名称或者内容中包含指定字符的所有普通文件(不包含目录)


一、文件的定义

在一般情况下,我们说的文件都是指存储在硬盘里的数据(普通文件),但是实际上,文件的概念要更广泛一点,操作系统会将很多的硬件设备、软件资源等也给抽象成文件。举例:当学习网络编程时,读写网卡,这个时候操作网卡的代码和操作普通文件没啥区别,主要是因为操作系统把网卡这样的硬件设备也给抽象成文件了。

二、文件路径

绝对路径:以/开头或者以盘符开头,是指从树根出发,到该文件之间经历的路径

相对路径:以.或者..开头,从当前的工作目录出发,中间历经的路径

三、文件的类型

文本文件:文件里存的是文本,使用记事本能直接打开不乱码

二进制文件:文件里存的是二进制数据,使用记事本打开会出现乱码

在操作文件时,要先明确文件的类型,文本文件操作的基本单位是字符,二进制文件操作的基本单位是字节。

四、JAVA中如何操作文件

4.1 在文件系统的层面上来操作文件

创建文件、删除文件、创建目录、拷贝文件、重命名文件

JAVA中通过java.io.File类对一个文件、目录进行抽象的描述。注意:有File对象,并不代表真实存在该文件。

说法

说明

File(File parent,String child)

根据父目录+孩子文件路径,创建一个新的File实例

File(String pathname)

根据文件路径创建一个新的File实例,路径可以是绝对路径或者相对路径

File(String parent,String child)

根据父目录+孩子文件路径创建一个新的File实例,父目录用路径表示

 4.1.1 get相关方法

方法

说明

getParent()

返回File对象的父目录文件路径

getName()

返回File对象的纯文件名称

getPath()

返回File对象的文件路径

getAbsolutePath()

返回File对象的绝对路径

getCanonicalPath()

返回File对象的修饰过的绝对路径

File f2 = new File("./a2.txt");
System.out.println(f2.getParent());
System.out.println(f2.getName());
System.out.println(f2.getPath());
System.out.println(f2.getAbsolutePath());
System.out.println(f2.getCanonicalPath());

java 根据InputStream获取flie路劲_数据

 4.1.2 普通文件的创建和删除

方法

说明

exists()

判断File对象描述的文件是否真实存在

isDirectory()

判断File对象代表的文件是否是一个目录

isFile()

判断File对象代表的文件是否是一个普通文件

createNewFile()

根据File对象,自动创建一个空文件。成功创建后返回true

delete()

根据File对象,删除该文件,成功删除后返回true

File f3 = new File("./a3.txt");
System.out.println(f3.exists());
System.out.println(f3.isDirectory());
System.out.println(f3.isFile());
System.out.println("----------------");
System.out.println(f3.createNewFile());
System.out.println(f3.exists());
System.out.println(f3.isDirectory());
System.out.println(f3.isFile());
System.out.println(f3.delete());
System.out.println(f3.exists());

java 根据InputStream获取flie路劲_System_02

 4.1.3 观察deleteOnExit的现象

方法

说明

deleteOnExit()

根据File对象,标注文件将被删除,删除动作会到JVM运行结束时才会进行

File f4 = new File("./a4.txt");
System.out.println(f4.createNewFile());
f4.deleteOnExit();
// 通过Scanner构造一个阻塞,看看效果
Scanner scanner = new Scanner(System.in);
scanner.hasNext();

4.1.4 创建目录

方法

说明

mkdir()

创建File对象代表的目录

mkdirs()

创建File对象代表的目录,如果必要,会创建中间目录

File f5 = new File("./a/b/c/");
f5.mkdirs();
System.out.println(f5.isDirectory());

java 根据InputStream获取flie路劲_文件路径_03

 创建多级目录时中间目录不存在,mkdir()无法创建成功,mkdirs()可以创建成功。

4.1.5 文件重命名

方法

说明

renameTo(FileDest)

进行文件改名,也可视为平时的文件剪切、粘贴操作

File f6 = new File("./aa.txt");
File f7 = new File("./aaa.txt");
f6.createNewFile();
System.out.println("f6.exists()"+f6.exists());
System.out.println("f7.exists()"+f7.exists());
f6.renameTo(f7);
System.out.println("f6.exists()"+f6.exists());
System.out.println("f7.exists()"+f7.exists());

java 根据InputStream获取flie路劲_System_04

 实际上不是每次创建文件都会成功,最典型的失败就是没有权限,如果创建失败就会出现一个IOException这样的异常。delete也不是每次都会成功,一方面是权限,另一方面是文件路径不对

4.2 操作文件的内容,即操作文件里保存的数据

字节流:InputStream读,OutputStream写。针对二进制文本进行读写,基本单位是字节

字符流:Reader读,Writer写,针对文本文件进行读写,操作基本单位是字符

4.2.1利用InputStream/FileInputStream读文件 

方法

说明

read()

读取一个字节的数据。返回-1代表已经完全读完了

read(byte[] b)

最多读取b.length字节的数据到b中,返回实际读到的数量。-1代表已经读完了

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

最多读取len-off字节的数据到b中,放在从off开始的位置,返回实际读到的数量,-1代表已经读完了

close()

关闭字节流

InputStream只是一个抽象类,需要使用需要实现一个具体的实现类。关于InputStream的实现类有很多,由于现在只关心从文件中读取,所以使用FileInputStream。

方法

说明

FileInputStream(File file)

利用File文件构造文件输入流

FileInputStream(String name)

利用文件路径构造文件输入流


// InputStream是一个抽象类,是总领全局的,表示所有的按二进制读取文件的类
        // FileInputStream这是其中的一个典型实现
        // 构造方法中指定要打开的文件名,可以是相对路径,也可以是绝对路径,也可以是File对象
        // 这样写就可以不要手动的关闭文件
        try(InputStream inputStream = new FileInputStream("./a.txt")){
            while (true){
                int b = inputStream.read(); // 每次读出一个字节
                if(b == -1){
                    break;
                }
                System.out.println(b);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
try(InputStream inputStream = new FileInputStream("./a.txt")){
            byte[] buffer = new byte[1024];
            while (true){
                int len = inputStream.read(buffer); //把文件的数据尽可能的读到这个数组里
                // 最后一次成功的读取,返回的结果是实际的长度 小于1024
                // 当文件读完了再次尝试读,返回结果就是-1
                if(len == -1){
                    break;
                }
                // 循环输出
                for(byte b:buffer){
                    System.out.println(b);
                }
                // 转成字符串
                String str = new String(buffer,0,len,"UTF-8");
                System.out.println(str);
            }
        }catch (IOException e){
            e.printStackTrace();
        }

为啥通过int来接收?

返回值是-1,255.0-255本来就表示字节表示的范围。byte表示的范围是-128,127.此处用正整数的方式要比带负数的方式变现要好。

原因:读出来的只是表示一个单纯的字节,并不是真的要参加加减乘除运算

读到文件末尾,就返回-1,表示读完了

按照字节的方式读,读出来的结果就相当于这些字符的ascii码。要是存的是中文,读的是中文的每个字节的编码,GBK/UTF-8

4.2.2 利用Scanner进行字符读取

对字符类型直接使用InputStream进行读取是比较麻烦且困难的,利用Scanner会简单一点。

构造方法

说明

Scanner(InputStream is,string charSet)

使用charset字符集进行is的扫描读取

try(InputStream inputStream = new FileInputStream("./a.txt")){
            try(Scanner scanner = new Scanner(inputStream,"UTF-8")){
                while(scanner.hasNext()){
                    String s = scanner.next();
                    System.out.println(s);
                }
            }
        }catch(IOException e){
            e.printStackTrace();
        }

4.2.3 利用Reader/FileReader读取文件

public static void main(String[] args) {
        try(Reader reader = new FileReader("./a.txt")){
            while (true){
                int ret = reader.read();
                if(ret == -1){
                    break;
                }
                System.out.println((char)ret);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

4.2.4 利用OutputStreamWriter进行字符写入

方法

说明

write(int b)

写入文件的数据

write(byte[] b)

将b这个字符数组中的数据全部写入os中

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

将b这个字符数组中从off开始的数据写入os中,共写len个

close()

关闭字节流

flush()

重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。

try(OutputStream outputStream = new FileOutputStream("./a.txt")){
            outputStream.write(97);
            outputStream.write(98);
            outputStream.write(99);
            byte[] bytes = {'1','a','b','c','d'};
            outputStream.write(bytes,1,2);
            outputStream.flush();
        }catch (IOException e){
            e.printStackTrace();
        }

五、示例

例子1:扫描指定目录,并找到包含指定字符的所有普通文件,并询问用户是否要删除该文件

public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 1.先让用户输入一个带扫描的路径
        System.out.println("请输入要扫描的目录路径: ");
        String deleteDirPath = scanner.next();
        // 2.判断路径是否合法
        File deleteDir = new File(deleteDirPath);
        if(!deleteDir.isDirectory()){
            // 如果给定的这个路径不存在, 或者不是目录, 就直接提示出错!
            System.out.println("您输入的扫描路径非法!");
            return;
        }
        // 3.输入要删除的文件
        System.out.println("请输入要删除的文件名: ");
        String deleteFileName = scanner.next();
        // 4.遍历当前的目录,直到所有和待删除文件匹配的文件
        List<File> result = new ArrayList<>();
        scanzDir(deleteDir,deleteFileName,result);
        // 5.进行删除操作,把result里面所有的文件,都依次进行删除
        for(File f:result){
            System.out.println(f.getCanonicalPath()+" 该文件是否确认删除? Y/n");
            String choice = scanner.next();
            if (choice.equals("Y")) {
                f.delete();
                System.out.println(f.getCanonicalPath() + " 该文件删除成功!");
            }
        }
    }
    public static void scanzDir(File deleteDir,String deleteFileName,List<File> result){
        File[] files = deleteDir.listFiles(); //
        for(File file:files){
            try {
                System.out.println("扫描了文件"+file.getCanonicalPath());
                // 如果是普通文件,就直接判断名字
                if(file.isFile()){
                    if(file.getName().equals(deleteFileName)){
                        result.add(file);
                    }
                    // 如果是文件夹,就需要递归调用
                }else if(file.isDirectory()){
                    scanzDir(file,deleteFileName,result);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

例子2:普通文件的复制

public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要复制的源文件:");
        String ori = scanner.next();
        System.out.println("请输入要复制的目标文件:");
        String desc = scanner.next();
        File oriFile = new File(ori);
        if(!oriFile.isFile()){
            System.out.println("当前输入的路径有误,不合法");
            return;
        }
        // 进行复制操作,把ori的内容一个字节一个字节的读出来,写入到desc中
        try(InputStream inputStream = new FileInputStream(ori)){
            try(OutputStream outputStream = new FileOutputStream(desc)){
                byte[] buffer = new byte[1024];
                while (true){
                    // 把读到的内容放入 buffer 中, 返回实际读到的字节数.
                    int len = inputStream.read(buffer);
                    if(len == -1){
                        break;
                    }
                    // 读取数据成功, 写入到 outputStream 中
                    outputStream.write(buffer,0,len);
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

例子3:扫描指定目录,找到名称或者内容中包含指定字符的所有普通文件(不包含目录)

public static void main(String[] args) throws IOException {
        //1.先输入目录和要查找的内容
        Scanner scanner = new Scanner(System.in);
        // 1. 先输入目录和要查找的内容
        System.out.println("请输入要遍历的目录: ");
        String rootDirPath = scanner.next();
        System.out.println("请输入要查找的内容: ");
        String content = scanner.next();
        File rootDir = new File(rootDirPath);
        if (!rootDir.isDirectory()) {
            System.out.println("输入的路径存在问题!");
            return;
        }
        // 2.进行遍历,找到所有复合要求的文件
        List<File> results = new ArrayList<>();
        ScanDirByContent(rootDir,content,results);
        //3.打印一下找到的文件结果
        for(File f:results){
            System.out.println(f.getCanonicalPath());
        }

    }
    public static void ScanDirByContent(File rootDir,String content,List<File> results){
        File[] files = rootDir.listFiles();
        // 空目录
        if(files == null){
            return;
        }
        for(File f:files){
            if(f.isFile()){
                if(isContentExists(f,content)){
                    results.add(f);
                }
            }else if(f.isDirectory()){
                ScanDirByContent(f,content,results);
            }
        }
    }
    public static boolean isContentExists(File f,String content){
        // 读取f中的内容,放到一个String中
        StringBuilder stringBuilder = new StringBuilder();
        try(InputStream inputStream = new FileInputStream(f)){
            while (true){
                int ret = inputStream.read();
                if(ret == -1){
                    break;
                }
                stringBuilder.append((char)ret);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
        // 判定 content 是否是读取的 StringBuilder 的子串
        //    indexOf 如果找到了子串, 就返回合理下标. 没找到子串, 就返回 -1
        return stringBuilder.indexOf(content) != -1;
    }