JavaSE个人复习式整理知识点之⽂件操作、IO流

本文详细介绍了Java中的File类、IO流的各种操作,包括构造方法、常用方法、目录遍历、递归计算、文件搜索、过滤器优化,以及字节流和字符流的深入理解,同时涵盖了IO异常处理的最新进展和Properties类的使用。
摘要由CSDN通过智能技术生成

1 File类

1.1 概述

java.io.File 类是⽂件和⽬录路径名的抽象表示,主要⽤于⽂件和⽬录的创建、查找和删除等操作。

1.2 构造⽅法

  • public File(String pathname):通过将给定的路径名字符串转换为抽象路径名来创建新的 File 实例。
  • public File(String parent, String child):从⽗路径名字符串和⼦路径名字符串创建新的 File 实例。
  • public File(File parent, String child):从⽗抽象路径名和⼦路径名字符串创建新的File 实例。
  • 构造举例,代码如下:
// ⽂件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname);
// ⽂件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2);
// 通过⽗路径和⼦路径字符串
String parent = "d:\\aaa";
String child = "bbb.txt";
File file3 = new File(parent, child);
// 通过⽗级File对象和⼦路径字符串
File parentDir = new File("d:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);

⼩贴⼠:

  1. ⼀个File对象代表硬盘中实际存在的⼀个⽂件或者⽬录。
  2. ⽆论该路径下是否存在⽂件或者⽬录,都不影响 File 对象的创建。

1.3 常⽤⽅法

获取功能的⽅法
  • public String getAbsolutePath():返回此File的绝对路径名字符串。
  • public String getPath():将此File转换为路径名字符串。
  • public String getName() :返回由此File表示的⽂件或⽬录的名称。
  • public long length() :返回由此File表示的⽂件的⻓度。
    ⽅法演示,代码如下:
public class FileGet {
    public static void main(String[] args) {
        File f = new File("d:/aaa/bbb.java");
        System.out.println("⽂件绝对路径:" + f.getAbsolutePath());
        System.out.println("⽂件构造路径:" + f.getPath());
        System.out.println("⽂件名称:" + f.getName());
        System.out.println("⽂件⻓度:" + f.length() + "字节");
        File f2 = new File("d:/aaa");
        System.out.println("⽬录绝对路径:" + f2.getAbsolutePath());
        System.out.println("⽬录构造路径:" + f2.getPath());
        System.out.println("⽬录名称:" + f2.getName());
        System.out.println("⽬录⻓度:" + f2.length());
    }
}
/*
        输出结果:
        ⽂件绝对路径:d:\aaa\bbb.java
        ⽂件构造路径:d:\aaa\bbb.java
        ⽂件名称:bbb.java
        ⽂件⻓度:636字节
        ⽬录绝对路径:d:\aaa
        ⽬录构造路径:d:\aaa
        ⽬录名称:aaa
        ⽬录⻓度:4096
 */

API中说明:length(),表示⽂件的⻓度。但是File对象表示⽬录,则返回值未指定。

绝对路径和相对路径
  • 绝对路径:从盘符开始的路径,这是⼀个完整的路径。
  • 相对路径:相对于项⽬⽬录的路径,这是⼀个便捷的路径,开发中经常使⽤。
public class FilePath {
    public static void main(String[] args) {
        // D盘下的bbb.java⽂件
        File f = new File("D:\\bbb.java");
        System.out.println(f.getAbsolutePath());
        // 项⽬下的bbb.java⽂件
        File f2 = new File("bbb.java");
        System.out.println(f2.getAbsolutePath());
    }
}
/*
    输出结果:
    D:\bbb.java
    D:\idea_project_test4\bbb.java
 */
判断功能的⽅法
  • public boolean exists():此File表示的⽂件或⽬录是否实际存在。
  • public boolean isDirectory():此File表示的是否为⽬录。
  • public boolean isFile():此File表示的是否为⽂件。

⽅法演示,代码如下:

public class FileIs {
    public static void main(String[] args) {
        File f = new File("d:\\aaa\\bbb.java");
        File f2 = new File("d:\\aaa");
        // 判断是否存在
        System.out.println("d:\\aaa\\bbb.java 是否存在:" + f.exists());
        System.out.println("d:\\aaa 是否存在:" + f2.exists());
        // 判断是⽂件还是⽬录
        System.out.println("d:\\aaa ⽂件?:" + f2.isFile());
        System.out.println("d:\\aaa ⽬录?:" + f2.isDirectory());
    }
}
/*
    输出结果:
    d:\aaa\bbb.java 是否存在:true
    d:\aaa 是否存在:true
    d:\aaa ⽂件?:false
    d:\aaa ⽬录?:true
        
 */
创建删除功能的⽅法
  • public boolean createNewFile():当且仅当具有该名称的⽂件尚不存在时,创建⼀个新的空⽂件。
  • public boolean delete():删除由此File表示的⽂件或⽬录。
  • public boolean mkdir():创建由此File表示的⽬录。
  • public boolean mkdirs():创建由此File表示的⽬录,包括任何必需但不存在的⽗⽬录。

⽅法演示,代码如下:

public class FileCreateDelete {
    public static void main(String[] args) throws IOException {
        // ⽂件的创建
        File f = new File("aaa.txt");
        System.out.println("是否存在:" + f.exists()); // false
        System.out.println("是否创建:" + f.createNewFile()); // true
        System.out.println("是否存在:" + f.exists()); // true
        // ⽬录的创建
        File f2= new File("newDir");
        System.out.println("是否存在:" + f2.exists()); // false
        System.out.println("是否创建:" + f2.mkdir()); // true
        System.out.println("是否存在:" + f2.exists()); // true
        // 创建多级⽬录
        File f3= new File("newDira\\newDirb");
        System.out.println(f3.mkdir()); // false
        File f4= new File("newDira\\newDirb");
        System.out.println(f4.mkdirs()); // true
        // ⽂件的删除
        System.out.println(f.delete()); // true
        // ⽬录的删除
        System.out.println(f2.delete()); // true
        System.out.println(f4.delete()); // false
    }
}

API中说明:delete⽅法,如果此File表示⽬录,则⽬录必须为空才能删除。

1.4 ⽬录的遍历

  • public String[] list():返回⼀个String数组,表示该File⽬录中的所有⼦⽂件或⽬录。
  • public File[] listFiles():返回⼀个File数组,表示该File⽬录中的所有的⼦⽂件或⽬录。
public class FileFor {
    public static void main(String[] args) {
        File dir = new File("d:\\java_code");
        // 获取当前⽬录下的⽂件以及⽂件夹的名称。
        String[] names = dir.list();
        for (String name : names) {
            System.out.println(name);
        }
        // 获取当前⽬录下的⽂件以及⽂件夹对象,只要拿到了⽂件对象,那么就可以获取更多信息
        File[] files = dir.listFiles();
        for (File file : files) {
            System.out.println(file);
        }
    }
}

⼩贴⼠:
调⽤listFiles⽅法的File对象,表示的必须是实际存在的⽬录,否则返回null,⽆法进⾏遍历。

2 递归

2.1 概述

  • 递归:指在当前⽅法内调⽤⾃⼰的这种现象。
  • 递归的分类
    • 递归分为两种,直接递归和间接递归。
    • 直接递归称为⽅法⾃身调⽤⾃⼰。
    • 间接递归可以A⽅法调⽤B⽅法,B⽅法调⽤C⽅法,C⽅法调⽤A⽅法。
  • 注意事项
    • 递归⼀定要有条件限定,保证递归能够停⽌下来,否则会发⽣栈内存溢出。
    • 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发⽣栈内存溢出。
    • 构造⽅法,禁⽌递归
public class Demo01DiGui {
    public static void main(String[] args) {
        // a();
        b(1);
    }

    /*
     * 3.构造⽅法,禁⽌递归
     * 编译报错:构造⽅法是创建对象使⽤的,不能让对象⼀直创建下去
     */
    public Demo01DiGui() {
        // Demo01DiGui();
    }

    /*
     * 2.在递归中虽然有限定条件,但是递归次数不能太多。否则也会发⽣栈内存溢出。
     * 4993
     * Exception in thread "main" java.lang.StackOverflowError
     */
    private static void b(int i) {
        System.out.println(i);
        //添加⼀个递归结束的条件,i==5000的时候结束
        if (i == 5000) {
            return; // 结束⽅法
        }
        b(++i);
    }

    /*
    * 1.递归⼀定要有条件限定,保证递归能够停⽌下来,否则会发⽣栈内存溢出。
    Exception in thread "main"
    * java.lang.StackOverflowError
    */
    private static void a() {
        System.out.println("a⽅法");
        a();
    }
}

2.2 递归累加求和

计算1 ~ n的和

分析:num的累和 = num + (num-1)的累和,所以可以把累和的操作定义成⼀个⽅法,递归调⽤。
实现代码:

public class DiGui {
    public static void main(String[] args) {
        //计算1~num的和,使⽤递归完成
        int a = 10;
        // 调⽤求和的⽅法
        int sum = getSum(a);
        // 输出结果
        System.out.println(sum);
    }
    /*
     * 通过递归算法实现.
     * 参数列表:int
     * 返回值类型:int
     */
    private static int getSum(int a) {
        /*
         * num为1时,⽅法返回1,
         * 相当于是⽅法的出⼝,num总有是1的情况
         */
        if (a == 1) {
            return 1;
        }
        /*
         * a不为1时,⽅法返回 a +(--a)的累和
         * 递归调⽤getSum⽅法
         */
        return a + getSum(--a);
    }
}
代码执⾏图解

在这里插入图片描述

⼩贴⼠:递归⼀定要有条件限定,保证递归能够停⽌下来,次数不要太多,否则会发⽣栈内存溢出。

2.3 递归求阶乘

  • 阶乘:所有⼩于及等于该数的正整数的积。

      n的阶乘:n! = n * (n-1) *...* 3 * 2 * 1
    

分析:这与累和类似,只不过换成了乘法运算,学员可以⾃⼰练习,需要注意阶乘值符合int类型的范围。

推理得出:n! = n * (n-1)!

代码实现:

public class DiGui {
    //计算n的阶乘,使⽤递归完成
    public static void main(String[] args) {
        int a = 5;
        // 调⽤求阶乘的⽅法
        int sum = getValue(a);
        // 输出结果
        System.out.println(sum);
    }
    /*
     * 通过递归算法实现.
     * 参数列表:int
     * 返回值类型:int
     */
    private static int getValue(int a) {
        // 1的阶乘为1
        if (a == 1)
            return a;
        /*
         * n不为1时,⽅法返回 n! = n*(n-1)!
         * 递归调⽤getValue⽅法
         */
        return a * getValue(--a);
    }
}

2.4 递归打印多级⽬录

分析:多级⽬录的打印,就是当⽬录的嵌套。遍历之前,⽆从知道到底有多少级⽬录,所以我们还是要使⽤递归实现。
代码实现:

public class DiGuiDemo2 {
    public static void main(String[] args) {
        // 创建File对象
        File dir = new File("D:\\aaa");
        // 调⽤打印⽬录⽅法
        printDir(dir);
    }
    
    public static void printDir(File dir) {
        // 获取⼦⽂件和⽬录
        File[] files = dir.listFiles();
        // 循环打印
        /*
         * 判断:
         * 当是⽂件时,打印绝对路径.
         * 当是⽬录时,继续调⽤打印⽬录的⽅法,形成递归调⽤.
         */
        for (File file : files) {
            // 判断
            if (file.isFile()) {
                // 是⽂件,输出⽂件绝对路径
                System.out.println("⽂件名:" + file.getAbsolutePath());
            } else {
                // 是⽬录,输出⽬录绝对路径
                System.out.println("⽬录:" + file.getAbsolutePath());
                // 继续遍历,调⽤printDir,形成递归
                printDir(file);
            }
        }
    }
}

3 综合案例

3.1 ⽂件搜索

搜索 D:\aaa ⽬录中的 .java ⽂件。
分析:

  1. ⽬录搜索,⽆法判断多少级⽬录,所以使⽤递归,遍历所有⽬录。
  2. 遍历⽬录时,获取的⼦⽂件,通过⽂件名称,判断是否符合条件。

代码实现:

public class DiGuiDemo3 {
    public static void main(String[] args) {
        // 创建File对象
        File dir = new File("D:\\aaa");
        // 调⽤打印⽬录⽅法
        printDir(dir);
    }
    public static void printDir(File dir) {
        // 获取⼦⽂件和⽬录
        File[] files = dir.listFiles();
        // 循环打印
        for (File file : files) {
            if (file.isFile()) {
                // 是⽂件,判断⽂件名并输出⽂件绝对路径
                if (file.getName().endsWith(".java")) {
                    System.out.println("⽂件名:" + file.getAbsolutePath());
                }
            } else {
                // 是⽬录,继续遍历,形成递归
                printDir(file);
            }
        }
    }
}

3.2 ⽂件过滤器优化

java.io.FileFilter是⼀个接⼝,是File的过滤器。 该接⼝的对象可以传递给File类的listFiles(FileFilter)作为参数, 接⼝中只有⼀个⽅法。
boolean accept(File pathname):测试pathname是否应该包含在当前File⽬录中,符合则返回true。
分析:

  1. 接⼝作为参数,需要传递⼦类对象,重写其中⽅法。我们选择匿名内部类⽅式,⽐较简单。
  2. accept ⽅法,参数为File,表示当前File下所有的⼦⽂件和⼦⽬录。保留住则返回true,过滤掉则返回false。保留规则:
    1. 要么是.java⽂件。
    2. 要么是⽬录,⽤于继续遍历。
  3. 通过过滤器的作⽤, listFiles(FileFilter) 返回的数组元素中,⼦⽂件对象都是符合条件的,可以直接打印。
    代码实现
public class DiGuiDemo4 {
    public static void main(String[] args) {
        File dir = new File("D:\\aaa");
        printDir2(dir);
    }
    public static void printDir2(File dir) {
        // 匿名内部类⽅式,创建过滤器⼦类对象
        File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.getName().endsWith(".java") ||
                        pathname.isDirectory();
            }
        });
        // 循环打印
        for (File file : files) {
            if (file.isFile()) {
                System.out.println("⽂件名:" + file.getAbsolutePath());
            } else {
                printDir2(file);
            }
        }
    }
}

