Java SE基础知识详解第[15]期—File、方法递归、IO流

写在前面:

        每一个不曾起舞的日子,都是对生命的辜负。

        希望看到这里的每一个人都能努力学习,不负韶华,成就更好的自己。


        以下仅是个人学习过程中的一些想法与感悟,Java知识博大精深,作为初学者,个人能力有限,哪里写的不够清楚、明白,还请各位不吝指正,欢迎交流与讨论。如果有朋友因此了解了一些知识或对Java有了更深层次的理解,从而进行更进一步的学习,那么这篇文章的意义也就达到了。

目录

1.File类概述

2.File类的常用API

3.方法递归

3.1递归的形式和特点

3.2递归的算法流程、核心要素

3.3递归经典问题

3.4非规律化递归案例-文件搜索

3.5非规律化递归案例-啤酒问题

4.字符集

4.1常见字符集介绍

4.2字符集的编码、解码操作

5.IO流概述

6.字节流的使用

6.1文件字节输入流:每次读取一个字节

6.2文件字节输入流:每次读取一个字节数组

6.3文件字节输出流:写字节数据到文件

6.4文件拷贝

7.资源释放的方式

8.字符流的使用

8.1文件字符输入流—一次读取一个字符

8.2文件字符输入流—一次读取一个字符数组

8.3文件字符输出流


File、方法递归、IO流

1.File类概述

        File类在包java.io.File下、代表操作系统的文件对象(文件、文件夹)

        File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能。

        File类作用:创建对象定位文件,可以删除、获取文件信息等。但是不能读写文件内容

File类构造器

方法名

说明

public File (String pathname)

根据文件路径创建文件对象

public File (String parent, String child)

从父路径名字符串和子路径名字符串创建文件对象

public File (File parent, String child)

根据父路径对应文件对象和子路径名字符串

创建文件对象

        File对象可以定位文件和文件夹。

        File封装的对象仅仅是一个路径名,这个路径可以是存在的,也可以是不存在的。

绝对路径和相对路径

        如下图所示,绝对路径:从盘符开始,依赖于当前系统

如下图所示,相对路径:不带盘符,默认直接到当前工程下的目录寻找文件

        示例代码如下:

public static void main(String[] args) {
        // 1.创建File对象(指定文件路径)
        // 路径写法
        // 反斜杠\ D:\360Downloads\modiloginbg.jpg
        File f = new File("D:\\360Downloads\\modiloginbg.jpg");
        // 正斜杠/ D:/360Downloads/modiloginbg.jpg
//        File f = new File("D:/360Downloads/modiloginbg.jpg");
        // API形式生成拼接符替代正(反)斜杠 File.separator 好处:跨平台,可以自动生成Windows、Linux等不同平台的拼接符
//        File f = new File("D:" + File.separator + "360Downloads" + File.separator + "modiloginbg.jpg");
        long size = f.length(); // 返回文件字节大小
        System.out.println(size); // 612265

        // 2.※File创建对象,支持绝对路径也支持相对路径
        // 绝对路径:从盘符开始
        File f2 = new File("D:\\360Downloads\\214510.jpg");
        System.out.println(f2.length()); // 363322

        // 相对路径:不带盘符,通常用来定义模块中的某个文件,相对于工程下的路径
        File f3 = new File("day10_file_io_app\\src\\2055376.jpg");
        System.out.println(f3.length()); // 777576

        // 3.File创建对象,可以是文件,也可以是文件夹
        File f4 = new File("D:\\360Downloads");
//        System.out.println(f4.length()); // 一般不通过这种方式获取文件夹的大小
        System.out.println(f4.exists()); // true 判断该文件对象是否存在
    }

注:路径写法

        ①反斜杠\ D:\360Downloads\modiloginbg.jpg

File f = new File("D:\\360Downloads\\modiloginbg.jpg");

        ②正斜杠/ D:/360Downloads/modiloginbg.jpg

File f = new File("D:/360Downloads/modiloginbg.jpg");

        ③API形式生成拼接符替代正(反)斜杠 File.separator

File f = new File("D:" + File.separator + "360Downloads" + File.separator + "modiloginbg.jpg");

        好处:跨平台,可以自动生成Windows、Linux等不同平台的拼接符。

2.File类的常用API

File类的判断文件类型、获取文件信息功能常用API

方法名

说明

public boolean isDirectory()

测试此抽象路径名表示的File是否为文件夹

public boolean isFile()

测试此抽象路径名表示的File是否为文件

public boolean exists()

测试此抽象路径名表示的File是否存在

public String getAbsolutePath()

返回此抽象路径名的绝对路径名字符串

