File、方法递归、IO流

前面我们所有的数据存储在内存中,程序一旦重启或者电脑关机后数据就丢失了,无法长久保存。因此实际工作中我们需要将用户数据全都保存到硬盘上,用以永久保存。

一、File

硬盘中的数据形式就是文件,文件是数据的载体

1.1 概述

  • File类在包java.io.File下,代表操作系统的文件对象(文件,文件夹)。
  • File类提供的功能:定位文件、获取文件本身信息、删除文件、创建文件(文件夹)等。

注意:File创建对象定位文件,删除,获取文件,但不能读写文件内容。

        //1.File类创建对象,参数:(文件、文件夹、绝对路径、相对路径)
        //路径中的\\转义为\,或者路径中使用/,功能一样
        //还可使用File的API separator自动识别系统的间隔符,灵活性更高
        File file1 = new File("D:\\code\\A.txt");
        File file2 = new File("D:/code/A.txt");
        File file3 = new File("D:" + File.separator + "code" + File.separator + "A.txt");
        //File只是封装了一个路径,该路径可以是存在的也可不存在
        File file4 = new File("A:/");
        //当File参数为相对路径时,默认直接在当前工程目录下查找文件
        File file5 = new File("A.txt");
        //该方法用以返回绝对路径,D:\IDEA_Projects\A.txt
        System.out.println(file5.getAbsolutePath());

        //查看文件字节大小,可知文件创建成功
        System.out.println(file3.length());
        System.out.println(file4.length());

1.2 常用API

1.2.1 判断文件类型、获取文件信息
方法名称说明
public boolean isDirectory()测试此抽象路径名表示的File是否为文件夹
public boolean isFile()测试此抽象路径名表示的File是否为文件
public boolean exists()测试此抽象路径名表示的File是否存在
public String getAbsolutePath()返回此抽象路径名的绝对路径名字符串
public String getPath()返回File中包装的路径名字字符串
public String getName()返回由此抽象路径名表示的文件或文件夹的名称
public long lastModified返回文件最后修改时间毫秒值
1.2.2 创建文件、删除文件

delete方法默认只能删除文件和空文件夹,且删除后不走回收站。

方法名称说明
public boolean createNewFile()创建一个新的文件
public boolean mkdir()只能创建一级目录
public boolean mkdirs()创建多级目录
public boolean delete()删除由此抽象路径名表示的文件或空文件夹
1.2.3 遍历文件夹
  • 当调用者不存在时,返回null
  • 当调用者是一个文件时,返回null
  • 当调用者是一个空文件夹时,返回长度为0的数组
  • 当调用者是有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
  • 当调用者是一个隐藏文件的文件夹时,将里面所有文件和文件夹的路径放到File数组中返回,包含隐藏内容
  • 当调用者是一个需要权限才能进入的文件夹时,返回null