### 3.3 Lambda优化
分析
: FileFilter 是只有⼀个⽅法的接⼝,因此可以⽤lambda表达式简写。
lambda格式:

() -> { }

代码实现:

    public static void printDir3(File dir) {
        // lambda的改写
        File[] files = dir.listFiles(f ->{
            return f.getName().endsWith(".java") || f.isDirectory();
        });
        // 循环打印
        for (File file : files) {
            if (file.isFile()) {
                System.out.println("⽂件名:" + file.getAbsolutePath());
            } else {
                printDir3(file);
            }
        }
    }

4 IO概述

4.1 什么是IO

⽣活中,你肯定经历过这样的场景。当你编辑⼀个⽂本⽂件,忘记了 ctrl+s ,可能⽂件就⽩⽩编辑了。当你电脑上插⼊⼀个U盘,可以把⼀个视频,拷⻉到你的电脑硬盘⾥。那么数据都是在哪些设备上的呢?比如键盘、内存、硬盘、外接设备等等。

我们把这种数据的传输,可以看做是⼀种数据的流动,按照流动的⽅向,以内存为基准,分为输⼊input输出output,即流向内存是输⼊流流出内存的输出流

Java中I/O操作主要是指使⽤ java.io包下的内容,进⾏输⼊、输出操作。输⼊也叫做读取数据,输出也叫做作写出数据。