public String getPath()

将此抽象路径名转换为路径名字符串(怎么定义的就怎么拿)

public String getName()

返回由此抽象路径名表示的文件名+后缀或文件夹名

public long lastModified()

返回文件最后修改的时间毫秒值

 public long length()

返回文件字节大小

        示例代码如下:

    public static void main(String[] args) {
        // 绝对路径
        File f1 = new File("D:\\360Downloads\\214510.jpg");
        //  1.public boolean isDirectory()	测试此抽象路径名表示的File是否为文件夹
        System.out.println(f1.isDirectory()); // false
        //  2.public boolean isFile()	测试此抽象路径名表示的File是否为文件
        System.out.println(f1.isFile()); // true
        //  3.public boolean exists()	测试此抽象路径名表示的File是否存在
        System.out.println(f1.exists()); // true
        //  4.public String getAbsolutePath()	返回此抽象路径名的绝对路径名字符串
        System.out.println(f1.getAbsolutePath()); // D:\360Downloads\214510.jpg
        //  5.public String getPath()	将此抽象路径名转换为路径名字符串(怎么定义的就怎么拿)
        System.out.println(f1.getPath()); // D:\360Downloads\214510.jpg
        //  6.public String getName()	返回由此抽象路径名表示的文件名+后缀或文件夹名
        System.out.println(f1.getName()); // 214510.jpg
        //  7.public long lastModified()	返回文件最后修改的时间毫秒值
        long time = f1.lastModified();
        System.out.println("最后修改时间:" + new SimpleDateFormat("yyyy-MM-ss HH:mm:ss").format(time));
        // 最后修改时间:2021-04-50 13:02:50
    }

File类创建文件的功能常用API

方法名

说明

public boolean createNewFile()

创建一个新的空的文件(几乎不用)

public boolean mkdir()

只能创建一级文件夹

public boolean mkdirs()

可以创建多级文件夹

File类删除文件的功能常用API

方法名

说明

public boolean delete ()

删除由此抽象路径名表示的文件或空文件夹(文件在被占用状态时也可以被删除)

        示例代码如下:

    public static void main(String[] args) throws IOException {
        File f = new File("day10_file_io_app\\src\\2055376.jpg");
        //  1.public boolean createNewFile()	创建一个新的空的文件(几乎不用)
        System.out.println(f.createNewFile()); // false
        File f2 = new File("day10_file_io_app\\src\\2055376aaa.jpg");
        System.out.println(f2.createNewFile()); // true

        //  2.public boolean mkdir()	只能创建一级文件夹
        File f3 = new File("D:\\360Downloads\\abc");
        System.out.println(f3.mkdir()); // true

        File f4 = new File("D:\\360Downloads\\abc\\def\\ghi");
        System.out.println(f4.mkdir()); // false
        //  3.public boolean mkdirs()	可以创建多级文件夹
        File f5 = new File("D:\\360Downloads\\abc\\def\\ghi");
        System.out.println(f5.mkdirs()); // true
        //  4.public boolean delete ()	删除由此抽象路径名表示的文件或空文件夹(文件在被占用状态时也可以被删除)
        System.out.println(f2.delete()); // true
        System.out.println(f3.delete()); // false 非空文件夹不可删除
        System.out.println(f5.delete()); // true 只能删除空文件夹
    }

注:delete方法默认只能删除文件和空文件夹

        delete方法直接删除不走回收站

File类的遍历功能

方法名

说明

public String[] list()

获取当前目录下所有的"一级文件名称"

到一个字符串数组中去返回

public File[] listFiles()(常用)

获取当前目录下所有的"一级文件对象"

到一个文件对象数组中去返回(重点)

        示例代码如下:

    public static void main(String[] args) {
        File f1 = new File("D:\\360Downloads");
        //  1.public String[] list()
        //  获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回
        String[] nameArray = f1.list();
        System.out.println(Arrays.toString(nameArray));
        // [2055376.jpg, 214510.jpg, abc, addlogin.jpg, bgpic5.jpg, loginbg.jpg, loginbg1.jpg, loginbg2.jpg, modiloginbg.jpg, Software, wpcache]

        //  2.public File[] listFiles()(常用)
        //  获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
        File[] fileArray = f1.listFiles();
        for (File file : fileArray) {
            System.out.println(file.getAbsolutePath());
        }
        /*
            D:\360Downloads\2055376.jpg
            D:\360Downloads\214510.jpg
            D:\360Downloads\abc
            D:\360Downloads\addlogin.jpg
            D:\360Downloads\bgpic5.jpg
            D:\360Downloads\loginbg.jpg
            D:\360Downloads\loginbg1.jpg
            D:\360Downloads\loginbg2.jpg
            D:\360Downloads\modiloginbg.jpg
            D:\360Downloads\Software
            D:\360Downloads\wpcache
         */
    }