方法名称说明
public String[] list()获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回
public File[] listFiles()获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回。(重要)
1.2.4 API练习
  1. 统计一个文件夹中每种文件的个数并打印

    打印格式为:

    ​ txt:3个

    ​ doc:4个

    ​ jpg:6个

        public static void main(String[] args) {
            //创建一个map对象
            Map<String, Integer> map = new HashMap();
            //创建一个路径变量
            String path = "D:" + File.separator + "code";
            //使用getFiles方法,传入路径和map
            map = getFiles(path, map);
            //判断map是否为空(设置的路径为文件就是null),大小是否大于0(文件夹中没有文件则大小为0)
            if (map != null && map.size() > 0) {
                //遍历
                map.forEach((k, v) -> System.out.println(k + "-->" + v));
            }else{
                System.out.println("没有文件!!!");
            }
        }
    
        public static Map getFiles(String path, Map<String, Integer> map) {
            //将传入路径创建为File对象
            File file = new File(path);
            //判断File对象是否为目录,不是目录就直接返回null的map
            if (file.isDirectory()) {
                //遍历File对象中的文件对象数组
                File[] fileArr = file.listFiles();
                //遍历文件对象数组
                for (File file1 : fileArr) {
                    //判断是否为目录
                    if (!file1.isDirectory()) {
                        //分割出文件的后缀
                        String[] fileSplit = file1.getName().split("\\.");
                        String s = fileSplit[fileSplit.length - 1];
                        //看map中是否存在后缀
                        if (map.containsKey(s)) {
                            //存在就继续值+1
                            int count = map.get(s) + 1;
                            map.put(s, count);
                        } else {
                            //不存在,就将后缀存入,值置为1
                            map.put(s, 1);
                        }
                        //如果是目录就继续往里面去获取文件对象遍历
                    } else {
                        getFiles(file1.getAbsolutePath(), map);
                    }
                }
            }
            //返回map对象
            return map;
        }
    
  2. 将某文件夹下一级文件对象,按照最近修改时间降序展示,并显示修改时间。

    打印格式为:

    ​ aaa.txt:2021-03-22 10:23:23

    ​ dlei.doc:2021-03-21 08:23:23

    ​ meinv.jpg:2008-11-11 11:11:11

    public static void main(String[] args) {
        //文件路径
        String path = "D:\\code";
        //使用我们建的方法
        List<File> list = getFileSort(path);
        //遍历list对象
        for (File file : list) {
            //格式化最后修改时间为想要的格式,因为默认是毫秒数
            String time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(file.lastModified());
            //打印文件名和时间
            System.out.println(file.getName() + " : " + time);
        }
    }

    //文件排序
    public static List<File> getFileSort(String path) {
        //创建ArrayList对象,并且调用创建的getFiles方法
        List<File> list = getFiles(path, new ArrayList<File>());
        //null(不是文件夹) size>0(list对象中有文件对象)
        if (list != null && list.size() > 0) {
                //比较文件对象的最后修改时间,并且排序
//            Collections.sort(list, new Comparator<File>() {
//                public int compare(File file, File newFile) {
//                    if (file.lastModified() < newFile.lastModified()) {
//                        return 1;
//                    } else if (file.lastModified() == newFile.lastModified()) {
//                        return 0;
//                    } else {
//                        return -1;
//                    }
//
//                }
//            });
            Collections.sort(list, (o1,o2) -> o1.lastModified() < o2.lastModified() ? 1 : o1.lastModified() == o2.lastModified() ? 0 : -1);

        }
        //返回排序好的list对象
        return list;
    }

    //获取所有文件对象,并存储到List中
    public static List<File> getFiles(String realpath, List<File> files) {
        //创建文件对象
        File realFile = new File(realpath);
        //判断文件是否为文件夹,如果不是就返回null的List
        if (realFile.isDirectory()) {
            //如果是文件夹,就遍历文件夹,获取该文件下的所有文件对象
            File[] subfiles = realFile.listFiles();
            //遍历文件对象数组
            for (File file : subfiles) {
                //判断文件对象是否为文件夹
                if (file.isDirectory()) {
                    //如果是文件夹,就继续获取文件夹中的所有文件对象
                    getFiles(file.getAbsolutePath(), files);
                } else {
                    //如果不是文件夹,就代表是文件,就将该文件对象存储到List中
                    files.add(file);
                }
            }
        }
        return files;
    }

二、方法递归

2.1 概念

什么是递归?

  • 当方法直接调用自己或者间接调用自己的形式称为方法递归(recursion);
    • 直接调用自己:方法自己调用自己
    • 间接调用自己:方法调用其他方法,其他方法又回调方法自己。
  • 递归是算法的一种,在程序设计语言中广泛应用。

**递归的缺点:**递归如果没有控制好终止,会出现递归死循环,导致栈溢出现象。

2.2 核心要素

递归就是把一个复杂的问题,层层转为一个与原问题相似的规模较小的问题来求解。