4.2 IO的分类

根据数据的流向分为:输⼊流输出流

  • 输⼊流 :把数据从 其他设备上读取到 内存中的流。
  • 输出流 :把数据从 内存中写出到 其他设备上的流。

格局数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流。

4.3 IO的流向说明图解

在这里插入图片描述

4.4 顶级⽗类们在这里插入图片描述

5 字节流

5.1 ⼀切皆为字节

⼀切⽂件数据(⽂本、图⽚、视频等)在存储时,都是以⼆进制数字的形式保存,都⼀个⼀个的字节,那么传输时⼀样如此。所以,字节流可以传输任意⽂件数据。在操作流的时候,我们要时刻明确,⽆论使⽤什么样的流对象,底层传输的始终为⼆进制数据。

5.2 字节输出流【OutputStream】

java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到⽬的地。它定义了字节输出流的基本共性功能⽅法。

  • public void close():关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写⼊此输出流。
  • public void write(byte[] b, int off, int len):从指定的字节数组写⼊ len 字节,从偏移量 off 开始输出到此输出流。
  • public abstract void write(int b):将指定的字节输出流。

⼩贴⼠:
close⽅法,当完成流的操作时,必须调⽤此⽅法,释放系统资源。

5.3 FileOutputStream类

OutputStream有很多⼦类,我们从最简单的⼀个⼦类开始。
java.io.FileOutputStream类是⽂件输出流,⽤于将数据写出到⽂件。