listFiles方法注意事项:

        当调用者不存在时,返回null。

        当调用者是一个文件时,返回null。

        当调用者是一个空文件夹时,返回一个长度为0的数组。

        当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回。

        当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容。

        当调用者是一个需要权限才能进入的文件夹时,返回null。

3.方法递归

3.1递归的形式和特点

什么是方法递归?

        方法直接调用自己或者间接调用自己的形式称为方法递归(recursion)。

        递归做为一种算法在程序设计语言中广泛应用。

递归的形式

        直接递归:方法自己调用自己。

        间接递归:方法调用其他方法,其他方法又回调方法自己。

方法递归存在的问题?

        递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象。

        示例代码如下:

public class RecursionDemo1 {
    public static void main(String[] args) {
        // test(); // 进入死循环,栈内存溢出错误
        // test2(); // 进入死循环,栈内存溢出错误
    }

    // 直接递归
    public static void test() {
        System.out.println("--------test执行--------");
        test(); // 方法递归,直接递归
    }

    // 间接递归
    public static void test2() {
        System.out.println("-------test2执行--------");
        test3(); // 方法递归,间接递归
    }

    private static void test3() {
        System.out.println("-------test3执行--------");
        test2();
    }
}

3.2递归的算法流程、核心要素

案例:递归案例导学-计算n的阶乘

        需求:计算n的阶乘的结果,使用递归思想解决,我们先从数学思维上理解递归的流程和核心点。

分析:

        ① 假如我们认为存在一个公式是 f(n) = 1*2*3*4*5*6*7*…(n-1)*n;

        ② 那么公式等价形式就是: f(n) = f(n-1) *n

        ③ 如果求的是 1-5的阶乘 的结果,手工应该应该如何应用上述公式计算。

        ④ f(5) = f(4) * 5 f(4) = f(3) * 4 f(3) = f(2) * 3 f(2) = f(1) * 2 f(1) = 1

递归解决问题的思路

        把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。

递归算法三要素大体可以总结为:

        递归的公式: f(n) = f(n-1) * n

        递归的终结点:f(1)

        递归的方向必须走向终结点

        示例代码如下:

public class RecursionDemo2 {
    public static void main(String[] args) {
        /*
            案例:递归案例导学-计算n的阶乘
            需求:计算n的阶乘的结果,使用递归思想解决,我们先从数学思维上理解递归的流程和核心点。
            分析:
            ① 假如我们认为存在一个公式是 f(n) = 1*2*3*4*5*6*7*…(n-1)*n;
            ② 那么公式等价形式就是: f(n) = f(n-1) *n
            ③ 如果求的是 1-5的阶乘 的结果,手工应该应该如何应用上述公式计算。
            ④ f(5) = f(4) * 5	f(4) = f(3) * 4	f(3) = f(2) * 3	f(2) = f(1) * 2	f(1) = 1
         */
        System.out.println("5的阶乘是:" + f(5)); // 5的阶乘是:120

    }

    public static int f(int n) {
        if (n == 1) {
            return 1;
        } else {
            return f(n - 1) * n;
        }
    }
}

3.3递归经典问题

案例-猴子吃桃问题

        猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个。第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个。以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个。等到第10天的时候发现桃子只有1个了。

        需求:请问猴子第一天摘了多少个桃子?

分析:

        ① 整体来看,每一天都是做同一个事件,典型的规律化问题,考虑递归三要素

        ② 递归公式:f(n) = 2 * (f(n + 1) + 1)

        ③ 递归终结点:f(10) = 1

        ④ 递归方向:n不断增大,向10靠拢,合理。

        示例代码如下:

public class RecursionDemo4 {
    public static void main(String[] args) {
        /*
        案例-猴子吃桃问题
        猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个。第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个。以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个。等到第10天的时候发现桃子只有1个了。 
        需求:请问猴子第一天摘了多少个桃子? 
        分析:
        ① 整体来看,每一天都是做同一个事件,典型的规律化问题,考虑递归三要素:
        ② 递归公式:f(n) = 2 * (f(n + 1) + 1)
        ③ 递归终结点:f(10) = 1
        ④ 递归方向:n不断增大,向10靠拢,合理。
         */
        System.out.println("第一天摘下" + eat(1) + "个桃子"); // 第一天摘下1534个桃子
    }