递归算法三要素(必须有三要素才能写递归):

  • 递归公式:先找到问题的关系式,例如阶乘的关系式就是 f(n)=f(n-1)*n,求和的关系式就是f(n)=f(n-1)+n
  • 递归的终结点:找到问题的终点在哪里,例如阶乘的终点就是1,因为f(1)=1,求和的终点也是1
  • 递归的方向:必须是走向终结点的,例如求和的关系式就是 f(n)=f(n-1)+n,终点为f(1)=1,该关系式可以求1-100的总和,因为n=100想要到终点需要一直减,所以公式是n和n-1的关系(100-1需要一个个减);但是不能求(1-(-100)的总和,因为n=-100想要到终点需要一直加,所以公式为f(n)=f(n+1)+n,是n和n+1的关系
//1-任何正数的总和
public static int sum(int n) {
    if (n==1){
        return 1;
    }
    return sum(n-1) + n;
}

//任何负数-1的总和
public static int sum(int n) {
    if (n==1){
        return 1;
    }
    return sum(n+1) + n;
}

2.3 经典案例

/*
 * 猴子吃桃,猴子摘了若干桃子,第一天吃了桃子的一半后,又多吃一个,
 * 第二天吃了前一天桃子的一半,又多吃一个,天天都吃前一天的一半后,多吃一个,
 * 第十天桃子只剩下1个,问猴子一共摘了多少个桃子
 * 分析:
 *   每一天吃的是前一天的一半+1后,多吃一个,设天数为x,今天的桃子数为f(x),那么今天的f(x)和明天的f(x+1)的关系为
 *       f(x) / 2 - 1 = f(x+1)
 *       f(x) = 2f(x+1) + 2
 *      那么关系公式就是:f(x) = 2f(x+1) + 2
 *      当x=10时,f(10)=1;(递归终结点)
 *      第一天是x,第二天是x+1,那么第十天可以从第一天往上加出来,所以递归方向正确
 *      注意:若递归方向不正确时,
 *              例如当f(x) = 2f(x+1) + 2方向不正确时,
 *                   公式可以改为f(x-1) = 2f(x) +2,得出f(x)和f(x-1)的关系为:f(x)=f(x-1)/2-1
 * */

    public static void main(String[] args) {
        //求第一天的桃子数量
        System.out.println(feibo(1));
        //第二天的桃子数量
        System.out.println(feibo(2));
    }

    public static int feibo(int n) {
        //终结点为第十天
        if (n == 10) {
            return 1;
        }
        return 2 * feibo(n + 1) + 2;
    }

2.4 非规律化递归

2.4.1 案例一:文件搜索

需求:文件搜索,从C:盘中,搜索出某个文件名称并输出绝对路径

    public static void main(String[] args) {
//需求:文件搜索,从C:盘中,搜索出某个文件名称并输出绝对路径
        getFilePath(new File("D:/"), "用户.txt");

    }

    public static void getFilePath(File dir, String fileName) {
        //判断是否为null且是否为目录
        if ( dir != null && dir.isDirectory()) {
            //获取目录下所有一级文件对象数组
            File[] fileArr = dir.listFiles();
            //判断数组对象是否为null且长度大于0
            if (fileArr != null && fileArr.length > 0) {
                //遍历一级文件对象数组
                for (File file : fileArr) {
                    //判断文件对象是否为文件还是目录
                    if (file.isFile()) {
                        if (file.getName().contains(fileName)) {
                            System.out.println("文件位置为:" + file.getPath());
                        }
                    } else {
                        //目录则继续查找
                        getFilePath(file, fileName);
                    }
                }
            }
        } else {
            System.out.println("搜索位置不是目录!!!");
        }
    }
2.4.2 案例二:啤酒问题

需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶啤酒,剩余多少个瓶子和盖子?

    //一共买了多少瓶,和上次买后剩余的瓶子和盖子
    public static int AllBottle;
    public static int lastBottle;
    public static int lastCover;

    //需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶啤酒,剩余多少个瓶子和盖子?
    public static void main(String[] args) {
        buy(10);
        System.out.println("总共买了:" + AllBottle);
        System.out.println("剩余瓶子:" + lastBottle);
        System.out.println("剩余盖子:" + lastCover);
    }

    public static void buy(int money) {
        //钱一共能买多少瓶
        int buyMoney = money / 2;

        AllBottle += buyMoney;
        //上次买后剩余的瓶子和盖子 + 这次买酒剩余的瓶子和盖子 统计
        int bottle = buyMoney + lastBottle;
        int cover = buyMoney + lastCover;

        //因为2块一瓶酒,所以第一次买完后剩余钱就是0元了,所以初值为0,后面拿瓶子和盖子是换酒,当瓶子和盖子不够换时,并不能拿去换成钱
        //统计盖子和瓶子能换多少瓶酒(瓶数*2就是换酒的钱,盖子*4就是换酒的钱)
        int allmoney = 0;
        //可以添加判断瓶子和盖子够不够数,然后变成下一次换酒的钱
        if (bottle >= 2) {
            allmoney += bottle / 2 * 2;
        }
        lastBottle = bottle % 2;
        if (cover >= 4) {
            allmoney += cover / 4 * 2;
        }
        lastCover = cover % 4;

        //判断换酒的钱够不够换,不够换就不递归了
        //这里也是递归的终结点
        if (allmoney>=2){
            buy(allmoney);
        }

    }

2.5 扩展

2.5.1 删除非空文件夹

三、字符集

3.1 概述

什么是字符集?

  • 计算机底层中不可以直存储字符,底层只能存储二进制(0,1)
  • 二级制可以转换为十进制

结论:计算机底层可以直接表示十进制的编码,计算机可一个给人类字符进行编号存储,这套编号规则就是字符集。

字符集有哪些?

  • ASCll字符集

    • ASCll:包括数字、英文、符号。
    • ASCll采用一个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,可以表示数字和英文。
  • GBK

    • 因为ASCll字符集只能表示128个字符信息,并不能表示汉字,所以中国推出GBK编码,其中英文和数字与ASCll字符集一致
    • windows系统默认的码表,兼容ASCll码表,也包含几万个汉字,并支持繁体汉字和部分日韩文字。
    • 注意:GBK是是中国的码表,一个中文以两个字节存储。但不包含世界上所有国家文字。
  • Unicode码表

  • unicode(又称统一码、万国码、单一码)是计算机领域的一项业界字符编码标准。

    • 容纳世界上大多数国家所有常见的文字和符号,一个中文一般以三个字节的形式存储
    • Unicode会先通过UTF-8,UTF-16以及UTF-32的编码成二进制后在存入计算机,其中最常见是UTF-8

注意:

  • Unicode是万国码,以UTF-8编码成一个中文,一般以三个字节的形式存储。
  • UTF-8也兼容ASCLL编码表
  • 技术人员都应该使用UTF-8字符编码
  • 编码前和编码后的字符集必须一致,否则会出现中文乱码
  • 英文和数字再任何国家的编码中都不会出现乱码(因为都兼容Ascall码表)

3.2 编码与解码

注意:编码和解码时的字符集必须一致。

编码
byte[] getBytes()使用平台默认的编码字符集将该String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes(String ,charseName)使用指定的编码字符集将该String编码为一系列字节,将结果存储到新的字节数组中
解码
String(byte[] bytes)使用平台默认的字符集解码指定的字节数组来构造新的String
String(byte[] bytes,String charsetName)使用指定的字符集解码指定的字节数组来构造新的String
    public static void main(String[] args) throws UnsupportedEncodingException {

        //因为UTF-8文字是三个字节,所以占用21字节
        //因为GBK文字是两个字节,所以占用16字节
        String str = "abc123我爱你中国";
        //getBytes()默认编码格式为UTF-8,想编码的格式写入括号
        byte[] bytes_UTF = str.getBytes();
        //选择编码格式需要处理异常,因为程序怕你编码格式写错(此处我们选择抛出异常)
        byte[] bytes_GBK = str.getBytes("GBK");
        System.out.println(bytes_UTF.length);//18
        //编码后:[97, 98, 99, 49, 50, 51, -26, -120, -111, -25, -120, -79, -28, -67, -96, -28, -72, -83, -27, -101, -67]
        System.out.println(Arrays.toString(bytes_UTF));
        System.out.println(bytes_GBK.length);//16
        //编码后:[97, 98, 99, 49, 50, 51, -50, -46, -80, -82, -60, -29, -42, -48, -71, -6]
        System.out.println(Arrays.toString(bytes_GBK));

        //解码,注意:解码的格式必须与编码时的格式一致,不然会乱码
        String strOpenUTF = new String(bytes_UTF,"UTF-8");
        String strOpenGBK = new String(bytes_GBK,"GBK");
        System.out.println(strOpenUTF);
        System.out.println(strOpenGBK);

        //乱码:abc123鎴戠埍浣犱腑鍥�
        String strOpenError= new String(bytes_UTF,"GBK");
        System.out.println(strOpenError);

    }

四、IO流

4.1 概述

IO流也称为输入输出流,用来读写数据的。

  • I表示input,是数据从硬盘读入内存的过程,称之为输入,负责读
  • O表示output,是数据从内存写出到硬盘的过程,称之为输出,负责写

在这里插入图片描述

字节流和字符流如何选择?

  • 字节流适合做一切文件数据的拷贝(音视频、文本)
  • 字节流不适合读取中文内容输出
  • 字符流适合做文本文件的操作(读、写)

4.2 IO流体系

流分为四大类:

  • 字节输入流:以内存为基准,本地磁盘文件/网络中的数据以字节的形式读入到内存中的流称为字节输入流
  • 字节输出流:以内存为基准,将内存中的数据以字节的形式写出到硬盘文件/网络中的流称为字节输出流
  • 字符输入流:以内存为基准,本地磁盘的文件/网络中的数据以字符的形式读入到内存中的流称为字符输入流
  • 字符输出流:以内存为基准,将内存中的数据以字符的形式写出到硬盘的文件/网络中的流称为字符输出流

在这里插入图片描述

4.2.1 文件字节输入流

作用:以内存为基准,本地磁盘文件/网络中的数据以字节的形式读入到内存中的流称为字节输入流

构造器说明
public FileInputStrem(File file)创建字节输入流管道与源文件对象接通
pubic FileInputStream(String pathName)创建字节输入流管道源文件路径接通
方法名称说明
public int read()每次读取一个字节返回,如果无字节可读就返回-1
public int read(byte[] buffer)每次读取一个字节数组,返回读取的字节个数,如果无字节可读返回-1
    public static void main(String[] args) throws IOException {
        //这里我们使用多态,用父类引用指向子类对象
        //创建字节输入流与源文件对象接通
//        InputStream inFile = new FileInputStream(new File( "Logback-use" + File.separator+ "src" + File.separator + "demo1.txt"));
        //创建字节输入流与文件路径接通,其实底层还是将路径new成文件对象(需要处理异常,因为系统怕你路径输入错误,这里我们抛出)
        InputStream in = new FileInputStream("Logback-use" + File.separator+ "src" + File.separator + "demo1.txt");

        //read每次读取一个字节,返回字节的编码值(int),没有可读时返回-1
//        int len = in.read();
//        System.out.println(len + "," + (char)len);//97

        //当读取到一个文字时,会产生乱码,因为utf-8中一个文字占3个字节
//        int len = in.read();
//        System.out.println(len + "," + (char)len);//230,æ

        //读取一个字节数组(设置每次读取的字节大小)
//        byte[] buffer = new byte[3];
//        int len = in.read(buffer);//返回值为读取的字节数,此时读取的字节存储在数组中,文字一般为负数
//        System.out.println(len + "," + Arrays.toString(buffer));//3,[-26, -120, -111](这三个字节表示一个字)

        //循环读取文件内容(一次读取一个字节数组)
        byte[] buffer = new byte[3];
        int len;
        while ((len = in.read(buffer))!= -1){
            //这里我们使用String的构造方法,打印显示如果不设置显示的字节长度就会出现乱码
            //文件内容为abcd
            //因为“abcd”是4个字节,一次读取3个字节存储到数组,当读第二次时只有1个字节可读
            //那么字节数组中就只会覆盖第一个字节的内容,第一次存储的后两个字节内容并没有覆盖
            //所以会导致打印abcdbc,所以我们设置每次打印显示(0-读取的字节数)来限制
//            System.out.print(new String(buffer));//abcdbc
            System.out.print(new String(buffer,0,len));//abcd
        }
    }
4.2.1.1 弊端的解决

弊端::读取中文字符输出(打印)无法避免乱码问题(读取一个字节和读取一个字节数组都会遇到这种问题)

处理方法:

  • 方法一:我们可以创建一个字节数组,大小为源文件总字节大小,那样就可以一次性读完全部字节。
  • 方法二:使用提供的API,可以读取全部字节(readAllBytes(),返回类型为byte[])

**问题:**如果文件过大,字节数组可能引起内存溢出

    public static void main(String[] args) throws Exception {
        //创建流和源文件对象的连接
        File file1 = new File("Logback-use/src/demo1.txt");
        FileInputStream in1 = new FileInputStream(file1);
        //因为字节流只能使用一次,一旦读取了就无法在读了
        File file2 = new File("Logback-use/src/demo1.txt");
        FileInputStream in2 = new FileInputStream(file2);

        //创建字节数组,长度为文件对象的总长度
        byte[] buffer = new byte[(int) file1.length()];
        //因为是一次性读取总字节长度,所以只需要read一下
        in1.read(buffer);
        //打印读取后的字节数组,使用String的构造方法,传入字节数组
        System.out.print(new String(buffer));

        //使用所提供的的API,readAllBytes(),使用String的构造方法
        System.out.print(new String(in2.readAllBytes()));
        //abcd我爱中国
        //abcd我爱中国
        //abcd我爱中国
        //abcd我爱中国
    }
4.2.2 文件字节输出流

字节流适合做一切文件数据的拷贝,因为任何文件底层都是字节,拷贝是一字不漏转移字节,只要前后文件格式,编码规则没有问题

作用:以内存为基准,将内存中的数据以字节的形式写出到硬盘文件/网络中的流称为字节输出流

注意:

  • 字节输出流只能以字节为参数,默认字节输出流是覆盖的形式写数据
  • 字节输出流使用write(“\r\n”.getBytes())来书写换行
  • 输出流的地址必须加上文件名才能写,否则"(拒绝访问。)"
  • 写出去的数据只有刷新数据后才能生效
  • 一旦接通了流资源,使用完后就必须关闭,不然会浪费资源
构造器说明
public FileOutputStream(File file)创建字节输出流管道与源文件对象接通
public FileOutputStream(File file,boolean append)创建字节输出流管道与源文件接通,可追加数据
public FileOutputStream(String pathname)创建字节输出流管道与文件路径接通
public FileOutputStream(String pathName,boolean append)创建字节输出流管道与文件路径接通,可追加数据
方法名称说明
public void write(int a)写一个字节出去
public void write(byte[] buffer)写一个字节数组出去
public void write(byte[] buffer,int pos,int len)写一个字节数组的一部分出去
流的关闭和刷新
flush()刷新流,流还可以继续用
close()关闭流,释放资源,但是关闭前会先自动刷新流。一旦关闭,就不能写数据了
    public static void main(String[] args) throws Exception {
//        OutputStream out = new FileOutputStream(new File("Logback-use/src/demo1.txt"));
        OutputStream out = new FileOutputStream("Logback-use/src/demo1.txt");

        //write写数据,可以传入一个字节或字节数组
        out.write(67);
        out.write('B');

        //刷新
        out.flush();

        byte[] buffer = {'a','b','c'};
        out.write(buffer);
        out.write(buffer,1,2);
        //CBabcbc

        out.close();
//        out.write('1');//流已经关闭,在写会报错

        //多加一个true参数,表示追加
        OutputStream out1 = new FileOutputStream("Logback-use/src/demo1.txt",true);
        //换行,需要使用getBytes()方法去将字符串变成字符数组
        out1.write("\r\n".getBytes());
        //getBytes()方法去将字符串变成字符数组,源文件的编码值必须与转变的编码值相同,不然会乱码,getBytes()默认UTF-8编码
        byte[] bytes = "这是追加的数据".getBytes("Utf-8");
        out1.write(bytes);
        //CBabcbc
        //这是追加的数据

    }
4.2.2.1 字符串转字节数组

getBytes可以将字符串转为字节数组

"这是追加的数据".getBytes("Utf-8")
4.2.3 文件字符输入流

字节流读取中文输出会存在什么问题?

  • 会乱码,或者内存溢出

读取中文输出,哪个流更合适?为什么?

  • 字符流更合适,最小单位是按照单个字符读取的
构造器说明
public FileReader(File file)创建字符输入流管道与源文件对象接通
public FileReader(String pathname)创建字符输入流管道与源文件路径接通
方法名称说明
public int read()每次读取一个字符返回,当无字符可读就返回-1
public int read(char[] buffer)每次读取一个字符数组,返回读取的字符个数,当无字符可读就返回-1
        try (
                //        Reader redaer = new FileReader(new File("Logback-use\\src\\demo1.txt"));
                Reader reader = new FileReader("Logback-use\\src\\demo1.txt");
        ) {

            //每次读取一个字符
//            int len = reader.read();
//            System.out.println(len + "," + (char)len);//97,a
//            int len1 = reader.read();
//            System.out.println(len1 + "," + (char)len1);//25105,我

            //创建一个字符数组,每次读取一个字符数组
            char[] buffer = new char[3];
            //返回值位读取了多少个字符个数
            int len = reader.read(buffer);
            System.out.println(len + "," + Arrays.toString(buffer));//3,[a, 我, 爱]

        } catch (Exception e) {
            e.printStackTrace();
        }
4.2.4 文件字符输出流
  • 输出流的地址必须加上文件名才能写,否则"(拒绝访问。)"
构造器说明
public FileWriter(File file)创建字符输出流管道与源文件对象接通
public FileWriter(File file,boolean append)创建字符输出流管道与源文件对象接通,可追加数据
public FileWriter(String filepath)创建字符输出流管道与源文件路径接通
public FileWriter(String filepath,boolean append)创建字符输出流管道与源文件路径接通,可追加数据
方法名称说明
void write(int c)写一个字符
void write(char[] buffer)写一个字符数组
void write(char[] buffer,int off,int len)写一个字符数组的部分
void write(String str)写一个字符串
void write(String str,int off,int len)写一个字符串的一部分
关闭和刷新
flush()刷新流,还可以继续写数据
close()关闭流,释放资源,关闭之前会自动刷新流,关闭后无法使用流
        try (
                Writer writer = new FileWriter("Logback-use/src/demo1.txt");

        ){
            //写一个字符
            writer.write(97);

            //字符串为参数写入
            writer.write("我是地球人");
            writer.write("\r\n");
            //字符数组为参数写入
            char[] buffer = "我是中国人".toCharArray();
            writer.write(buffer);
            writer.write("\r\n");
            writer.write(buffer,0,3);
            //a我是地球人
            //我是中国人
            //我是中
        } catch (IOException e) {
            e.printStackTrace();
        }

4.3 资源释放的方式

因为使用了IO流资源完毕后,我们还需要关闭他,防止过多的资源浪费。所以java提供了一些方式方便我们去提醒我们关闭资源。

注意:

  • JDK7和JDK9的()中只能放置资源对象,否则报错
  • 什么是资源?
  • 资源就是实现了Closeable/AutoCloseable接口的类的对象
4.3.1 try-catch-finally

finally:在异常处理时提供finally块来执行所有清除操作,比如IO流中的释放资源

  • 被finally控制的语句最终一定会执行,除非JVM退出
  • 异常处理标准格式为:try…catch…finally
//标准使用格式
    public static void main(String[] args) throws IOException {
        OutputStream out = null;
        try {

//            int a = 10 / 0;//如果在out赋值前出现异常,那么out就是null

            out = new FileOutputStream("a.txt");
            out.write('a');

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("这里的一定会被执行,除非JVM退出");
            //这里我们可以添加一个判断,看out流是否为null,null的话就不需要关闭(out为null也去关闭,就会出现异常)
            if (out != null) {
                //close也有异常需要处理,这里我们选择抛出,因为系统怕该资源已经被关闭过了
                out.close();
            }
        }
    }
4.3.1 try-catch-resource

finally虽然可以用于释放资源,但是代码过于繁琐,所以JDK7和JDK9进行了改进方案

4.3.1.1 JDK7方案(常用)

JDK7的精简格式,在try()中去定义资源,当try代码块执行完毕后,会自动去关闭所定义的资源**(try()中只能使用资源,不然会报错)**

        //格式如下
        try(
                //代码块执行完毕后会自动关闭这些定义的资源
                InputStream in = new FileInputStream("a.txt");
                OutputStream out = new FileOutputStream("a.txt");
                ) {
            in.read();
            out.write('a');
            System.out.println("代码块执行完毕后会自动关闭资源");

        } catch (IOException e) {
            e.printStackTrace();
        }
4.3.1.2 JDK9方案(不常用)

在这里插入图片描述

4.4 练习:拷贝文件夹

需求:将磁盘的文件夹拷贝到另一个文件夹下,包括文件夹中的全部信息

思路:

  • 根据数据源创建字节输入和字节输出流对象
  • 读写数据,复制视频
  • 释放资源
  • 我们需要遍历文件夹,如果是文件则拷贝过去,如果是文件夹就要进行1-1创建,继续复制内容
    public static void main(String[] args) {
        //创建源地址对象和目的地址对象
        File file = new File("D:\\练习目录\\原地址");
        File newFile = new File("D:\\练习目录\\目的地址");
        //调用方法
        getFile(file,newFile);
    }

    /**
     *
     * @param file 源地址对象
     * @param newFile 目的地址对象
     */
    public static void getFile(File file,File newFile) {

        //当原路径为null 或者 原路径的文件对象不存在
        if ( file == null || !file.exists()) {
//            System.out.println("目录不存在!!!");
            return;
        }

        //将原地址的当前文件名赋值给目的地址,让目的地址中有同名的文件名
        String newPath = newFile.getPath() + File.separator + file.getName();

        //当原路径对象为文件时(第一次进入该方法时源地址为文件时执行)
        if (file.isFile()) {
            //在目的地址创建该文件,并退出方法
            inAndOut(file,new File(newPath));
            return;
        }

        //只有为文件夹才会执行后面,先遍历目录得到所有文件对象
        //创建同名文件夹
        createDir(new File(newPath));
        //将文件夹中的所有文件对象装到文件对象数组中
        File[] files = file.listFiles();
        //当文件夹没有权限时
        if (files == null){
//            System.out.println("文件夹权限不足");
            return;
        }

        //遍历文件夹且判断是否为空的文件夹
        if (files.length == 0){
//            System.out.println("目录为空目录");
            return;
        }

        //遍历文件对象数组
        for (File file1 : files) {
            //判断数组中的文件对象是否为文件还是文件夹
            if (file1.isFile()){
                //如果是文件就将文件名追加到目的地址后面
                String newFilePath = newPath + File.separator + file1.getName();
                //直接将源地址的文件内容写到目的地址的该文件中
                inAndOut(file1,new File(newFilePath));
            }else{
                //如果是文件夹就继续调用getFile方法去获取文件
                File newDirFile = new File(newPath);
                getFile(file1,newDirFile);
            }
        }
    }

    //创建目录
    public static void createDir(File newDirFile){
//        System.out.println("创建了一个目录:" + newDirFile.getName());
        newDirFile.mkdir();
    }

    //源地址的文件内容写到目的地址的同名文件中
    public static void inAndOut(File oldFile,File newFile){
        try (
                InputStream in = new FileInputStream(oldFile);
                OutputStream out = new FileOutputStream(newFile);
                ){
//            System.out.println("创建了一个文件:" + newFile.getName());
            out.write(in.readAllBytes());

        }catch (Exception e){
            e.printStackTrace();
        }
    }

五、FileFilter(文件过滤器)

FileFilter是一个接口

public interface FileFilter
//重写方法
boolean accept(File pathname)
  • 当调用File类中的listFiles()方法时,支持传入FileFilter接口的实现类,对获取文件进行过滤,只有满足条件的文件才可以出现在listFiles()的返回值中。
        File[] files = file.listFiles(new FilenameFilter() {
            //该过滤器用来过滤文件夹中后缀.png的文件不出现在File[]中
            //如果是复制文件夹,那么文件夹中.png文件不复制,其他的文件都复制
            @Override
            public boolean accept(File dir, String name) {
                if (name.endsWith(".png")){
                    return false
                }
                return true;
            }
        });
        File[] files = file.listFiles(new FileFilter() {
            //该过滤器会过滤掉文件大小大于1M的文件(文件夹大小为0)
            @Override
            public boolean accept(File f) {
                if (f.length() > 1024 * 1024) {
                    return false;
                }
                return true;
            }
        });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值