构造⽅法
  • public FileOutputStream(File file):创建⽂件输出流以写⼊由指定的 File对象表示的⽂件。
  • public FileOutputStream(String name):创建⽂件输出流以指定的名称写⼊⽂件。

当你创建⼀个流对象时,必须传⼊⼀个⽂件路径。该路径下,如果没有这个⽂件,会创建该⽂件。如果有这个⽂件,会清空这个⽂件的数据。

  • 构造举例,代码如下:
public class FileOutputStreamConstructor {
    public static void main(String[] args) throws FileNotFoundException {
        // 使⽤File对象创建流对象
        File file = new File("a.txt");
        FileOutputStream fos1 = new FileOutputStream(file);
        // 使⽤⽂件名称创建流对象
        FileOutputStream fos2 = new FileOutputStream("b.txt");
    }
}
写出字节数据
  1. 写出字节: write(int b) ⽅法,每次可以写出⼀个字节数据,代码使⽤演示:
public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        // 写出数据
        fos.write(97); // 写出第1个字节
        fos.write(98); // 写出第2个字节
        fos.write(99); // 写出第3个字节
        // 关闭资源
        fos.close();
    }
}
//输出结果:
//abc

⼩贴⼠:

  1. 虽然参数为int类型四个字节,但是只会保留⼀个字节的信息写出。
  2. 流操作完毕后,必须释放系统资源,调⽤close⽅法,千万记得。
  1. 写出字节数组: write(byte[] b) ,每次可以写出数组中的数据,代码使⽤演示:
public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        // 字符串转换为字节数组
        byte[] b = "Java程序员".getBytes();
        // 写出字节数组数据
        fos.write(b);
        // 关闭资源
        fos.close();
    }
}
//输出结果:
//Java程序员
  1. 写出指定⻓度字节数组: write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节,代码使⽤演示:
public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        // 字符串转换为字节数组
        byte[] b = "abcde".getBytes();
        // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
        fos.write(b, 2, 2);
        // 关闭资源
        fos.close();
    }
}
//输出结果:
//cd
数据追加续写