    public static int eat(int n) {
        if (n == 10) {
            return 1;
        } else {
            return (eat(n + 1)  + 1 ) * 2;
        }
    }

}

3.4非规律化递归案例-文件搜索

案例-文件搜索

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

分析:

        ① 先定位出的应该是一级文件对象。

        ② 遍历全部一级文件对象,判断是否是文件。

        ③ 如果是文件,判断是否是自己想要的。

        ④ 如果是文件夹,需要继续递归进去重复上述过程。

        示例代码如下:

public class RecursionDemo5 {
    public static void main(String[] args) {
        // 需求:去D盘搜索2055376.jpg文件
        searchFile(new File("D:/"), "2055376.jpg");
        /*
            文件存在,路径名称:D:\360Downloads\2055376.jpg
            文件存在,路径名称:D:\sss2055376.jpg
         */

    }

    /**
     * create by: 全聚德在逃烤鸭、
     * description: 搜索某个目录下的所需文件
     * create time: 2022/6/3 0003 15:50
     *
     * @param dir      查询目录
     * @param fileName 被搜索的文件名称
     * @return void
     */
    public static void searchFile(File dir, String fileName) {
        // 判断是否是文件夹
        if (dir != nul&& dir.isDirectory()) {
            File[] fileArray = dir.listFiles();
            // 判断文件数组中是否不为null且有内容(数组为null时,增强for循环报错空值异常)
            if (fileArray != nul&& fileArray.length > 0) {
                for (File file : fileArray) {
                    // 判断是否是文件
                    if (file.isFile()) { // 是文件,判断是否是被搜索的文件
                        if (file.getName().contains(fileName)) {
                            System.out.println("文件存在,路径名称:" + file.getAbsolutePath());
                        }
                    } else { // 否则为文件夹,调用递归方法searchFile继续向下搜索
                        searchFile(file, fileName);
                    }
                }
            }
        } else {
            System.out.println("当前搜索的位置不是文件夹!");
        }
    }
}

        注:切记注意空值异常的问题,尽可能增加判断空值的代码!!!

3.5非规律化递归案例-啤酒问题

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

        答案:15瓶 3盖子 1瓶子。

        第一种方式:将空瓶、瓶盖折算为金钱,买酒方法参数为money。

        示例代码如下:

public class RecursionDemo6 {
    // 定义静态变量,代表买酒的数目,每轮买酒后增加
    public static int totalNumber;
    public static int lastBottleNumber; // 上轮剩余的空瓶数
    public static int lastCoverNumber; // 上轮剩余的瓶盖数

    public static void main(String[] args) {
        // 需求:需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子
        // 答案:15瓶 3盖子 1瓶子

        buyBeer(10); // 可以喝15瓶酒,剩余1个空瓶,3个瓶盖
    }

    public static void buyBeer(int money) {
        int buyNumber = money / 2;
        // 判断剩余的钱是否可以买酒
        if (buyNumber == 0) { // 若不能买酒,则退出并输出相应信息
            System.out.println("可以喝" + totalNumber + "瓶酒,剩余" + lastBottleNumber + "个空瓶," + lastCoverNumber + "个瓶盖");
            return;
        }
        totalNumber += buyNumber; // 更新买酒的数目
        // 统计本轮空瓶与瓶盖总数
        int bottleNumber = lastBottleNumber + buyNumber;
        int coverNumber = lastCoverNumber + buyNumber;

        // 统计可以换算的用于买酒的钱
        int allMoney = money - buyNumber * 2;
        // 空瓶相当于1元一瓶,满两瓶可买一瓶啤酒,相当于2元
        allMoney += (bottleNumber / 2) * 2; // 若不满2瓶,则无法购买,因此需要保留lastBottleNumber / 2
        lastBottleNumber = bottleNumber % 2; // 更新本轮剩余空瓶数,用于下一轮买酒

        // 瓶盖相当于0.5元一个,满四个可买一瓶啤酒,相当于2元
        allMoney += (coverNumber / 4) * 2; // 若不满4个,则无法购买,因此需要保留lastCoverNumber / 4
        lastCoverNumber = coverNumber % 4; // 更新本轮剩余瓶盖瓶数,用于下一轮买酒

        // 调用递归方法,进行下一轮买酒
        buyBeer(allMoney);
    }
}

        第二种方式:买酒方法参数为金钱、当前空瓶数量、当前瓶盖数量。

        示例代码如下:

public class RecursionDemo7 {
    // 定义静态变量,代表买酒的数目,每轮买酒后增加
    public static int totalNumber;

    public static void main(String[] args) {
        // 需求:需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子
        // 答案:15瓶 3盖子 1瓶子

        buyBeer(10, 0, 0); // 可以喝15瓶酒,剩余1个空瓶,3个瓶盖
    }

    public static void buyBeer(int money, int lastBottleNumber, int lastCoverNumber) {
        int bottleNumber = lastBottleNumber; // 本轮剩余空瓶数目
        int coverNumber = lastCoverNumber; // 本轮剩余瓶盖数目
        // 金额足够用钱买酒,钱不够了用空瓶、瓶盖换酒
        // 判断剩余金额是否足够买酒
        if (money >= 2) {
            int buyNumber = money / 2;
            totalNumber += buyNumber; // 更新买酒的数目
            money -= buyNumber * 2; // 更新剩余金额
            // 更新当前剩余空瓶、瓶盖数目
            bottleNumber += buyNumber;
            coverNumber += buyNumber;
        } else {
            if (bottleNumber >= 2) {
                int buyNumber = bottleNumber / 2; // 空瓶换购啤酒数目
                totalNumber += buyNumber; // 更新买酒的数目
                // 更新当前剩余空瓶、瓶盖数目
                bottleNumber = bottleNumber % 2 + buyNumber;
                coverNumber += buyNumber;
            }
            if (coverNumber >= 4) {
                int buyNumber = coverNumber / 4; // 瓶盖换购啤酒数目
                totalNumber += buyNumber; // 更新买酒的数目
                // 更新当前剩余空瓶、瓶盖数目
                bottleNumber += buyNumber;
                coverNumber = coverNumber % 4 + buyNumber;
            }
        }

        // 调用递归方法,进行下一轮买酒
        if (money >= 2 || bottleNumber >= 2 || coverNumber >= 4) { // 若满足下一轮买酒条件则继续,否则结束买酒
            buyBeer(money, bottleNumber, coverNumber);
        } else {
            System.out.println("可以喝" + totalNumber + "瓶酒,剩余" + bottleNumber + "个空瓶," + coverNumber + "个瓶盖");
        }
    }
}

4.字符集

4.1常见字符集介绍

字符集基础知识

        计算机底层不可以直接存储字符的。计算机中底层只能存储二进制(0、1)。

        二进制是可以转换成十进制的。

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

ASCII字符集

        ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。

        ASCII使用1个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。

GBK

        Windows系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字。

        注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。

Unicode码表

        Unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准,容纳世界上大多数国家的所有常见文字和符号。

        由于Unicode会先通过UTF-8,UTF-16,以及UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是UTF-8(UTF-8是Unicode的一种编码方式)

注:

        Unicode是万国码,UTF-8编码后一个中文一般以三个字节的形式存储

        UTF-8也要兼容ASCII编码表。

        技术人员都应该使用UTF-8的字符集编码。

        编码前和编码后的字符集需要一致,否则会出现中文乱码。

        汉字存储和展示过程如下图所示。

知识点汇总:

        字符串常见的字符底层组成是什么样的?

        英文和数字等在任何国家的字符集中都占1个字节。

        GBK字符中一个中文字符占2个字节

        UTF-8编码中一个中文1般占3个字节

        编码前的字符集和编码后的字符集有什么要求?

        必须一致,否则会出现中文字符乱码。

        英文和数字在任何国家的编码中都不会乱码。

4.2字符集的编码、解码操作

String编码常用API

方法名

说明

byte[] getBytes()

使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中

byte[] getBytes(String charsetName)

使用指定的字符集将该 String编码为一系列字节,

将结果存储到新的字节数组中

String解码常用构造器

方法名

说明

String (byte[] bytes)

通过使用平台的默认字符集解码指定的字节数组来构造新的String

String (byte[] bytes, String charsetName)

通过指定的字符集解码指定的字节数组

来构造新的String

String(byte bytes[], int offset, int length)

解码指定的字节数组,指定开始位置与解码长度

        示例代码如下:

public class Test {
    public static void main(String[] args) throws UnsupportedEncodingException {
        // 1.编码   把文字转换为字节(可以使用指定编码)
        String name = "abc我爱你中国";
        byte[] bytes = name.getBytes(); // 以当前代码默认字符集进行编码(当前默认UTF-8)
        // 英文、数字在UTF-8字符集中为正数,汉字为负数
        System.out.println(Arrays.toString(bytes)); // [97, 98, 99, -26, -120, -111, -25, -120, -79, -28, -67, -96, -28, -72, -83, -27, -101, -67]
        System.out.println(bytes.length); // 18

        // 指定编码(字符集)为GBK
        byte[] bytes2 = name.getBytes("GBK");
        System.out.println(Arrays.toString(bytes2)); // [97, 98, 99, -50, -46, -80, -82, -60, -29, -42, -48, -71, -6]
        System.out.println(bytes2.length); // 13

        // 2.解码 把字节转换为文字(编码前和编码后的字符集必须一致,否则出现乱码)
        String rs = new String(bytes); // 以当前代码默认字符集进行解码(当前默认UTF-8)
        System.out.println(rs); // abc我爱你中国

        // 指定解码(字符集)为GBK
        String rs2 = new String(bytes2, "GBK");
        System.out.println(rs2); // abc我爱你中国
    }
}