经过以上的演示,每次程序运⾏,创建输出流对象,都会清空⽬标⽂件中的数据。如何保留⽬标⽂件中数据,还能继续添加新数据呢?

  • public FileOutputStream(File file, boolean append): 创建⽂件输出流以写⼊由指定的 File对象表示的⽂件。
  • public FileOutputStream(String name, boolean append):创建⽂件输出流以指定的名称写⼊⽂件。

这两个构造⽅法,参数中都需要传⼊⼀个boolean类型的值, true 表示追加数据, false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使⽤演示:

public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt",true);
        // 字符串转换为字节数组
        byte[] b = "abcde".getBytes();
        // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
        fos.write(b);
        // 关闭资源
        fos.close();
    }
}
//⽂件操作前:cd
//⽂件操作后:cdabcde

写出换⾏

Windows系统⾥,换⾏符号是 \r\n 。
代码使⽤演示:

public class FOSWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        // 定义字节数组
        byte[] words = { 97, 98, 99, 100, 101 };
        // 遍历数组
        for (int i = 0; i < words.length; i++) {
            // 写出⼀个字节
            fos.write(words[i]);
            // 写出⼀个换⾏, 换⾏符号转成数组写出
            fos.write("\r\n".getBytes());
        }
        // 关闭资源
        fos.close();
    }
}
//输出结果:
//a
//b
//c
//d
//e
  • 回⻋符 \r 和换⾏符 \n :
    回⻋符:回到⼀⾏的开头(return)。
    换⾏符:下⼀⾏(newline)。
  • 系统中的换⾏:
    Windows系统⾥,每⾏结尾是 回⻋+换⾏ ,即 \r\n ;
    Unix系统⾥,每⾏结尾只有 换⾏ ,即 \n ;
    Mac系统⾥,每⾏结尾是 回⻋ ,即 \r 。从 Mac OS X开始与Linux统⼀。

5.4 字节输⼊流【InputStream】

java.io.InputStream抽象类是表示字节输⼊流的所有类的超类,可以读取字节信息到内存中。它定义了字节输⼊流的基本共性功能⽅法。

  • public void close():关闭此输⼊流并释放与此流相关联的任何系统资源。
  • public abstract int read():从输⼊流读取数据的下⼀个字节。
  • public int read(byte[] b):从输⼊流中读取⼀些字节数,并将它们存储到字节数组 b中 。

⼩贴⼠:
close⽅法,当完成流的操作时,必须调⽤此⽅法,释放系统资源。

5.5 FileInputStream类

java.io.FileInputStream类是⽂件输⼊流,从⽂件中读取字节。

构造⽅法
  • FileInputStream(File file):通过打开与实际⽂件的连接来创建⼀个 FileInputStream,该⽂件由⽂件系统中的 File对象 file命名。
  • FileInputStream(String name):通过打开与实际⽂件的连接来创建⼀个FileInputStream ,该⽂件由⽂件系统中的路径名 name命名。

当你创建⼀个流对象时,必须传⼊⼀个⽂件路径。该路径下,如果没有该⽂件,会抛出FileNotFoundException 。

  • 构造举例,代码如下:
public class FileInputStreamConstructor {
    public static void main(String[] args) throws FileNotFoundException {
        // 使⽤File对象创建流对象
        File file = new File("a.txt");
        FileInputStream fos1 = new FileInputStream(file);
        // 使⽤⽂件名称创建流对象
        FileInputStream fos2 = new FileInputStream("b.txt");
    }
}
读取字节数据
  1. 读取字节read⽅法,每次可以读取⼀个字节的数据,提升为int类型,读取到⽂件末尾,返回 -1,代码使⽤演示:
public class FISRead {
    public static void main(String[] args) throws IOException{
        // 使⽤⽂件名称创建流对象
        FileInputStream fis = new FileInputStream("read.txt");
        // 读取数据,返回⼀个字节
        int read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        // 读取到末尾,返回-1
        read = fis.read();
        System.out.println(read);
        // 关闭资源
        fis.close();
    }
}
//输出结果:
//a
//b
//c
//d
//e
//-1

循环改进读取⽅式,代码使⽤演示:

public class FISRead {
    public static void main(String[] args) throws IOException{
        // 使⽤⽂件名称创建流对象
        FileInputStream fis = new FileInputStream("read.txt");
        // 定义变量,保存数据
        int b;
        // 循环读取
        while ((b = fis.read()) != -1) {
            System.out.println((char)b);
        }
        // 关闭资源
        fis.close();
    }
}
//输出结果:
//a
//b
//c
//d
//e

⼩贴⼠:

  1. 虽然读取了⼀个字节,但是会⾃动提升为int类型。
  2. 流操作完毕后,必须释放系统资源,调⽤close⽅法,千万记得。
  1. 使⽤字节数组读取: read(byte[] b) ,每次读取b的⻓度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回 -1 ,代码使⽤演示:
public class FISRead {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象.
        FileInputStream fis = new FileInputStream("read.txt"); // ⽂件中为abcde
        // 定义变量,作为有效个数
        int len;
        // 定义字节数组,作为装字节数据的容器
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b)) != -1) {
            // 每次读取后,把数组变成字符串打印
            System.out.println(new String(b));
        }
        // 关闭资源
        fis.close();
    }
}
//输出结果:
//ab
//cd
//ed

错误数据 d ,是由于最后⼀次读取时,只读取⼀个字节 e ,数组中,上次读取的数据没有被完全替换,所以要通过 len ,获取有效的字节,代码使⽤演示:

public class FISRead {
    public static void main(String[] args) throws IOException{
        // 使⽤⽂件名称创建流对象.
        FileInputStream fis = new FileInputStream("read.txt"); // ⽂件中为abcde
        // 定义变量,作为有效个数
        int len;
        // 定义字节数组,作为装字节数据的容器
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b)) != -1) {
        // 每次读取后,把数组的有效字节部分,变成字符串打印
            System.out.println(new String(b, 0, len));// len 每次读取的有效字节个数
        }
        // 关闭资源
        fis.close();
    }
}
//输出结果:
//ab
//cd
//e

⼩贴⼠:
使⽤数组读取,每次读取多个字节,减少了系统间的IO操作次数,从⽽提⾼了读写的效率,建议开发中使⽤。

5.6 字节流练习:图⽚复制

复制原理图解在这里插入图片描述

案例实现

复制图⽚⽂件,代码使⽤演示:

public class Copy {
    public static void main(String[] args) throws IOException {
        // 1.创建流对象
        // 1.1 指定数据源
        FileInputStream fis = new FileInputStream("D:\\test.jpg");
        // 1.2 指定⽬的地
        FileOutputStream fos = new FileOutputStream("test_copy.jpg");
        // 2.读写数据
        // 2.1 定义数组
        byte[] b = new byte[1024];
        // 2.2 定义⻓度
        int len;
        // 2.3 循环读取
        while ((len = fis.read(b)) != -1) {
            // 2.4 写出数据
            fos.write(b, 0, len);
        }
        // 3.关闭资源
        fos.close();
        fis.close();
    }
}

⼩贴⼠:
流的关闭原则:先开后关,后开先关。

6 字符流

当使⽤字节流读取⽂本⽂件时,可能会有⼀个⼩问题。就是遇到中⽂字符时,可能不会显示完整的字符,那是因为⼀个中⽂字符可能占⽤多个字节存储。所以 Java 提供⼀些字符流类,以字符为单位读写数据,专⻔⽤于处理⽂本⽂件。

6.1 字符输⼊流【Reader】

java.io.Reader抽象类是表示⽤于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输⼊流的基本共性功能⽅法。

  • public void close():关闭此流并释放与此流相关联的任何系统资源。
  • public int read():从输⼊流读取⼀个字符。
  • public int read(char[] cbuf):从输⼊流中读取⼀些字符,并将它们存储到字符数组cbuf 中 。

6.2 FileReader类

java.io.FileReader类是读取字符⽂件的便利类。构造时使⽤系统默认的字符编码和默认字节缓冲区。

⼩贴⼠:

  1. 字符编码:字节与字符的对应规则。Windows系统的中⽂编码默认是GBK编码表。idea中UTF-8
  2. 字节缓冲区:⼀个字节数组,⽤来临时存储字节数据。
构造⽅法
  • FileReader(File file) :创建⼀个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName) :创建⼀个新的 FileReader ,给定要读取的⽂件的名称。

当你创建⼀个流对象时,必须传⼊⼀个⽂件路径。类似于FileInputStream。

  • 构造举例,代码如下:
public class FileReaderConstructor throws IOException{
    public static void main(String[] args) {
        // 使⽤File对象创建流对象
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);
        // 使⽤⽂件名称创建流对象
        FileReader fr = new FileReader("b.txt");
    }
}

读取字符数据

  1. 读取字符read⽅法,每次可以读取⼀个字符的数据,提升为int类型,读取到⽂件末尾,返回-1,循环读取,代码使⽤演示:
public class FRRead {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存数据
        int b;
        // 循环读取
        while ((b = fr.read()) != -1) {
            System.out.println((char)b);
        }
        // 关闭资源
        fr.close();
    }
}
//输出结果:
//程
//序
//员

⼩贴⼠:虽然读取了⼀个字符,但是会⾃动提升为int类型。

  1. 使⽤字符数组读取: read(char[] cbuf) ,每次读取b的⻓度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回 -1 ,代码使⽤演示:
public class FRRead {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存有效字符个数
        int len;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf)) != -1) {
            System.out.println(new String(cbuf));
        }
        // 关闭资源
        fr.close();
    }
}
//输出结果:
//程序
//员序

获取有效的字符改进,代码使⽤演示:

public class FISRead {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileReader fr = new FileReader("read.txt");
        // 定义变量,保存有效字符个数
        int len;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf)) != -1) {
            System.out.println(new String(cbuf,0,len));
        }
        // 关闭资源
        fr.close();
    }
}
//输出结果:
//程序
//员