5.IO流概述

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

        I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。

        O表示output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。

IO流的分类

        字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。

        字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。

        字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。

        字符输出流:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件或者网络中去的流称为字符输出流。

        IO流体系如下图所示。

6.字节流的使用

6.1文件字节输入流:每次读取一个字节

        文件字节输入流:FileInputStream

        作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去。

FileInputStream构造器

方法名

说明

public FileInputStream(File file)

创建字节输入流管道与源文件对象接通

public FileInputStream(String pathname)

创建字节输入流管道与源文件路径接通

FileInputStream常用API

方法名

说明

public int read()

每次读取一个字节,将读取的数据内容的编号返回

如果字节已经没有可读的返回-1

public int read(byte[] buffer)

每次读取一个字节数组,将读取的数据数目返回读取的数据内容保存在字节数组buffer中,如果字节已经没有可读的返回-1

        示例代码如下;

public static void main(String[] args) throws Exception {
        // 1.创建一个文件字节输入流管道与源文件接通
        InputStream is = new FileInputStream("day10_file_io_app\\src\\data.txt");

        // 读取第一个字节返回
        int b1 = is.read();
        System.out.println(b1); // 97   字符编号
        System.out.println((char) b1); // a  转为字符型

        int b2 = is.read();
        System.out.println(b2); // 98
        System.out.println((char) b2); // b

        int b3 = is.read();
        System.out.println(b3); // 99
        System.out.println((char) b3); // c

        // 如果字节已经没有可读的返回-1
        int b4 = is.read();
        System.out.println(b4); // -1
    }

        注:每次读取一个字节会不可避免的存在一些问题。

        性能较慢。

        读取中文字符输出无法避免乱码问题(每次读取1个字节,1个中文字符在UTF-8中占3个字节)。

6.2文件字节输入流:每次读取一个字节数组

        示例代码如下:

    public static void main(String[] args) throws Exception {
        // 1.创建一个文件字节输入流管道与源文件接通
        InputStream is = new FileInputStream("day10_file_io_app\\src\\data2.txt");

        // 2.定义一个字节数组,用于读取字节数组中的数据
        byte[] buffer = new byte[3]; // 每次最多读取3B的数据
        int number = is.read(buffer);
        System.out.println("读取了几个字节:" + number); // 读取了几个字节:3
        // 解码 把字节转换为文字
        String rs = new String(buffer);
        System.out.println(rs); // abc

        int number2 = is.read(buffer);
        System.out.println("读取了几个字节:" + number2); // 读取了几个字节:3
        String rs2 = new String(buffer);
        System.out.println(rs2); // 我

        int number3 = is.read(buffer);
        System.out.println("读取了几个字节:" + number3); // 读取了几个字节:2
        // String rs3 = new String(buffer);
        // System.out.println(rs3); // 12�
        /*
            第28行后,此时字节数组buffer没有清空,其中存储的是代表汉字"我"的三个字节,再次进行读取时,仅能读取2个字节,因此将"12"代表的2个字节存入至buffer的前2位中
            第3位仍为代表汉字"我"的3个字节中的第3位,因此乱码
         */
        // 改进   读取多少数据,转换多少数据
        String rs3 = new String(buffer, 0, number3); // 指定开始位置与解码长度
        System.out.println(rs3); // 12

        // 如果字节已经没有可读的返回-1
        int number4 = is.read(buffer);
        System.out.println("读取了几个字节:" + number4); // 读取了几个字节:-1
    }

注:

        第28行后,此时字节数组buffer没有清空,其中存储的是代表汉字"我"的三个字节,再次进行读取时,仅能读取2个字节,因此将"12"代表的2个字节存入至buffer的前2位中,第3位仍为代表汉字"我"的3个字节中的第3位,因此乱码。

        每次读取一个字节数组会不可避免的存在一些问题。

        读取中文字符输出无法避免乱码问题(读取字节数组容易将代表每个中文字符的3个字节截断,在转换为文本时会出现乱码)。

如何使用字节输入流读取中文内容输出不乱码呢?

        定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。

        示例代码如下:

public static void main(String[] args) throws Exception {
        File f = new File("day10_file_io_app\\src\\data3.txt");
        InputStream is = new FileInputStream(f);
        // 获取文件大小并强转为int型
        int length = (int) f.length();
        byte[] buffer = new byte[length];
        int number = is.read(buffer);
        System.out.println("读取了多少字节:" + number); // 读取了多少字节:139
        System.out.println("文件大小:" + f.length() + "字节"); // 文件大小:139字节
        String rs = new String(buffer);
        System.out.println(rs);
        /*
            abc我12dsadasd撒大声地所多sdgg
            abc我12dsadasd撒大声地所多sdggabc我12dsadasd撒大声地所多s的dggsfSDAS是RQ示范法123
         */
    }

注:直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?

        如果文件过大,定义的字节数组可能引起内存溢出

6.3文件字节输出流:写字节数据到文件

文件字节输出流:FileOutputStream

        作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流。

FileOutputStream构造器

方法名

说明

public FileOutputStream (File file)

创建字节输出流管道与源文件对象接通

public FileOutputStream (File file,boolean append)

创建字节输出流管道与源文件对象接通,可追加数据

public FileOutputStream (String filepath)

创建字节输出流管道与源文件路径接通

public FileOutputStream (String filepath,boolean append)

创建字节输出流管道与源文件路径接通,可追加数据

FileOutputStream常用API

方法名

说明

public void write(int a)

写一个字节出去

public void write(byte[] buffer)

写一个字节数组出去

public void write(byte[] buffer , int pos , int len)

写一个字节数组的一部分出去

流的关闭与刷新

方法名

说明

flush()

刷新流,还可以继续写数据

close()

关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

        示例代码如下:

public class FileOutputStreamDemo4 {
    public static void main(String[] args) throws Exception {
        // 1.创建一个文件字节输出流管道与目标文件接通
//        OutputStream os = new FileOutputStream("day10_file_io_app\\output04.txt"); // 写路径,自动创建路径中的文件(会先清空之前的数据,再将新数据写入)
        OutputStream os = new FileOutputStream("day10_file_io_app\\output04.txt", true); // 追加数据,不会先清空之前的数据
//        public void write(int a)	写一个字节出去
        os.write('a');
        os.write(98);

//        public void write(byte[] buffer)	写一个字节数组出去
        byte[] buffer = {'a', 97, 98};
        os.write(buffer);
        os.write("\r\n".getBytes()); // 换行

        byte[] buffer2 = "我爱你中国".getBytes();
        os.write(buffer2);

//        public void write(byte[] buffer , int pos , int len)	写一个字节数组的一部分出去
        byte[] buffer3 = {'d', 58, 28, 39};
        os.write(buffer3, 0, 3); // 将buffer数组中第0位开始的前3位写入到文件中

        // 刷新流,写数据必须刷新数据
        os.flush();
        // 用完后一定要关闭流,释放资源(关闭流中包含刷新流)
        os.close();
    }
}

        注:os.write("\r\n".getBytes());是换行。

6.4文件拷贝

        需求:把某个视频复制到其他文件目录下。

思路:

        ① 根据数据源创建字节输入流对象。

        ② 根据目的地创建字节输出流对象。

        ③ 读写数据,复制视频。

        ④ 释放资源。

        示例代码如下:

public class FileCopyDemo05 {
    public static void main(String[] args) {
        try {
            // 1.创建一个字节输入流管道与原文件接通
            InputStream is = new FileInputStream("E:\\录屏\\学生作业相关知识学习.mp4");

            // 2.创建一个字节输出流管道与目标文件接通
            OutputStream os = new FileOutputStream("C:\\Users\\Administrator\\Desktop\\CopyMp4.mp4"); // 一定要加上目标文件名

            // 3.定义一个字节数组,用于文件复制
            byte[] buffer = new byte[1024]; // 每次读取1kb的数据
            // 不能用此种方式,每次循环is.read(buffer)输入流执行了两次,需要定义变量来记录读取长度
           /* while (is.read(buffer) != -1) {
                os.write(buffer, 0, is.read(buffer)); // 读多少倒多少
            }*/
            // 定义变量记录读取的字节数
            int len;
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len); // 读多少倒多少
            }
            System.out.println("复制结束");

            // 切记关闭流
            os.close(); // 一般从里向外关
            is.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7.资源释放的方式

try-catch-finally

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

        特点:被finally控制的语句最终一定会执行,除非JVM退出。

        异常处理标准格式:try….catch…finally

注:遇到return语句时,先执行finally代码块中的语句,再执行return语句。

try-catch-resource