6.3 字符输出流【Writer】

java.io.Writer抽象类是表示⽤于写出字符流的所有类的超类,将指定的字符信息写出到⽬的地。它定义了字节输出流的基本共性功能⽅法。

  • void write(int c):写⼊单个字符。
  • void write(char[] cbuf):写⼊字符数组。
  • abstract void write(char[] cbuf, int off, int len):写⼊字符数组的某⼀部分,off数组的开始索引,len写的字符个数。
  • void write(String str):写⼊字符串。
  • void write(String str, int off, int len):写⼊字符串的某⼀部分,off字符串的开始索引,len写的字符个数。
  • void flush():刷新该流的缓冲。
  • void close():关闭此流,但要先刷新它。

6.4 FileWriter类

java.io.FileWriter类是写出字符到⽂件的便利类。构造时使⽤系统默认的字符编码和默认字节缓冲区。

构造⽅法
  • FileWriter(File file):创建⼀个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName):创建⼀个新的 FileWriter,给定要读取的⽂件的名称。
    当你创建⼀个流对象时,必须传⼊⼀个⽂件路径,类似于FileOutputStream。
  • 构造举例,代码如下:
public class FileWriterConstructor {
    public static void main(String[] args) throws IOException {
        // 使⽤File对象创建流对象
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);
        // 使⽤⽂件名称创建流对象
        FileWriter fw = new FileWriter("b.txt");
    }
}
基本写出数据

写出字符write(int b)⽅法,每次可以写出⼀个字符数据,代码使⽤演示:

public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 写出数据
        fw.write(97); // 写出第1个字符
        fw.write('b'); // 写出第2个字符
        fw.write('C'); // 写出第3个字符
        fw.write(30000); // 写出第4个字符,中⽂编码表中30000对应⼀个汉字。
        /*
         *【注意】关闭资源时,与FileOutputStream不同。
         * 如果不关闭,数据只是保存到缓冲区,并未保存到⽂件。
         */
        // fw.close();
    }
}
//输出结果:
//abC⽥

⼩贴⼠:

  1. 虽然参数为int类型四个字节,但是只会保留⼀个字符的信息写出。
  2. 未调⽤close⽅法,数据只是保存到了缓冲区,并未写出到⽂件中。
关闭和刷新

因为内置缓冲区的原因,如果不关闭输出流,⽆法写出字符到⽂件中。但是关闭的流对象,是⽆法继续写出数据的。如果我们既想写出数据,⼜想继续使⽤流,就需要 flush ⽅法了。

  • flush:刷新缓冲区,流对象可以继续使⽤。
  • close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使⽤了。
    代码使⽤演示:
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 写出数据,通过flush
        fw.write('刷'); // 写出第1个字符
        fw.flush();
        fw.write('新'); // 继续写出第2个字符,写出成功
        fw.flush();
        // 写出数据,通过close
        fw.write('关'); // 写出第1个字符
        fw.close();
        fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream
        closed
        fw.close();
    }
}

⼩贴⼠:即便是flush⽅法写出了数据,操作的最后还是要调⽤close⽅法,释放系统资源。

写出其他数据
  1. 写出字符数组write(char[] cbuf)write(char[] cbuf, int off, int len),每次可以写出字符数组中的数据,⽤法类似FileOutputStream,代码使⽤演示:
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 字符串转换为字节数组
        char[] chars = "程序员".toCharArray();
        // 写出字符数组
        fw.write(chars); // 程序员
        // 写出从索引1开始,2个字符。索引1是'序',两个字符,也就是'序员'。
        fw.write(b, 1, 2); // 序员
        // 关闭资源
        fos.close();
    }
}
  1. 写出字符串write(String str)write(String str, int off, int len),每次可以写出字符串中的数据,更为⽅便,代码使⽤演示:
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 字符串
        String msg = "程序员";
        // 写出字符数组
        fw.write(msg); //程序员
        // 写出从索引1开始,2个字符。索引1是'序',两个字符,也就是'序员'。
        fw.write(msg, 1, 2); // 序员
        // 关闭资源
        fos.close();
    }
}
  1. 续写和换⾏:操作类似于FileOutputStream。
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使⽤⽂件名称创建流对象,可以续写数据
        FileWriter fw = new FileWriter("fw.txt",true);
        // 写出字符串
        fw.write("Java");
        // 写出换⾏
        fw.write("\r\n");
        // 写出字符串
        fw.write("程序员");
        // 关闭资源
        fw.close();
    }
}
//输出结果:
//Java
///程序员

⼩贴⼠:字符流,只能操作⽂本⽂件,不能操作图⽚,视频等⾮⽂本⽂件。
当我们单纯读或者写⽂本⽂件时 使⽤字符流 其他情况使⽤字节流

7 IO异常的处理

7.1 JDK7 前处理

之前的⼊⻔练习,我们⼀直把异常抛出,⽽实际开发中并不能这样处理,建议使⽤ try…catch…finally 代码块,处理异常部分,代码使⽤演示:

public class HandleException1 {
    public static void main(String[] args) {
        // 声明变量
        FileWriter fw = null;
        try {
            // 创建流对象
            fw = new FileWriter("fw.txt");
            // 写出数据
            fw.write("Java程序员"); // Java程序员
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

7.2 JDK7的处理(扩展知识点了解内容)

还可以使⽤JDK7优化后的 try-with-resource语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。

格式:

try (创建流对象语句,如果多个,使⽤';'隔开) {
         // 读写数据
    } catch (IOException e) {
        e.printStackTrace();
}

代码使⽤演示:

public class HandleException2 {
            public static void main(String[] args) {
                // 创建流对象
                try ( FileWriter fw = new FileWriter("fw.txt"); ) {
                    // 写出数据
                    fw.write("Java程序员"); // Java程序员
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

7.3 JDK9的改进(扩展知识点了解内容)

JDK9中 try-with-resource的改进,对于引⼊对象的⽅式,⽀持的更加简洁。被引⼊的对象,同样可以⾃动关闭,⽆需⼿动close,我们来了解⼀下格式。

改进前格式:

// 被final修饰的对象
final Resource resource1 = new Resource("resource1");
// 普通对象
Resource resource2 = new Resource("resource2");
// 引⼊⽅式:创建新的变量保存
try (Resource r1 = resource1;
	Resource r2 = resource2) {
	// 使⽤对象
}

改进后格式:

// 被final修饰的对象
final Resource resource1 = new Resource("resource1");
// 普通对象
Resource resource2 = new Resource("resource2");
// 引⼊⽅式:直接引⼊
try (resource1; resource2) {
// 使⽤对象
}

改进后,代码使⽤演示:

public class TryDemo {
    public static void main(String[] args) throws IOException {
        // 创建流对象
        final FileReader fr = new FileReader("in.txt");
        FileWriter fw = new FileWriter("out.txt");
        // 引⼊到try中
        try (fr; fw) {
            // 定义变量
            int b;
            // 读取数据
            while ((b = fr.read()) != -1) {
                // 写出数据
                fw.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

8 属性集

8.1 概述

java.util.Properties继承于 Hashtable,来表示⼀个持久的属性集。它使⽤键值结构存储数据,每个键及其对应值都是⼀个字符串。该类也被许多Java类使⽤,⽐如获取系统属性时, System.getProperties⽅法就是返回⼀个 Properties对象。

8.2 Properties类

构造⽅法
  • public Properties():创建⼀个空的属性列表。
基本的存储⽅法
  • public Object setProperty(String key, String value):保存⼀对属性。
  • public String getProperty(String key):使⽤此属性列表中指定的键搜索属性值。
  • public Set<String> stringPropertyNames():所有键的名称的集合。
public class ProDemo {
    public static void main(String[] args) throws FileNotFoundException {
        // 创建属性集对象
        Properties properties = new Properties();
        // 添加键值对元素
        properties.setProperty("filename", "a.txt");
        properties.setProperty("length", "209385038");
        properties.setProperty("location", "D:\\a.txt");
        // 打印属性集对象
        System.out.println(properties);
        // 通过键,获取属性值
        System.out.println(properties.getProperty("filename"));
        System.out.println(properties.getProperty("length"));
        System.out.println(properties.getProperty("location"));
        // 遍历属性集,获取所有键的集合
        Set<String> strings = properties.stringPropertyNames();
        // 打印键值对
        for (String key : strings ) {
            System.out.println(key + " -- " + properties.getProperty(key));
        }
    }
}
/*
    输出结果:
    {filename=a.txt, length=209385038, location=D:\a.txt} a.txt
    209385038
    D:\a.txt
    filename -- a.txt
    length -- 209385038
    location -- D:\a.txt
 */
与流相关的⽅法
  • public void load(InputStream inStream):从字节输⼊流中读取键值对。
    参数中使⽤了字节输⼊流,通过流对象,可以关联到某⽂件上,这样就能够加载⽂本中的数据了。⽂本数据格式:
filename=a.txt
length=209385038
location=D:\a.txt

加载代码演示:

public class ProDemo2 {
    public static void main(String[] args) throws FileNotFoundException {
        // 创建属性集对象
        Properties pro = new Properties();
        // 加载⽂本中信息到属性集
        pro.load(new FileInputStream("read.txt"));
        // 遍历集合并打印
        Set<String> strings = pro.stringPropertyNames();
        for (String key : strings ) {
            System.out.println(key + " -- " + pro.getProperty(key));
        }
    }
}
/*
    输出结果:
    filename -- a.txt
    length -- 209385038
    location -- D:\a.txt
 */

⼩贴⼠:⽂本中的数据,必须是键值对形式,可以使⽤空格、等号、冒号等符号分隔。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值