        finally虽然可以用于释放资源,但是释放资源的代码过于繁琐,有没有办法简化?

        JDK 7中简化资源释放的改进如下图所示。

        注:JDK 7的()中只能放置资源对象,,否则报错,try代码块执行结束后自动调用close()方法,释放资源

什么是资源?

        资源都是实现了Closeable/AutoCloseable接口的类对象,如下图示例所示。

8.字符流的使用

8.1文件字符输入流—一次读取一个字符

文件字符输入流:FileReader

        作用:以内存为基准,把磁盘文件中的数据以字符的形式读取到内存中去。

Reader构造器

方法名

说明

public FileReader(File file)

创建字符输入流管道与源文件对象接通

public FileReader(String pathname)

创建字符输入流管道与源文件路径接通

Reader常用API

方法名

说明

public int read()

每次读取一个字符,返回字符对应的编码

如果字符已经没有可读的返回-1

public int read(char[] buffer)

每次读取一个字符数组,返回读取的字符个数

如果字符已经没有可读的返回-1

        示例代码如下:

 

    public static void main(String[] args) throws Exception {
        // 1.创建一个字符输入流管道与源文件接通
        Reader r = new FileReader("day10_file_io_app\\src\\data3.txt");

        // 2.每次读取一个字符返回,如果字符已经没有可读的返回-1
        int code = r.read();
        // 打印第一个字符对应的字符集(UTF-8)编码
        System.out.println(code); // 25105
        // 将字符集(UTF-8)编码转换为对应的字符
        System.out.println((char) code); // 我
    }

注:字符流的好处:读取中文字符不会出现乱码(代码文件编码一致的前提下

8.2文件字符输入流—一次读取一个字符数组

        示例代码如下:

 public static void main(String[] args) throws Exception {
        Reader r = new FileReader("day10_file_io_app\\src\\data3.txt");
        char[] buffer = new char[3]; // 每次读取三个字符
        int len; // 定义变量记录读取字符数
        while ((len = r.read(buffer)) != -1) {
            // 将数组内容以字符串形式拼接起来
            System.out.print(new String(buffer, 0, len)); // 读取多少拼接多少
        }
    }

        注:最终打印时使用public String(char value[])而不是public static String toString(char[] a),前者是将数组中的字符/字节拼接为字符串返回,后者是将数组内容转换为字符串返回(带[])。

8.3文件字符输出流

文件字符输出流:FileWriter

        作用:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件中去的流。

FileWriter构造器

方法名

说明

public FileWriter(File file)

创建字符输出流管道与源文件对象接通

public FileWriter (File file,boolean append)

创建字符输出流管道与源文件对象接通,

可追加数据

public FileWriter (String filepath)

创建字符输出流管道与源文件路径接通

public FileWriter (String filepath,

boolean append)

创建字符输出流管道与源文件路径接通,

可追加数据

FileWriter常用API

方法名

说明

void write(int c)

写一个字符

void write(char[] buffer)

写入一个字符数组

void write(char[] buffer, int pos, int len)

写入字符数组的一部分

void write(String str)

写一个字符串

void write(String str, int pos, int len)

写一个字符串的一部分

流的关闭与刷新

方法名

说明

flush()

刷新流,还可以继续写数据

close()

关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

        示例代码如下:

    public static void main(String[] args) {
        // 创建一个文件字符输出流管道与目标文件接通
        try (
//                Writer w = new FileWriter("day10_file_io_app\\src\\data_out.txt")
                Writer w = new FileWriter("day10_file_io_app\\src\\data_out.txt", true); // 不清空原文件数据,在其基础上添加数据
        ) {
            w.write("\r\n"); // 换行

            // 1.void write(int c)	写一个字符
            w.write(97);
            w.write('a');
            w.write('中');

            // 2.void write(char[] buffer)	写入一个字符数组
            char[] chars = "abc我爱你北京".toCharArray();
            w.write(chars);

            // 3.void write(char[] buffer, int pos, int len)	写入字符数组的一部分
            w.write(chars, 1, 5); // len代表字符数

            // 4.void write(String str)	写一个字符串
            w.write("123我爱你中国");

            // 5.void write(String str, int pos, int len)	写一个字符串的一部分
            w.write("123我爱你中国", 3, 5);

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

注:w.write("\r\n");是换行。

字节流、字符流如何选择使用?

        字节流适合做一切文件数据的拷贝(音视频,文本),不适合读取中文内容输出。

        字符流适合做文本文件的操作(读,写)。


写在最后:

        感谢读完!

        纵然缓慢,驰而不息!加油!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值