目录
File类概述
File类的常用API
判断文件类型、获取文件信息
创建文件、删除文件功能
遍历文件夹
方法递归
递归的形式和特点
递归的算法流程、核心要素
递归常见案例
递归的经典问题
非规律化递归案例-文件搜索
非规律化递归案例-啤酒问题
字符集
常见字符集介绍
字符集的编码、解码操作
IO流概述
字节流的使用
文件字节输入流:每次读取一个字节
文件字节输入流:每次读取一个字节数组
文件字节输入流:一次读完全部字节
文件字节输出流:写字节数据到文件
文件拷贝
资源释放的方式
try-catch-finally
try-with-resource
字符流的使用
文件字符输入流-一次读取一个字符
文件字符输入流-一次读取一个字符数组
文件字符输出流
缓冲流
缓冲流概述
字节缓冲流
字节缓冲流性能分析
字符缓冲流
转换流
问题引出:不同编码读取乱码问题
字符输入转换流
字符输出转换流
序列化对象
对象序列化
对象反序列化
打印流
PrintStream、PrintWriter
输出语句的重定向
补充知识:Properties
补充知识: IO框架

File类概述
 
File类概述
File类在包java.io.File下、代表操作系统的文件对象(文件、文件夹)。
File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能。 
File类创建对象
|   方法名称  |   说明  | 
|   public File(String pathname)  |   根据文件路径创建文件对象  | 
|   public File(String parent, String child)  |   从父路径名字符串和子路径名字符串创建文件对象  | 
|   public File(File parent, String child)  |   根据父路径对应文件对象和子路径名字符串创建文件对象  | 
File对象可以定位文件和文件夹
File封装的对象仅仅是一个路径名,这个路径可以是存在的,也可以是不存在的。
 
绝对路径和相对路径
    绝对路径:从盘符开始
        File file1 = new File(“D:\\Java\\a.txt”); 
    相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
        File file3 = new File(“模块名\\a.txt”);  
import java.io.File;
//目标:学会创建File对象定位操作系统的文件(文件、文件夹等)
public class FileDemo {
    public static void main(String[] args) {
        //1、创建File对象(指定了文件的路径)
        //路径的写法:D:\桌面壁纸\桌面.jpg
        //          D:/桌面壁纸/桌面.jpg
        //          File.separator
//        File f = new File("D:\\桌面壁纸\\桌面.jpg");
//        File f = new File("D:/桌面壁纸/桌面.jpg");
        File f = new File("D:" + File.separator + "桌面壁纸" + File.separator + "桌面.jpg");
        long size = f.length();//是文件的字节大小
        System.out.println(size);//950,915
        //2、File创建对象,支持绝对路径 支持相对路径(重点)
        File f1 = new File("D:\\桌面壁纸\\飞机.jpg");//绝对路径
        System.out.println(f1.length());//214075
        //相对路径:一般定位模块中的文件,相对到工程下
        File f2 = new File("hello-app/src/data");
        System.out.println(f2.length());//4
        //3、File创建对象可能是文件,也可以是文件夹
        File f3 = new File("D:\\桌面壁纸\\");
        System.out.println(f3.exists());//判断这个路径是否存在,文件夹是否存在
    }
}
 
总结
1、File类的作用?
创建对象定位文件,可以删除、获取文件信息等。但是不能读写文件内容。
2、File类构建对象的方式 ?
File file = new File(“文件/文件/绝对路径/相对路径”);
3、绝对路径和相对路径是什么样的?
绝对路径是带盘符的,依赖当前系统。
相对路径是不带盘符的,默认相对到工程下开始寻找文件。
 
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()  |   返回文件最后修改的时间毫秒值  | 
import java.io.File;
import java.text.SimpleDateFormat;
/**
 * 目标:File类的获取功能的API
 * - public String getAbsolutePath()  :返回此File的绝对路径名字符串。
 * - public String getPath()  : 获取创建文件对象的时候用的路径
 * - public String getName()  : 返回由此File表示的文件或目录的名称。
 * - public long length()  :    返回由此File表示的文件的长度。
 */
public class FileDemo {
    public static void main(String[] args) {
        //1、绝对路径创建一个文件对象
        File f1 = new File("D:\\桌面壁纸\\桌面.jpg");
        //a.获取它的绝对路径
        System.out.println(f1.getAbsoluteFile());//D:\桌面壁纸\桌面.jpg
        //b.获取文件定义的时候使用的路径
        System.out.println(f1.getPath());//D:\桌面壁纸\桌面.jpg
        //c.获取文件的名称,带后缀
        System.out.println(f1.getName());//桌面.jpg
        //d.获取文件的大小:字节个数
        System.out.println(f1.length());//950915
        //e.获取文件的最后修改时间
        long time = f1.lastModified();
        System.out.println("最后修改的时间:" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(time));//最后修改的时间:2021/06/03 21:00:53
        //f.判断文件是文件还是文件夹
        System.out.println(f1.isFile());//true
        System.out.println(f1.isDirectory());//false
        System.out.println("-----------相对路径-----------");
        //1、相对路径创建一个文件对象
        File f2 = new File("hello-app\\src\\data");
        //a.获取它的绝对路径
        System.out.println(f2.getAbsoluteFile());//D:\code\javasepro\hello-app\src\data
        //b.获取文件定义的时候使用的路径
        System.out.println(f2.getPath());//hello-app\src\data
        //c.获取文件的名称,带后缀
        System.out.println(f2.getName());//data
        //d.获取文件的大小:字节个数
        System.out.println(f2.length());//4
        //e.获取文件的最后修改时间
        long time1 = f2.lastModified();
        System.out.println("最后修改的时间:" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(time1));//最后修改的时间:2023/02/26 16:19:52
        //f.判断文件是文件还是文件夹
        System.out.println(f2.isFile());//true
        System.out.println(f2.isDirectory());//false
        File file = new File("D:/");
        System.out.println(file.isFile());//false
        System.out.println(file.isDirectory());//true
        System.out.println(file.exists());//判断文件夹是否存在
    }
} 
创建文件、删除文件功能
|   方法名称  |   说明  | 
|   public boolean createNewFile()  |   创建一个新的空的文件  | 
|   public boolean mkdir()  |   只能创建一级文件夹  | 
|   public boolean mkdirs()  |   可以创建多级文件夹  | 
|   方法名称  |   说明  | 
|   public boolean delete()  |   删除由此抽象路径名表示的文件或空文件夹  | 
import java.io.File;
import java.io.IOException;
/**
     目标:File类的创建和删除的方法
     - public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,
              创建一个新的空文件。 (几乎不用的,因为以后文件都是自动创建的!)
     - public boolean delete() :删除由此File表示的文件或目录。 (只能删除空目录)
     - public boolean mkdir() :创建由此File表示的目录。(只能创建一级目录)
     - public boolean mkdirs() :可以创建多级目录(建议使用的)
 */
public class FileDemo03 {
    public static void main(String[] args) throws Exception {
        File f = new File("file-io-app\\src\\data.txt");
        // a.创建新文件,创建成功返回true ,反之 ,不需要这个,以后文件写出去的时候都会自动创建
        System.out.println(f.createNewFile());
        File f1 = new File("file-io-app\\src\\data02.txt");
        System.out.println(f1.createNewFile()); // (几乎不用的,因为以后文件都是自动创建的!)
        // b.mkdir创建一级目录
        File f2 = new File("D:/resources/aaa");
        System.out.println(f2.mkdir());
        // c.mkdirs创建多级目录(重点)
        File f3 = new File("D:/resources/ccc/ddd/eee/ffff");
//        System.out.println(f3.mkdir());
        System.out.println(f3.mkdirs()); // 支持多级创建
        // d.删除文件或者空文件夹
        System.out.println(f1.delete());
        File f4 = new File("D:/resources/xueshan.jpeg");
        System.out.println(f4.delete()); // 占用一样可以删除
        // 只能删除空文件夹,不能删除非空文件夹.
        File f5 = new File("D:/resources/aaa");
        System.out.println(f5.delete());
    }
} 
delete方法默认只能删除文件和空文件夹。
delete方法直接删除不走回收站
 
总结
1、创建多级目录使用哪个方法?
    public boolean mkdirs()
2、删除文件需要注意什么?
    可以删除文件、空文件夹。
    默认不能删除非空文件夹。
 
遍历文件夹
|   方法名称  |   说明  | 
|   public String[] list()  |   获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。  | 
|   public File[] listFiles()(常用)  |   获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)  | 
import java.io.File;
import java.util.Arrays;
/**
 目标:File针对目录的遍历
 - public String[] list():
 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。
 - public File[] listFiles()(常用):
 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
 */
public class FileDemo {
    public static void main(String[] args) {
        // 1、定位一个目录
        File f1 = new File("D:\\桌面壁纸");
        String[] names = f1.list();
        for (String name : names) {
            System.out.println(name);
        }
        // 2.一级文件对象
        // 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
        File[] files = f1.listFiles();
        for (File f : files) {
            System.out.println(f.getAbsolutePath());
        }
        // 注意事项:当调用者不存在时返回null
        File dir = new File("D:/resources/ddd");
        File[] files1 = dir.listFiles();
        System.out.println(Arrays.toString(files1));
    }
} 
listFiles方法注意事项:
当调用者不存在时,返回null
当调用者是一个文件时,返回null
当调用者是一个空文件夹时,返回一个长度为0的数组
当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容
当调用者是一个需要权限才能进入的文件夹时,返回null
 
方法递归
递归的形式和特点
什么是方法递归?
    方法直接调用自己或者间接调用自己的形式称为方法递归( recursion)。
    递归做为一种算法在程序设计语言中广泛应用。
递归的形式
    直接递归:方法自己调用自己。
    间接递归:方法调用其他方法,其他方法又回调方法自己。
方法递归存在的问题?
    递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象。
什么是递归死循环?
    递归的方法无限调用自己,无法终止,出现栈内存溢出
 
/**
 递归的形式
 */
public class RecursionDemo {
    public static void main(String[] args) {
        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();
    }
}
 
递归的算法流程、核心要素
/**
 * 目标:递归的算法和执行流程
 */
public class RecursionDemo {
    public static void main(String[] args) {
        System.out.println(f(5));//120
    }
    public static int f(int n) {
        if (n == 1) {
            return 1;
        } else {
            return f(n - 1) * n;
        }
    }
} 
递归解决问题的思路:
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归算法三要素大体可以总结为:
递归的公式: f(n) =  f(n-1) * n;
递归的终结点:f(1) 
递归的方向必须走向终结点:
f(5) =  f(4) * 5
f(4) =  f(3) * 4
f(3) =  f(2) * 3
f(2) =  f(1) * 2
f(1) =  1
 
递归常见案例
需求:计算1-n的和的结果,使用递归思想解决,我们先从数学思维上理解递归的流程和核心点。
分析
假如我们认为存在一个公式是 f(n) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + …(n-1) + n;
那么公式等价形式就是: f(n) = f(n-1)  + n
递归的终结点:f(1) = 1
如果求的是 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
 
/**
 * 求1~n的和
 */
public class RecursionDemo {
    public static void main(String[] args) {
        System.out.println(f(5));//15
    }
    public static int f(int n) {
        if (n == 1) {
            return 1;
        } else {
            return f(n - 1) + n;
        }
    }
} 
递归的经典问题
猴子吃桃问题
    猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个
    第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个
    以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个
    等到第10天的时候发现桃子只有1个了。
需求:请问猴子第一天摘了多少个桃子?
分析:
    整体来看,每一天都是做同一个事件,典型的规律化问题,考虑递归三要素:
    递归公式: f(n)-f(n)/2-1=f(n+1)
    递归终结点:f(10) = 1
    递归方向:f(n) = 2f(n+1)+2 -> f(10) = 1
 
/**
 目标 猴子吃桃。
 公式(合理的): f(x) - f(x)/2 - 1 = f(x+1)
 2f(x) - f(x) - 2 = 2f(x + 1)
 f(x) = 2f(x + 1) + 2
 求f(1) = ?
 终结点: f(10) = 1
 递归的方向:合理的
 */
public class RecursionDemo {
    public static void main(String[] args) {
        System.out.println(f(1));//1534
    }
    public static int f(int n) {
        if (n == 10) {
            return 1;
        } else {
            return 2 * f(n + 1) + 2;
        }
    }
} 
非规律化递归案例-文件搜索
需求:文件搜索、从C:盘中,搜索出某个文件名称并输出绝对路径。
分析:
先定位出的应该是一级文件对象
遍历全部一级文件对象,判断是否是文件
如果是文件,判断是否是自己想要的
如果是文件夹,需要继续递归进去重复上述过程
 
import java.io.File;
public class RecursionDemo {
    public static void main(String[] args) {
        // 2、传入目录 和  文件名称
        searchFile(new File("D:/") , "测试.txt");
    }
    /**
     * 1、搜索某个目录下的全部文件,找到我们想要的文件。
     *
     * @param dir      被搜索的源目录
     * @param fileName 被搜索的文件名称
     */
    public static void searchFile(File dir, String fileName) {
        // 3、判断dir是否是目录
        if (dir != null && dir.isDirectory()) {
            // 可以找了
            // 4、提取当前目录下的一级文件对象
            File[] files = dir.listFiles(); // null  []
            // 5、判断是否存在一级文件对象,存在才可以遍历
            if (files != null && files.length > 0) {
                for (File file : files) {
                    // 6、判断当前遍历的一级文件对象是文件 还是 目录
                    if (file.isFile()) {
                        // 7、是不是咱们要找的,是把其路径输出即可
                        if (file.getName().contains(fileName)) {
                            System.out.println("找到了:" + file.getAbsolutePath());
                        }
                    } else {
                        // 8、是文件夹,需要继续递归寻找
                        searchFile(file, fileName);
                    }
                }
            }
        } else {
            System.out.println("对不起,当前搜索的位置不是文件夹!");
        }
    }
} 
非规律化递归案例-啤酒问题
需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,
请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。
答案:15瓶 3盖子 1瓶子
 
import java.util.Arrays;
/**
 * 目标:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,
 * 请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。
 * 答案:15瓶 3盖子 1瓶子
 */
public class RecursionDemo {
    // 定义一个静态的成员变量用于存储可以买的酒数量
    public static int totalNumber; // 总数量
    public static int lastBottleNumber; // 记录每次剩余的瓶子个数
    public static int lastCoverNumber; // 记录每次剩余的盖子个数
    public static void main(String[] args) {
        // 1、拿钱买酒
        buy(10);
        System.out.println("总数:" + totalNumber);
        System.out.println("剩余盖子数:" + lastCoverNumber);
        System.out.println("剩余瓶子数:" + lastBottleNumber);
    }
    public static void buy(int money) {
        // 2、看可以立马买多少瓶
        int buyNumber = money / 2; // 5
        totalNumber += buyNumber;
        // 3、把盖子 和瓶子换算成钱
        // 统计本轮总的盖子数  和 瓶子数
        int coverNumber = lastCoverNumber + buyNumber;
        int bottleNumber = lastBottleNumber + buyNumber;
        // 统计可以换算的钱
        int allMoney = 0;
        if (coverNumber >= 4) {
            allMoney += (coverNumber / 4) * 2;
        }
        lastCoverNumber = coverNumber % 4;
        if (bottleNumber >= 2) {
            allMoney += (bottleNumber / 2) * 2;
        }
        lastBottleNumber = bottleNumber % 2;
        if (allMoney >= 2) {
            buy(allMoney);
        }
    }
} 
删除文件夹[拓展]
import java.io.File;
/**
 目标:删除非空文件夹
 */
public class RecursionDemo {
    public static void main(String[] args) {
        deleteDir(new File("D:/new"));
    }
    /**
     删除文件夹,无所谓里面是否有内容,都可以删除
     * @param dir
     */
    public static void deleteDir(File dir){
        // 1、判断dir存在且是文件夹
        if(dir != null && dir.exists() && dir.isDirectory()){
            // 2、提取一级文件对象。
            File[] files = dir.listFiles();
            // 3、判断是否存在一级文件对象,存在则遍历全部的一级文件对象去删除
            if(files != null && files.length > 0){
                // 里面有内容
                for (File file : files) {
                    // 4、判断file是文件还是文件夹,文件直接删除
                    if(file.isFile()){
                        file.delete();
                    }else {
                        // 递归删除
                        deleteDir(file);
                    }
                }
            }
            // 删除自己
            dir.delete();
        }
    }
} 
字符集
常见字符集介绍
字符集基础知识:
    计算机底层不可以直接存储字符的。计算机中底层只能存储二进制(0、1)
    二进制是可以转换成十进制的
    结论:计算机底层可以表示十进制编号。计算机可以给人类字符进行编号存储,这套编号规则就是字符集。
ASCII字符集:
    ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。
     ASCII使用1个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。
GBK:
    window系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字。
注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。
Unicode码表:
    unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准。
    容纳世界上大多数国家的所有常见文字和符号。
    由于Unicode会先通过UTF-8,UTF-16,以及 UTF-32的编码成二进制后再存储到计算机,
    其中最为常见的就是UTF-8。
注意
    Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储。
    UTF-8也要兼容ASCII编码表。
    技术人员都应该使用UTF-8的字符集编码。
    编码前和编码后的字符集需要一致,否则会出现中文乱码。
 
汉字存储和展示过程解析

总结
1、字符串常见的字符底层组成是什么样的?
    英文和数字等在任何国家的字符集中都占1个字节
    GBK字符中一个中文字符占2个字节
    UTF-8编码中一个中文1般占3个字节
2、编码前的字符集和编码好的字符集有什么要求?
    必须一致,否则会出现中文字符乱码
    英文和数字在任何国家的编码中都不会乱码
 
字符集的编码、解码操作
|   方法名称  |   说明  | 
|   byte[] getBytes()  |   使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中  | 
|   byte[] getBytes(String charsetName)  |   使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中  | 
|   构造器  |   说明  | 
|   String(byte[] bytes)  |   通过使用平台的默认字符集解码指定的字节数组来构造新的 String  | 
|   String(byte[] bytes, String charsetName)  |   通过指定的字符集解码指定的字节数组来构造新的 String  | 
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class Test {
    public static void main(String[] args) throws Exception {
        //1、编码:把文字转换成字节(使用指定的编码)
        String name = "abc我爱你中国";
        //byte[] bytes = name.getBytes();//以当前代码默认字符集进行编码(UTF-8)
        byte[] bytes = name.getBytes("GBK");//以GBK字符集进行编码
        System.out.println(bytes.length);
        System.out.println(Arrays.toString(bytes));
        //2、解码:把字节转换成相对应的中文形式(编码前 和 编码后的字符集必须一致,否则乱码)
        //String rs = new String(bytes);//默认UTF-8解码
        String rs = new String(bytes,"GBK");//指定GBK解码
        System.out.println(rs);
    }
} 
IO流概述
IO流也称为输入、输出流,就是用来读写数据的。
IO流概述
    I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。
    O表示output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。 

IO流的分类

总结流的四大类:
字节输入流:
    以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。
字节输出流:
    以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
字符输入流:
    以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
字符输出流:
    以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流。
 
 

总结
1、IO流的作用?
    读写文件数据的
2、IO流是怎么划分的,大体分为几类,各自的作用?
    字节流:字节输入流,字节输出流(读写字节数数据的)
    字符流:字符输入流,字符输出流(读写字符数据的) 
字节流的使用
 文件字节输入流:每次读取一个字节
 
文件字节输入流:FileInputStream
作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去。 
|   构造器  |   说明  | 
|   public FileInputStream(File file)  |   创建字节输入流管道与源文件对象接通  | 
|   public FileInputStream(String pathname)  |   创建字节输入流管道与源文件路径接通  | 
|   方法名称  |   说明  | 
|   public int read()  |   每次读取一个字节返回,如果字节已经没有可读的返回-1  | 
|   public int read(byte[] buffer)  |   每次读取一个字节数组返回,如果字节已经没有可读的返回-1  | 
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
 目标:字节输入流的使用。
 IO流的体系:
 字节流                                   字符流
 字节输入流            字节输出流               字符输入流        字符输出流
 InputStream          OutputStream           Reader           Writer  (抽象类)
 FileInputStream      FileOutputStream       FileReader       FileWriter(实现类,可以使用的)
 文件字节输入流:FileInputStream
 -- 作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去。
 按照字节读文件数据到内存中。
 -- 构造器:
 public FileInputStream(File file):创建字节输入流管道与源文件对象接通
 public FileInputStream(String pathname):创建字节输入流管道与源文件路径接通。
 -- 方法:
 public int read(): 每次读取一个字节返回,读取完毕返回-1。
 小结:
 一个一个字节读取中文数据输出其实是被淘汰的,性能极差!
 一个一个字节读取中文数据输出,会出现截断中文字节的情况,无法避免读取中文输出乱码的问题。
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建一个文件字节输入流管道与源文件接通
        //InputStream is = new FileInputStream(new File("hello-app\\src\\data.txt"));
        //简化写法
        InputStream is = new FileInputStream("hello-app\\src\\data.txt");
        //2、读取一个字节返回(每次读取一滴水)
        /*int b1 = is.read();
        System.out.println((char) b1);//a
        int b2 = is.read();
        System.out.println((char)b2);//b
        int b3 = is.read();
        System.out.println((char)b3);//3
        int b4 = is.read();
        System.out.println(b4);//读取完毕返回-1*/
        //3、使用循环改进
        int b;
        while ((b = is.read()) != -1) {
            System.out.print((char) b);
        }
    }
} 
每次读取一个字节存在什么问题?
    性能较慢
    读取中文字符输出无法避免乱码问题。
 
文件字节输入流:每次读取一个字节数组

文件字节输入流:FileInputStream
作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去。 
|   方法名称  |   说明  | 
|   public int read()  |   每次读取一个字节返回,如果字节已经没有可读的返回-1  | 
|   public int read(byte[] buffer)  |   每次读取一个字节数组返回,如果字节已经没有可读的返回-1  | 
import java.io.FileInputStream;
import java.io.InputStream;
/**
 * data.txt的数据是:abc爱3abccd
 * 学会使用文件字节输入流每次读取一个字节数组的数据
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建一个文件字节输入流管道与源文件接通
        InputStream is = new FileInputStream("hello-app\\src\\data.txt");
        //2、定义一个字节数组,用于读取字节数组
        /*byte[] bytes = new byte[3];//3B
        int len = is.read(bytes);
        System.out.println("读取了"+len+"个字节");
        String rs = new String(bytes);//解码
        System.out.println(rs);*/
        /*byte[] bytes = new byte[3];//3B
        int len = is.read(bytes);
        System.out.println("读取了"+len+"个字节");
        String rs = new String(bytes,0,len);//读取多少倒多少
        System.out.println(rs);
        int len1 = is.read(bytes);
        System.out.println("读取了"+len1+"个字节");
        String rs1 = new String(bytes,0,len1);//读取多少倒多少
        System.out.println(rs1);
        int len2 = is.read(bytes);
        System.out.println("读取了"+len2+"个字节");
        String rs2 = new String(bytes,0,len2);//读取多少倒多少
        System.out.println(rs2);
         */
        //3、改进使用循环,每次读取一个字节数组
        /**
         * 当data.txt的数据是:abc爱3abccd时,因为每次最多能取三个字节,所以恰好没有乱码
         * 当data.txt的数据是ab爱3abccd时,则会出现乱码
         */
        byte[] buffer = new byte[3];
        int len;
        while ((len = is.read(buffer)) != -1) {
            String rs = new String(buffer, 0, len);
            System.out.print(rs);
        }
    }
} 
每次读取一个字节数组存在什么问题?
    读取的性能得到了提升
    读取中文字符输出无法避免乱码问题。
 
文件字节输入流:一次读完全部字节
1、如何使用字节输入流读取中文内容输出不乱码呢?
    定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。
2、直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
    如果文件过大,字节数组可能引起内存溢出。 
方式一
 自己定义一个字节数组与文件的大小一样大,然后使用读取字节数组的方法,一次性读取完成。
 
|   方法名称  |   说明  | 
|   public int read(byte[] buffer)  |   每次读取一个字节数组返回,如果字节已经没有可读的返回-1  | 
方式二
 官方为字节输入流InputStream提供了如下API可以直接把文件的全部数据读取到一个字节数组中
 
|   方法名称  |   说明  | 
|   public byte[] readAllBytes() throws IOException  |   直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回  | 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
/**
 * data.txt的数据是:
 * ab我c爱3a你bccd
 * ab我c爱3a你bccd
 * ab我c爱3a你bccd
 * ab我c爱3a你bccd
 * ab我c爱3a你bccdab我c爱3a你bccd
 * 使用文件字节输入流一次读完文件的全部字节,可以解决乱码问题
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建一个文件字节输入流管道与文件接通
        File f = new File("hello-app\\src\\data.txt");
        InputStream is = new FileInputStream(f);
        //方式一 :自己定义一个字节数组与文件的大小一样大,然后使用读取字节数组的方法,一次性读取完成。
        //2、定义一个字节数组与文件的大小一样,可以通过File类的length知道文件的大小
        /*byte[] buffer = new byte[(int) f.length()];
        int len = is.read(buffer);
        System.out.println("读取了" + len + "个字节");
        System.out.println(f.getName() + "文件大小:" + f.length());
        String rs = new String(buffer);
        System.out.println(rs);*/
        //方式二:官方为字节输入流InputStream提供的API可以直接把文件的全部数据读取到一个字节数组中
        byte[] buffer = is.readAllBytes();
        System.out.println(new String(buffer));
    }
} 
总结:
1、如何使用字节输入流读取中文内容输出不乱码呢?
    一次性读取完全部字节。
    可以定义与文件一样大的字节数组读取,也可以使用官方API.
2、直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
    如果文件过大,定义的字节数组可能引起内存溢出。
 
文件字节输出流:写字节数据到文件
  
文件字节输出流: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()  |   关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据  | 
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
/**
 * 目标:字节输出流的使用。
 * <p>
 * IO流的体系:
 * 字节流                                   字符流
 * 字节输入流           字节输出流               字符输入流       字符输出流
 * InputStream         OutputStream           Reader         Writer     (抽象类)
 * FileInputStream     FileOutputStream       FileReader     FileWriter (实现类)
 * <p>
 * a.FileOutputStream文件字节输出流。
 * -- 作用:以内存为基准,把内存中的数据,按照字节的形式写出到磁盘文件中去。
 * 简单来说,把内存数据按照字节写出到磁盘文件中去。
 * -- 构造器:
 * public FileOutputStream(File file):创建一个字节输出流管道通向目标文件对象。
 * public FileOutputStream(String file):创建一个字节输出流管道通向目标文件路径。
 * public FileOutputStream(File file , boolean append):创建一个追加数据的字节输出流管道通向目标文件对象。
 * public FileOutputStream(String file , boolean append):创建一个追加数据的字节输出流管道通向目标文件路径。
 * -- 方法:
 * public void write(int a):写一个字节出去 。
 * public void write(byte[] buffer):写一个字节数组出去。
 * public void write(byte[] buffer , int pos , int len):写一个字节数组的一部分出去。
 * 参数一,字节数组;参数二:起始字节索引位置,参数三:写多少个字节数出去。
 * 小结:
 * 记住。
 * 换行:  os.write("\r\n".getBytes()); // 换行
 * 追加数据管道: OutputStream os = new FileOutputStream("day10_demo/out01.txt" , true); // 追加管道!!
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建一个文件字节输出流管道与文件接通
        OutputStream os = new FileOutputStream("hello-app\\src\\out.txt");//先清空之前的数据,再写新数据进入
        //OutputStream os = new FileOutputStream("hello-app\\src\\out.txt",true);//不会清空管道之前存在的数据
        //2、写数据出去
        //a.public void write(int a):写一个字节出去
        os.write('a');
        os.write(98);
        os.write("\r\n".getBytes());//换行
        //os.write('我');//[ooo]写三个字节会乱码
        //b.public void write(byte[] buffer):写一个字节数组出去
        byte[] buffer = {'a', 97, 98, 99};
        os.write(buffer);
        os.write("\r\n".getBytes());//换行
        //byte[] buffer1 = "我是中国人".getBytes("GBK");//GBK编码形式
        byte[] buffer1 = "我是中国人".getBytes();//编码成字节数组
        os.write(buffer1);
        os.write("\r\n".getBytes());//换行
        //c.public void write(byte[] buffer , int pos , int len):写一个字节数组的一部分出去。
        byte[] buffer2 = {'a', 65, 66, 67};
        os.write(buffer2, 0, 3);
        os.write("\r\n".getBytes());//换行
        //os.flush();//写数据必须刷新数据,可以继续使用流
        os.close();//释放资源,包含了刷新数据!关闭后流不可以使用了
    }
} 
文件拷贝

案例:文件拷贝
需求:
    把某个视频复制到其他目录下
思路:
    根据数据源创建字节输入流对象
    根据目的地创建字节输出流对象
    读写数据,复制视频
    释放资源 
import java.io.*;
//学会使用字节流完成文件的复制(支持一切格式)
public class Test {
    public static void main(String[] args) {
        //1、创建一个字节流输入管道与文件接通
        try {
            InputStream is = new FileInputStream("D:\\Java学习\\07、文件拷贝.mp4");
            //2、创建一个字节流输出管道与文件接通
            OutputStream os = new FileOutputStream("D:\\Java学习\\new.mp4");
            //3、定义一个字节数据转移数据
            byte[] buffer = new byte[1024];
            int len;//记录每次读取的字节数
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            System.out.println("复制完成!");
            //4、关闭流
            os.close();
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 
 

字节流适合做一切文件数据的拷贝吗?
    任何文件的底层都是字节,拷贝是一字不漏的转移字节,只要前后文件格式、编码一致没有任何问题。
 
资源释放的方式
try-catch-finally
try-catch-finally
    finally:在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源
    特点:被finally控制的语句最终一定会执行,除非JVM退出
    异常处理标准格式:try….catch…finally
try-catch-finally的作用
    finally代码块是最终一定要执行的,可以在代码执行完毕的最后用于释放资源。
 

import java.io.*;
//学会使用字节流完成文件的复制(支持一切格式)
public class Test {
    public static void main(String[] args) {
        InputStream is = null;
        OutputStream os = null;
        //System.out.println(10/0);
        try {
            //1、创建一个字节流输入管道与文件接通
            File f = new File("D:\\Java学习\\07、文件拷贝.mp4");
            is = new FileInputStream(f);
            //2、创建一个字节流输出管道与文件接通
            os = new FileOutputStream("D:\\Java学习\\new.mp4");
            //3、定义一个字节数据转移数据
            byte[] buffer = new byte[1024];
            int len;//记录每次读取的字节数
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            /*byte[] buffer1 = new byte[(int) f.length()];
            is.read(buffer1);
            os.write(buffer1);*/
           /* byte[] buffer = is.readAllBytes();
            os.write(buffer);*/
            System.out.println("复制完成!");
            //System.out.println(10 / 0);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //无论代码是正常结束,还是出现异常都要最后执行这里!
            System.out.println("=========finally=========");
            try {
                //4、关闭流 注意关闭时是否会出现空值异常!在还没创建管道时,是否前面已经出现异常导致管道还未创建
                if (os != null) os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (is != null) is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
} 
注意return的使用
public static int test(int a , int b){
        try {
            int c = a / b;
            return c;
        }catch (Exception e){
            e.printStackTrace();
            return -111111; // 计算出现bug.
        }finally {
            System.out.println("--finally--");
            // 哪怕上面有return语句执行,也必须先执行完这里才可以!
            // 开发中不建议在这里加return ,如果加了,返回的永远是这里的数据了,这样会出问题!
            return 100;
        }
    } 
try-with-resource
1. finally虽然可以用于释放资源,但是释放资源的代码过于繁琐?
2. 有没有办法简化?
InputStream is = null ;
OutputStream os = null;
try{
    ...
}catch (Exception e){
    e.printStackTrace();
} finally {
    // 关闭资源!
    try {
        if(os != null) os.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    try {
        if(is != null) is.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
 
JDK 7和JDK9中都简化了资源释放操作

注意:
JDK 7 以及 JDK 9的()中只能放置资源对象,否则报错
什么是资源呢?
资源都是实现了Closeable/AutoCloseable接口的类对象
public abstract class InputStream implements Closeable {}
public abstract class OutputStream implements Closeable, Flushable{} 
 
import java.io.*;
//学会使用字节流完成文件的复制(支持一切格式)
public class Test {
    public static void main(String[] args) {
        //System.out.println(10/0);
        try (
                // 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
                //1、创建一个字节流输入管道与文件接通
                InputStream is = new FileInputStream("D:\\Java学习\\07、文件拷贝.mp4");
                //2、创建一个字节流输出管道与文件接通
                OutputStream os = new FileOutputStream("D:\\Java学习\\new.mp4");
                //int age = 21;//这里只能释放资源
                MyConnection connection = new MyConnection();//最终会自动调用出资源的close方法
        ) {
            //3、定义一个字节数据转移数据
            byte[] buffer = new byte[1024];
            int len;//记录每次读取的字节数
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            /*byte[] buffer1 = new byte[(int) f.length()];
            is.read(buffer1);
            os.write(buffer1);*/
           /* byte[] buffer = is.readAllBytes();
            os.write(buffer);*/
            System.out.println("复制完成!");
            //System.out.println(10 / 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class MyConnection implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("连接资源被成功释放了!");
    }
} 
package com.itheima.d5_resource;
import java.io.*;
/**
 *   目标:JDK 9释放资源的方式:可以了解下。
 */
public class TryCatchResouceDemo3 {
    public static void main(String[] args) throws Exception {
        // 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
        // 1、创建一个字节输入流管道与原视频接通
        InputStream is = new FileInputStream("file-io-app/src/out04.txt");
        // 2、创建一个字节输出流管道与目标文件接通
        OutputStream os = new FileOutputStream("file-io-app/src/out05.txt");
        try ( is ; os ) {
            // 3、定义一个字节数组转移数据
            byte[] buffer = new byte[1024];
            int len; // 记录每次读取的字节数。
            while ((len = is.read(buffer)) != -1){
                os.write(buffer, 0 , len);
            }
            System.out.println("复制完成了!");
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
 
字符流的使用
文件字符输入流-一次读取一个字符
字节流读取中文输出会存在什么问题?
    会乱码。或者内存溢出。
读取中文输出,哪个流更合适,为什么?
    字符流更合适,最小单位是按照单个字符读取的。
字符流的好处。每次读取一个字符存在什么问题?
    读取中文字符不会出现乱码(如果代码文件编码一致)
    性能较慢 

文件字符输入流:Reader
作用:以内存为基准,把磁盘文件中的数据以字符的形式读取到内存中去。
 
|   构造器  |   说明  | 
|   public FileReader(File file)  |   创建字符输入流管道与源文件对象接通  | 
|   public FileReader(String pathname)  |   创建字符输入流管道与源文件路径接通  | 
|   方法名称  |   说明  | 
|   public int read()  |   每次读取一个字符返回,如果字符已经没有可读的返回-1  | 
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
/**
 * 目标:字符输入流的使用。
 * <p>
 * IO流的体系:
 * 字节流                                   字符流
 * 字节输入流           字节输出流               字符输入流       字符输出流
 * InputStream         OutputStream            Reader         Writer     (抽象类)
 * FileInputStream     FileOutputStream        FileReader     FileWriter (实现类)
 * <p>
 * c.FileReader:文件字符输入流。
 * -- 作用:以内存为基准,把磁盘文件的数据以字符的形式读入到内存。
 * 简单来说,读取文本文件内容到内存中去。
 * <p>
 * -- 构造器:
 * public FileReader(File file):创建一个字符输入流与源文件对象接通。
 * public FileReader(String filePath):创建一个字符输入流与源文件路径接通。
 * <p>
 * -- 方法:
 * public int read(): 读取一个字符的编号返回! 读取完毕返回-1
 * public int read(char[] buffer):读取一个字符数组,读取多少个字符就返回多少个数量,读取完毕返回-1
 * 小结:
 * 字符流一个一个字符的读取文本内容输出,可以解决中文读取输出乱码的问题。
 * 字符流很适合操作文本文件内容。
 * 但是:一个一个字符的读取文本内容性能较差!!
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //目标:每次读取一个字符
        //1、创建一个字符输入管道与文件接通
        Reader fr = new FileReader("hello-app\\src\\data.txt");
        //2、读取一个字符返回,没有可读字符时返回-1
//        int code = fr.read();
//        System.out.print((char) code);
//
//        int code1 = fr.read();
//        System.out.print((char) code1);
        //3、使用循环读取字符
        int code;
        while ((code = fr.read()) != -1) {
            System.out.print((char) code);
        }
    }
} 
文件字符输入流-一次读取一个字符数组
文件字符输入流:FileReader
作用:以内存为基准,把磁盘文件中的数据以字符的形式读取到内存中去。
每次读取一个字符数组的优势?
    读取的性能得到了提升
    读取中文字符输出不会乱码。
 
|   方法名称  |   说明  | 
|   public int read()  |   每次读取一个字符返回,如果字符已经没有可读的返回-1  | 
|   public int read(char[] buffer)  |   每次读取一个字符数组,返回读取的字符数,如果字符已经没有可读的返回-1  | 
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
/**
 * 目标:字符输入流的使用-按照字符数组读取。
 * <p>
 * IO流的体系:
 * 字节流                                       字符流
 * 字节输入流           字节输出流               字符输入流       字符输出流
 * InputStream         OutputStream           Reader         Writer     (抽象类)
 * FileInputStream     FileOutputStream       FileReader     FileWriter (实现类)
 * <p>
 * c.FileReader:文件字符输入流。
 * -- 作用:以内存为基准,把磁盘文件的数据以字符的形式读入到内存。
 * 简单来说,读取文本文件内容到内存中去。
 * -- 构造器:
 * public FileReader(File file):创建一个字符输入流与源文件对象接通。
 * public FileReader(String filePath):创建一个字符输入流与源文件路径接通。
 * -- 方法:
 * public int read(): 读取一个字符的编号返回! 读取完毕返回-1
 * public int read(char[] buffer):读取一个字符数组,
 * 读取多少个字符就返回多少个数量,读取完毕返回-1
 * 小结:
 * 字符流按照字符数组循环读取数据,可以解决中文读取输出乱码的问题,而且性能也较好!!
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建一个字符输入管道与文件接通
        Reader fr = new FileReader("hello-app\\src\\data.txt");
        //2、用循环,每次读取一个字符数组的数据
        char[] buffer = new char[1024];
        int len;
        while ((len = fr.read(buffer)) != -1) {
            String rs = new String(buffer, 0, len);
            System.out.print(rs);
        }
    }
} 
文件字符输出流


文件字符输出流: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[] cbuf)  |   写入一个字符数组  | 
|   void write(char[] cbuf, int off, int len)  |   写入字符数组的一部分  | 
|   void write(String str)  |   写一个字符串  | 
|   void write(String str, int off, int len)  |   写一个字符串的一部分  | 
|   void write(int c)  |   写一个字符  | 
流的关闭与刷新
|   方法  |   说明  | 
|   flush()  |   刷新流,还可以继续写数据  | 
|   close()  |   关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据  | 
import java.io.FileWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
/**
 * 目标:字符输出流的使用。
 * <p>
 * IO流的体系:
 * 字节流                                   字符流
 * 字节输入流           字节输出流               字符输入流       字符输出流
 * InputStream         OutputStream           Reader         Writer     (抽象类)
 * FileInputStream     FileOutputStream       FileReader     FileWriter (实现类)
 * <p>
 * d.FileWriter文件字符输出流的使用。
 * -- 作用:以内存为基准,把内存中的数据按照字符的形式写出到磁盘文件中去。
 * 简单来说,就是把内存的数据以字符写出到文件中去。
 * -- 构造器:
 * public FileWriter(File file):创建一个字符输出流管道通向目标文件对象。
 * public FileWriter(String filePath):创建一个字符输出流管道通向目标文件路径。
 * public FileWriter(File file,boolean append):创建一个追加数据的字符输出流管道通向目标文件对象。
 * public FileWriter(String filePath,boolean append):创建一个追加数据的字符输出流管道通向目标文件路径。
 * -- 方法:
 * a.public void write(int c):写一个字符出去
 * b.public void write(String c)写一个字符串出去:
 * c.public void write(char[] buffer):写一个字符数组出去
 * d.public void write(String c ,int pos ,int len):写字符串的一部分出去
 * e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
 * 小结:
 * 字符输出流可以写字符数据出去,总共有5个方法写字符。
 * 覆盖管道:
 * Writer fw = new FileWriter("Day10Demo/src/dlei03.txt"); // 覆盖数据管道
 * 追加数据管道:
 * Writer fw = new FileWriter("Day10Demo/src/dlei03.txt",true); // 追加数据管道
 * 换行:
 * fw.write("\r\n"); // 换行
 * 结论:读写字符文件数据建议使用字符流。复制文件建议使用字节流。
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建一个字符输出流管道与目标文件接通
        //Writer fileWriter = new FileWriter("hello-app\\src\\write.txt");//覆盖管道,每次启动都会清空文件之前的数据
        Writer fileWriter = new FileWriter("hello-app\\src\\write.txt", true);//不会覆盖管道
        //a.public void write(int c):写一个字符出去
        fileWriter.write(98);
        fileWriter.write('a');
        fileWriter.write('我');//不会出现乱码
        fileWriter.write("\r\n".toCharArray());//换行
        //b.public void write(String c)写一个字符串出去:
        fileWriter.write("我爱你中国~~");
        fileWriter.write("\r\n".toCharArray());//换行
        //c.public void write(char[] buffer):写一个字符数组出去
        char[] code = {'a', 65, 66, 67, 98, '我'};
        char[] code1 = "我是中国人!".toCharArray();
        fileWriter.write(code);
        fileWriter.write("\r\n".toCharArray());//换行
        fileWriter.write(code1);
        fileWriter.write("\r\n".toCharArray());//换行
        //d.public void write(String c ,int pos ,int len):写字符串的一部分出去
        fileWriter.write("write写入字符".toCharArray(), 0, 7);
        fileWriter.write("\r\n".toCharArray());//换行
        //e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
        //第一个参数:字符数组  第二个参数:开始提取字符的位置  第三个参数:提取个数
        fileWriter.write(code1, 2, 3);
        fileWriter.write("\r\n".toCharArray());//换行
        fileWriter.close();//关闭包含刷新,关闭后流不能使用
    }
} 
字节流、字符流如何选择使用?
    字节流适合做一切文件数据的拷贝(音视频,文本)
    字节流不适合读取中文内容输出
    字符流适合做文本文件的操作(读,写)
 
缓冲流

缓冲流概述
缓冲流概述
缓冲流也称为高效流、或者高级流。之前学习的字节流可以称为原始流。
作用:缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能 

 
 
1、缓冲流的作用?
缓冲流自带缓冲区、可以提高原始字节流、字符流读写数据的性能
缓冲流有几种?
2、字节缓冲流
    字节缓冲输入流: BufferedInputStream
    字节缓冲输出流:BufferedOutputStream
3、字符缓冲流
    字符缓冲输入流:BufferedReader
    字符缓冲输出流:BufferedWriter
 
字节缓冲流
字节缓冲流性能优化原理:
    字节缓冲输入流自带了8KB缓冲池,以后我们直接从缓冲池读取数据,所以性能较好。
    字节缓冲输出流自带了8KB缓冲池,数据就直接写入到缓冲池中去,写数据性能极高了。
字节缓冲流
    字节缓冲输入流:BufferedInputStream,提高字节输入流读取数据的性能,读写功能上并无变化。
    字节缓冲输出流:BufferedOutputStream,提高字节输出流读取数据的性能,读写功能上并无变化。 
|   构造器  |   说明  | 
|   public BufferedInputStream(InputStream is)  |   可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能  | 
|   public BufferedOutputStream(OutputStream os)  |   可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能  | 
1、字节缓冲流为什么提高了操作数据的性能?
    字节缓冲流自带8KB缓冲区
    可以提高原始字节流、字符流读写数据的性能
2、字节缓冲流的功能如何调用?
    public BufferedOutputStream(OutputStream os)
    public BufferedInputStream(InputStream is)
    功能上并无很大变化,性能提升了。
 
import java.io.*;
/**
 * 目标:使用字节缓冲流完成数据的读写操作。
 */
public class Test {
    public static void main(String[] args) {
        try (
                //这里面只能放置资源对象,用完会自动关闭,自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
                //1、创建一个字节输入流管道与文件接通
                InputStream is = new FileInputStream("hello-app\\src\\out.txt");
                //a.把原始字节输入流包装成高级的缓冲字节输入流
                InputStream bis = new BufferedInputStream(is);
                //2、创建一个字节输出流管道与目标文件接通
                OutputStream os = new FileOutputStream("hello-app\\src\\out1.txt");
                //b.把字节输出流包装成高级的缓冲字节输出流
                OutputStream bos = new BufferedOutputStream(os);
        ) {
            //3、定义一个字节数组转移数据
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
            System.out.println("复制完成!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 
字节缓冲流性能分析
分别使用不同的方式复制大视频观察性能情况
需求
    分别使用低级字节流和高级字节缓冲流拷贝大视频,记录耗时。
分析
    使用低级的字节流按照一个一个字节的形式复制文件。
    使用低级的字节流按照一个一个字节数组的形式复制文件。
    使用高级的缓冲字节流按照一个一个字节的形式复制文件。
    使用高级的缓冲字节流按照一个一个字节数组的形式复制文件。
 
import java.io.*;
/**
 * 目标:利用字节流的复制统计各种写法形式下缓冲流的性能执行情况。
 * <p>
 * 复制流:
 * (1)使用低级的字节流按照一个一个字节的形式复制文件。
 * (2)使用低级的字节流按照一个一个字节数组的形式复制文件。
 * (3)使用高级的缓冲字节流按照一个一个字节的形式复制文件。
 * (4)使用高级的缓冲字节流按照一个一个字节数组的形式复制文件。
 * <p>
 * 源文件:C:\course\3-视频\18、IO流-文件字节输出流FileOutputStream写字节数据出去.avi
 * 目标文件:C:\course\
 * <p>
 * 小结:
 * 使用高级的缓冲字节流按照一个一个字节数组的形式复制文件,性能好,建议开发使用!
 */
public class Test {
    private static final String SRC_FILE = "D:\\Java学习\\视频拷贝.mp4";
    private static final String DEST_FILE = "D:\\Java学习\\";
    public static void main(String[] args) {
        //copy01();//使用低级的字节流按照一个一个字节的形式复制文件:慢的让人难以忍受,直接淘汰
        copy02();//使用低级的字节流按照一个一个字节数组的形式复制文件:比较慢,但是还是可以忍受!
        copy03();//使用高级的缓冲字节流按照一个一个字节的形式复制文件
        copy04();//使用高级的缓冲字节流按照一个一个字节数组的形式复制文件
    }
    /**
     * 使用高级的缓冲字节流按照一个一个字节数组的形式复制文件
     */
    private static void copy04() {
        long startTime = System.currentTimeMillis();//开始时间
        try (
                //1、创建低级的字节输入流与源文件接通
                InputStream is = new FileInputStream(SRC_FILE);
                //a.把原始字节输入流包装成高级的缓冲字节输入流
                InputStream bis = new BufferedInputStream(is);
                //2、创建低级的字节输出流域目标文件接通
                OutputStream os = new FileOutputStream(DEST_FILE + "new4.mp4");
                //b.把字节输出流包装成高级的缓冲字节输出流
                OutputStream bos = new BufferedOutputStream(os);
        ) {
            // 3、定义一个字节数组转移数据
            byte[] buffer = new byte[1024];
            int len;//记录每次读取的字节数
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("使用缓冲流按照一个一个字节数组的形式复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
    }
    /**
     * 使用高级的缓冲字节流按照一个一个字节的形式复制文件
     */
    private static void copy03() {
        long startTime = System.currentTimeMillis();//开始时间
        try (
                //1、创建低级的字节输入流与源文件接通
                InputStream is = new FileInputStream(SRC_FILE);
                //a.把原始字节输入流包装成高级的缓冲字节输入流
                InputStream bis = new BufferedInputStream(is);
                //2、创建低级的字节输出流域目标文件接通
                OutputStream os = new FileOutputStream(DEST_FILE + "new3.mp4");
                //b.把字节输出流包装成高级的缓冲字节输出流
                OutputStream bos = new BufferedOutputStream(os);
        ) {
            //3、定义一个变量记录每次读取的字节(一个一个字节的复制)
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("使用缓冲流按照一个一个字节的形式复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
    }
    /**
     * 使用低级的字节流按照一个一个字节数组的形式复制文件
     */
    private static void copy02() {
        long startTime = System.currentTimeMillis();//开始时间
        try (
                //1、创建低级的字节输入流与源文件接通
                InputStream is = new FileInputStream(SRC_FILE);
                //2、创建低级的字节输出流域目标文件接通
                OutputStream os = new FileOutputStream(DEST_FILE + "new2.mp4");
        ) {
            // 3、定义一个字节数组转移数据
            byte[] buffer = new byte[1024];
            int len;//记录每次读取的字节数
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("使用低级的字节流按照一个一个字节数组的形式复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
    }
    /**
     * 使用低级的字节流按照一个一个字节的形式复制文件
     */
    private static void copy01() {
        long startTime = System.currentTimeMillis();//开始时间
        try (
                //1、创建低级的字节输入流与源文件接通
                InputStream is = new FileInputStream(SRC_FILE);
                //2、创建低级的字节输出流域目标文件接通
                OutputStream os = new FileOutputStream(DEST_FILE + "new1.mp4");
        ) {
            //3、定义一个变量记录每次读取的字节(一个一个字节的复制)
            int b;
            while ((b = is.read()) != -1) {
                os.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("使用低级的字节流按照一个一个字节的形式复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
    }
} 
推荐使用哪种方式提高字节流读写数据的性能?
建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。
 
字符缓冲流
字符缓冲输入流
字符缓冲输入流:BufferedReader。
作用:提高字符输入流读取数据的性能,除此之外多了按照行读取数据的功能。 
|   构造器  |   说明  | 
|   public BufferedReader(Reader r)  |   可以把低级的字符输入流包装成一个高级的缓冲字符输入流管道,从而提高字符输入流读数据的性能  | 
字符缓冲输入流新增功能
|   方法  |   说明  | 
|   public String readLine()  |   读取一行数据返回,如果读取没有完毕,无行可读返回null  | 
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
/**
 * 目标:学会使用缓冲字符输入流提高字符输入流的性能,新增了按照行读取的方法(经典代码)
 */
public class Test {
    public static void main(String[] args) {
        try (
                //1、创建一个文件字符输入流与源文件接通
                Reader fr = new FileReader("hello-app\\src\\data.txt");
                //把低级的字符输入流包装成高级的缓冲字符输入流
                BufferedReader br = new BufferedReader(fr);
        ) {
            //一行一行的读取数据
//            System.out.println(br.readLine());
//            System.out.println(br.readLine());
//            System.out.println(br.readLine());
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 
字符缓冲输出流
    字符缓冲输出流:BufferedWriter。
    作用:提高字符输出流写取数据的性能,除此之外多了换行功能 
|   构造器  |   说明  | 
|   public BufferedWriter(Writer w)  |   可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能  | 
字符缓冲输出流新增功能
|   方法  |   说明  | 
|   public void newLine()  |   换行操作  | 
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建一个字符输出流管道与目标文件接通
        //Writer fileWriter = new FileWriter("hello-app\\src\\write1.txt");//覆盖管道,每次启动都会清空文件之前的数据
        Writer fileWriter = new FileWriter("hello-app\\src\\write1.txt", true);//不会覆盖管道
        //把低级的字符输出流包装成高级缓冲输出流
        BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
        //a.public void write(int c):写一个字符出去
        bufferedWriter.write(98);
        bufferedWriter.write('a');
        bufferedWriter.write('我');//不会出现乱码
        bufferedWriter.newLine();//bufferedWriter.write("\r\n".toCharArray());//换行
        //b.public void write(String c)写一个字符串出去:
        bufferedWriter.write("我爱你中国~~");
        bufferedWriter.newLine();
        //c.public void write(char[] buffer):写一个字符数组出去
        char[] code = {'a', 65, 66, 67, 98, '我'};
        char[] code1 = "我是中国人!".toCharArray();
        bufferedWriter.write(code);
        bufferedWriter.newLine();
        bufferedWriter.write(code1);
        bufferedWriter.newLine();
        //d.public void write(String c ,int pos ,int len):写字符串的一部分出去
        bufferedWriter.write("write写入字符".toCharArray(), 0, 7);
        bufferedWriter.newLine();
        //e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
        //第一个参数:字符数组  第二个参数:开始提取字符的位置  第三个参数:提取个数
        bufferedWriter.write(code1, 2, 3);
        bufferedWriter.newLine();
        bufferedWriter.close();//关闭包含刷新,关闭后流不能使用
    }
} 
总结
1、字符缓冲流为什么提高了操作数据的性能?
    字符缓冲流自带8K缓冲区
    可以提高原始字符流读写数据的性能
2、字符缓冲流的功能如何使用?
    public BufferedReader(Reader r)
    性能提升了,多了readLine()按照行读取的功能
    public BufferedWriter(Writer w)
    性能提升了,多了newLine()换行的功能
 
案例:拷贝出师表到另一个文件,恢复顺序
需求:把《出师表》的文章顺序进行恢复到一个新文件中。
分析:
    定义一个缓存字符输入流管道与源文件接通。
    定义一个List集合存储读取的每行数据。
    定义一个循环按照行读取数据,存入到List集合中去。
    对List集合中的每行数据按照首字符编号升序排序。
    定义一个缓存字符输出管道与目标文件接通。
    遍历List集合中的每个元素,用缓冲输出管道写出并换行。
 

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
 * 目标:完成出师表顺序的恢复,并存入到另一个新文件中去。
 */
public class Test {
    public static void main(String[] args) {
        try (
                // 1、创建缓冲字符输入流管道与源文件接通
                BufferedReader br = new BufferedReader(new FileReader("hello-app\\src\\csb.txt"));
                // 5、定义缓冲字符输出管道与目标文件接通
                BufferedWriter bw = new BufferedWriter(new FileWriter("hello-app\\src\\new-csb.txt"));
        ) {
            // 2、定义一个List集合存储每行内容
            List<String> data = new ArrayList<>();
            // 3、定义循环,按照行读取文章
            String line;
            while ((line = br.readLine()) != null) {
                data.add(line);//添加进集合
            }
            System.out.println(data);
            // 4、排序
            // 自定义排序规则
            List<String> sizes = new ArrayList<>();
            Collections.addAll(sizes, "一", "二", "三", "四", "五", "陆", "柒", "八", "九", "十", "十一");
            Collections.sort(data, new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    //return sizes.indexof("一") - sizes.indexof("柒");//indexof():取某个元素的索引位置
                    return sizes.indexOf(o1.substring(0, o1.indexOf(".")))
                            - sizes.indexOf(o2.substring(0, o2.indexOf(".")));
                }
            });
            System.out.println(data);
            // 6、遍历集合中的每行文章写出去,且要换行
            for (String datum : data) {
                bw.write(datum);//写入
                bw.newLine();//换行
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 
转换流
问题引出:不同编码读取乱码问题
1、之前我们使用字符流读取中文是否有乱码?
    没有的,因为代码编码和文件编码都是UTF-8。
2、如果代码编码和文件编码不一致,使用字符流直接读取还能不乱码吗?
    会乱码。
    文件编码和读取的编码必须一致才不会乱码。
 
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
/**
 * 演示一下代码编码与文件编码相同和不同的情况
 */
public class Test {
    public static void main(String[] args) {
        try (
                // 代码:UTF-8 文件 UTF-8 不会乱码
                // 1、创建一个文件字符输入流与源文件接通。
                // Reader fr = new FileReader("D:\Java学习\data.txt");
                
                // 代码:UTF-8 文件 GBK  乱码.     abc 我   爱 你中国
                //                                  oo  oo
                Reader fr = new FileReader("D:\\Java学习\\data.txt");
                // a、把低级的字符输入流包装成高级的缓冲字符输入流。
                BufferedReader br = new BufferedReader(fr);
        ) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
} 
字符输入转换流
1、如果代码编码和文件编码不一致,使用字符流直接读取还能不乱码吗?
    会乱码。
2、如果如何解决呢?
    使用字符输入转换流
    可以提取文件(GBK)的原始字节流,原始字节不会存在问题。
    然后把字节流以指定编码转换成字符输入流,这样字符输入流中的字符就不乱码了 
字符输入转换流
 字符输入转换流:InputStreamReader,可以把原始的字节流按照指定编码转换成字符输入流。 
|   构造器  |   说明  | 
|   public InputStreamReader(InputStream is)  |   可以把原始的字节流按照代码默认编码转换成字符输入流。几乎不用,与默认的FileReader一样。  | 
|   public InputStreamReader(InputStream is ,String charset)  |   可以把原始的字节流按照指定编码转换成字符输入流,这样字符流中的字符就不乱码了(重点)  | 
import java.io.*;
/**
 * 目标:字符输入转换流InputStreamReader的使用。
 * <p>
 * 字节流                                     字符流
 * 字节输入流               字节输出流              字符输入流            字符输出流
 * InputStream             OutputStream          Reader               Writer   (抽象类)
 * FileInputStream         FileOutputStream      FileReader           FileWriter(实现类)
 * BufferedInputStream     BufferedOutputStream  BufferedReader       BufferedWriter(实现类,缓冲流)
 * InputStreamReader    OutputStreamWriter
 * 字符输入转换流InputStreamReader:
 * -- 作用:可以解决字符流读取不同编码乱码的问题。
 * 也可以把原始的字节流按照指定编码转换成字符输入流
 * <p>
 * -- 构造器:
 * public InputStreamReader(InputStream is):可以使用当前代码默认编码转换成字符流,几乎不用!
 * public InputStreamReader(InputStream is,String charset):可以指定编码把字节流转换成字符流(核心)
 * <p>
 * 小结:
 * 字符输入转换流InputStreamReader:作用:可以解决字符流读取不同编码乱码的问题。
 * public InputStreamReader(InputStream is,String charset):可以指定编码把字节流转换成字符流(核心)
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //1、创建原始字节输入流管道与文件接通
        InputStream is = new FileInputStream("D:\\Java学习\\data.txt");
        //2、把原始字节流转换成字符输入流
        //Reader isr = new InputStreamReader(is); 默认以UTF-8的方式转换成字符流。 还是会乱码的  跟直接使用FileReader是一样的
        Reader isr = new InputStreamReader(is, "GBK"); // 以指定的GBK编码转换成字符输入流  完美的解决了乱码问题
        //包装成字符缓冲输入流
        BufferedReader br = new BufferedReader(isr);
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    }
} 
字符输出转换流
1、如果需要控制写出去的字符使用的编码,怎么办?
    可以把字符以指定编码获取字节后再使用字节输出流写出去:
    “我爱你中国”.getBytes(编码)
    也可以使用字符输出转换流实现。
 
import java.io.*;
/**
 * 目标:字符输出转换OutputStreamWriter流的使用。
 * <p>
 * 字节流                                         字符流
 * 字节输入流               字节输出流              字符输入流            字符输出流
 * InputStream             OutputStream          Reader               Writer   (抽象类)
 * FileInputStream         FileOutputStream      FileReader           FileWriter(实现类)
 * BufferedInputStream     BufferedOutputStream  BufferedReader       BufferedWriter(实现类,缓冲流)
 * InputStreamReader    OutputStreamWriter
 * <p>
 * 字符输出转换流:OutputStreamWriter
 * -- 作用:可以指定编码把字节输出流转换成字符输出流。
 * 可以指定写出去的字符的编码。
 * -- 构造器:
 * public OutputStreamWriter(OutputStream os) :   用当前默认编码UTF-8把字节输出流转换成字符输出流
 * public OutputStreamWriter(OutputStream os , String charset):指定编码把字节输出流转换成字符输出流
 * 小结:
 * 字符输出转换流OutputStreamWriter可以指定编码把字节输出流转换成字符输出流。
 * 从而实现指定写出去的字符编码!
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //1、定义一个字节输出流管道与文件接通
        OutputStream os = new FileOutputStream("hello-app\\src\\writer1.txt");
        //2、把原始的字节输出流转换成字符输出流
        Writer osw = new OutputStreamWriter(os);//已默认的UTF-8写字符出去,跟直接写FileWriter一样
        //Writer osw = new OutputStreamWriter(os,"GBK");//指定GBK的方式写字符出去
        //3、把低级的字符输出流包装成高级的缓冲字符输出
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("你好吗?\n");
        bw.write("我很好~\n");
        bw.write("交个朋友吧!\n");
        bw.close();
    }
} 
字符输出转换流
 字符输入转换流:OutputStreamWriter,可以把字节输出流按照指定编码转换成字符输出流。
 
|   构造器  |   说明  | 
|   public OutputStreamWriter(OutputStream os)  |   可以把原始的字节输出流按照代码默认编码转换成字符输出流。几乎不用。  | 
|   public OutputStreamWriter(OutputStream os,String charset)  |   可以把原始的字节输出流按照指定编码转换成字符输出流(重点)  | 
 
字节流
//创建一个文件字节输入流管道与源文件接通
//InputStream is = new FileInputStream(new File("hello-app\\src\\data.txt"));
//简化写法
InputStream is = new FileInputStream("hello-app\\src\\data.txt");
//创建一个文件字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("hello-app\\src\\out.txt");//先清空之前的数据,再写新数据进入
//OutputStream os = new FileOutputStream("hello-app\\src\\out.txt",true);//不会清空管道之前存在的数据
字符流
//创建一个字符输入流管道与文件接通
Reader fr = new FileReader("hello-app\\src\\data.txt");
//创建一个字符输出流管道与目标文件接通
//Writer fileWriter = new FileWriter("hello-app\\src\\write.txt");//覆盖管道,每次启动都会清空文件之前的数据
Writer fileWriter = new FileWriter("hello-app\\src\\write.txt", true);//不会覆盖管道
字节缓冲流
//1、创建一个字节输入流管道与文件接通
InputStream is = new FileInputStream("hello-app\\src\\out.txt");
//a.把原始字节输入流包装成高级的缓冲字节输入流
InputStream bis = new BufferedInputStream(is);
//2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("hello-app\\src\\out1.txt");
//b.把字节输出流包装成高级的缓冲字节输出流
OutputStream bos = new BufferedOutputStream(os);
字符缓冲流
 //创建一个文件字符输入流与源文件接通
Reader fr = new FileReader("hello-app\\src\\data.txt");
//把低级的字符输入流包装成高级的缓冲字符输入流
BufferedReader br = new BufferedReader(fr);
//创建一个字符输出流管道与目标文件接通
//Writer fileWriter = new FileWriter("hello-app\\src\\write1.txt");//覆盖管道,每次启动都会清空文件之前的数据
Writer fileWriter = new FileWriter("hello-app\\src\\write1.txt", true);//不会覆盖管道
 
//把低级的字符输出流包装成高级缓冲输出流
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
转换流
 //1、创建原始字节输入流管道与文件接通
InputStream is = new FileInputStream("D:\\Java学习\\data.txt");
//2、把原始字节流转换成字符输入流
//Reader isr = new InputStreamReader(is); 默认以UTF-8的方式转换成字符流。跟直接使用FileReader是一样的
Reader isr = new InputStreamReader(is, "GBK"); // 以指定的GBK编码转换成字符输入流
//1、定义一个字节输出流管道与文件接通
OutputStream os = new FileOutputStream("hello-app\\src\\writer1.txt");
//2、把原始的字节输出流转换成字符输出流
Writer osw = new OutputStreamWriter(os);//默认的UTF-8写字符出去,跟直接写FileWriter一样
//Writer osw = new OutputStreamWriter(os,"GBK");//指定GBK的方式写字符出去
 
序列化对象
对象序列化
对象序列化:
作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化。
使用到的流是对象字节输出流:ObjectOutputStream
 

对象序列化:
使用到的流是对象字节输出流:ObjectOutputStream
|   构造器  |   说明  | 
|   public ObjectOutputStream(OutputStream out)  |   把低级字节输出流包装成高级的对象字节输出流  | 
ObjectOutputStream序列化方法
|   方法名称  |   说明  | 
|   public final void writeObject(Object obj)  |   把对象写出去到对象序列化流的文件中去  | 
import java.io.Serializable;
/**
 * 对象如果要序列化,必须实现Serializable序列化接口。
 */
public class Student implements Serializable {
    private String name;
    private String loginName;
    private String passWord;
    private int age;
    public Student() {
    }
    public Student(String name, String loginName, String passWord, int age) {
        this.name = name;
        this.loginName = loginName;
        this.passWord = passWord;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getLoginName() {
        return loginName;
    }
    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }
    public String getPassWord() {
        return passWord;
    }
    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", loginName='" + loginName + '\'' +
                ", passWord='" + passWord + '\'' +
                ", age=" + age +
                '}';
    }
} 
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
/**
 * 目标:学会对象序列化,使用 ObjectOutputStream 把内存中的对象存入到磁盘文件中。
 * <p>
 * transient修饰的成员变量不参与序列化了
 * 对象如果要序列化,必须实现Serializable序列化接口。
 * <p>
 * 申明序列化的版本号码
 * 序列化的版本号与反序列化的版本号必须一致才不会出错!
 * private static final long serialVersionUID = 1;
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 1、创建学生对象
        Student student = new Student("张三", "豆精", "123456", 20);
        // 2、对象序列化:使用对象字节输出流包装字节输出流管道
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello-app\\src\\obj.txt"));
        // 3、直接调用序列化方法
        oos.writeObject(student);
        //4、释放资源
        oos.close();
        System.out.println("序列化完成了~~");
    }
} 
1、对象序列化的含义是什么?
    把对象数据存入到文件中去。
2、对象序列化用到了哪个流?
    对象字节输出流ObjectOutputStram
    public void writeObject(Object obj)
3、序列化对象的要求是怎么样的?
    对象必须实现序列化接口
 
对象反序列化
对象反序列化:
使用到的流是对象字节输入流:ObjectInputStream
作用:以内存为基准,把存储到磁盘文件中去的对象数据恢复成内存中的对象,称为对象反序列化。
 

|   构造器  |   说明  | 
|   public ObjectInputStream(InputStream out)  |   把低级字节输如流包装成高级的对象字节输入流  | 
ObjectInputStream序列化方法
|   方法名称  |   说明  | 
|   public Object readObject()  |   把存储到磁盘文件中去的对象数据恢复成内存中的对象返回  | 
1、对象反序列化的含义是什么?
    把磁盘中的对象数据恢复到内存的Java对象中。
2、对象反序列化用到了哪个流?
    对象字节输入流ObjectInputStram
    public Object readObject()
 
import java.io.Serializable;
/**
 * 对象如果要序列化,必须实现Serializable序列化接口。
 */
public class Student implements Serializable {
    // 申明序列化的版本号码
    // 序列化的版本号与反序列化的版本号必须一致才不会出错!
    private static final long serialVersionUID = 1;
    private String name;
    private String loginName;
    // transient修饰的成员变量不参与序列化了
    private transient String passWord;
    private int age;
    public Student() {
    }
    public Student(String name, String loginName, String passWord, int age) {
        this.name = name;
        this.loginName = loginName;
        this.passWord = passWord;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getLoginName() {
        return loginName;
    }
    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }
    public String getPassWord() {
        return passWord;
    }
    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", loginName='" + loginName + '\'' +
                ", passWord='" + passWord + '\'' +
                ", age=" + age +
                '}';
    }
} 
import java.io.FileInputStream;
import java.io.ObjectInputStream;
/**
 目标:学会进行对象反序列化:使用对象字节输入流把文件中的对象数据恢复成内存中的Java对象。
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 1、创建对象字节输入流管道包装低级的字节输入流管道
        ObjectInputStream is = new ObjectInputStream(new FileInputStream("hello-app\\src\\obj.txt"));
        // 2、调用对象字节输入流的反序列化方法
        Student s = (Student) is.readObject();
        is.close();
        System.out.println(s);
        System.out.println("反序列化完成!");
    }
} 
打印流
PrintStream、PrintWriter
打印流
作用:打印流可以实现方便、高效的打印数据到文件中去。打印流一般是指:PrintStream,PrintWriter两个类。
可以实现打印什么数据就是什么数据,例如打印整数97写出去就是97,打印boolean的true,写出去就是true。
 
PrintStream
|   构造器  |   说明  | 
|   public PrintStream(OutputStream os)  |   打印流直接通向字节输出流管道  | 
|   public PrintStream(File f)  |   打印流直接通向文件对象  | 
|   public PrintStream(String filepath)  |   打印流直接通向文件路径  | 
|   方法  |   说明  | 
|   public void print(Xxx xx)  |   打印任意类型的数据出去  | 
PrintWriter
|   构造器  |   说明  | 
|   public PrintWriter(OutputStream os)  |   打印流直接通向字节输出流管道  | 
|   public PrintWriter (Writer w)  |   打印流直接通向字符输出流管道  | 
|   public PrintWriter (File f)  |   打印流直接通向文件对象  | 
|   public PrintWriter (String filepath)  |   打印流直接通向文件路径  | 
|   方法  |   说明  | 
|   public void print(Xxx xx)  |   打印任意类型的数据出去  | 
PrintStream和PrintWriter的区别
    打印数据功能上是一模一样的,都是使用方便,性能高效(核心优势)
    PrintStream继承自字节输出流OutputStream,支持写字节数据的方法。
    PrintWriter继承自字符输出流Writer,支持写字符数据出去。
 
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
 * 目标:学会使用打印流 高效  方便写数据到文件。
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 1、创建一个打印流对象
        //PrintStream ps = new PrintStream(new FileOutputStream("hello-app\\src\\ps.txt"));
        //PrintStream ps = new PrintStream(new FileOutputStream("hello-app\\src\\ps.txt",true));//追加数据,在低级管道后面加True
        PrintStream ps = new PrintStream("hello-app\\src\\ps.txt");
        //PrintWriter ps = new PrintWriter("hello-app\\src\\ps.txt");
        ps.println(97);
        ps.println('a');
        ps.println(21.3);
        ps.println(true);
        ps.println("我是打印流,是啥就打印啥~~");
        ps.close();
    }
} 
1、打印流有几种?各有什么特点?
    打印流一般是指:PrintStream,PrintWriter两个类。
    打印功能2者是一样的使用方式
    PrintStream继承自字节输出流OutputStream,支持写字节
    PrintWrite继承自字符输出流Writer,支持写字符
2、打印流的优势是什么?
    两者在打印功能上都是使用方便,性能高效(核心优势) 
输出语句的重定向
输出语句重定向
    属于打印流的一种应用,可以把输出语句的打印位置改到文件。
 

import java.io.PrintStream;
/**
 * 目标:了解改变输出语句的位置到文件
 */
public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println("锦瑟无端五十弦");
        System.out.println("一弦一柱思华年");
        // 改变输出语句的位置(重定向)
        PrintStream ps = new PrintStream("hello-app\\src\\log.txt");
        System.setOut(ps); // 把系统打印流改成我们自己的打印流
        System.out.println("庄生晓梦迷蝴蝶");
        System.out.println("望帝春心托杜鹃");
    }
} 
补充知识:Properties

Properties属性集对象
    其实就是一个Map集合,但是我们一般不会当集合使用,因为HashMap更好用。
Properties核心作用:
    Properties代表的是一个属性文件,可以把自己对象中的键值对信息存入到一个属性文件中去。
    属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value,后续做系统配置信息的。 
Properties的API:
|   构造器  |   说明  | 
|   void load(InputStream inStream)  |   从输入字节流读取属性列表(键和元素对)  | 
|   void load(Reader reader)  |   从输入字符流读取属性列表(键和元素对)  | 
|   void store(OutputStream out, String comments)  |   将此属性列表(键和元素对)写入此 Properties表中,以适合于使用 load(InputStream)方法的格式写入输出字节流  | 
|   void store(Writer writer, String comments)  |   将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式写入输出字符流  | 
|   public Object setProperty(String key, String value)  |   保存键值对(put)  | 
|   public String getProperty(String key)  |   使用此属性列表中指定的键搜索属性值 (get)  | 
|   public Set<String> stringPropertyNames()  |   所有键的名称的集合 (keySet())  | 
import java.io.FileWriter;
import java.io.PrintStream;
import java.util.Properties;
/**
 * 目标:Properties的概述和使用(框架底层使用,了解这个技术即可)(保存数据到属性文件)
 * <p>
 * Properties: 属性集对象。
 * 其实就是一个Map集合。也就是一个键值对集合,但是我们一般不会当集合使用,
 * 因为有HashMap。
 * <p>
 * Properties核心作用:
 * Properties代表的是一个属性文件,可以把键值对的数据存入到一个属性文件中去。
 * 属性文件:后缀是.properties结尾的文件,里面的内容都是 key=value。
 * <p>
 * 大家在后期学的很多大型框架技术中,属性文件都是很重要的系统配置文件。
 * users.properties
 * admin=123456
 * dlei=dlei
 * <p>
 * 需求:使用Properties对象生成一个属性文件,里面存入用户名和密码信息。
 * <p>
 * Properties的方法:
 * -- public Object setProperty(String key, String value) : 保存一对属性。  (put)
 * -- public String getProperty(String key) : 使用此属性列表中指定的键搜索属性值 (get)
 * -- public Set<String> stringPropertyNames() : 所有键的名称的集合  (keySet())
 * -- public void store(OutputStream out, String comments): 保存数据到属性文件中去
 * -- public void store(Writer fw, String comments): 保存数据到属性文件中去
 * <p>
 * 小结:
 * Properties可以保存键值对数据到属性文件
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 需求:使用Properties把键值对信息存入到属性文件中去。
        Properties properties = new Properties();
        properties.setProperty("admin1", "123456");//properties.put("admin3","567890");效果一样
        properties.setProperty("admin2", "098765");
        properties.setProperty("admin3", "567890");
        //System.out.println(properties);
        /**
         参数一:保存管道 字符输出流管道
         参数二:保存心得
         */
        properties.store(new FileWriter("hello-app\\src\\properties.txt"), "Hello~~");
    }
} 
import java.io.FileReader;
import java.util.Properties;
/**
 * 目标:Properties读取属性文件中的键值对信息。(读取)
 * Properties的方法:
 * -- public Object setProperty(String key, String value) : 保存一对属性。
 * -- public String getProperty(String key) :使用此属性列表中指定的键搜索属性值
 * -- public Set<String> stringPropertyNames() :所有键的名称的集合
 * -- public void store(OutputStream out, String comments):保存数据到属性文件中去
 * -- public synchronized void load(InputStream inStream):加载属性文件的数据到属性集对象中去
 * -- public synchronized void load(Reader fr):加载属性文件的数据到属性集对象中去
 * 小结:
 * 属性集对象可以加载读取属性文件中的数据!!
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 需求:Properties读取属性文件中的键值对信息。(读取)
        Properties properties = new Properties();
        System.out.println(properties);//{}
        // 加载属性文件中的键值对数据到属性对象properties中去
        properties.load(new FileReader("hello-app\\src\\properties.txt"));
        System.out.println(properties);
        String rs = properties.getProperty("admin1");//键取值
        System.out.println(rs);
    }
} 
Properties的作用?
    可以存储Properties属性集的键值对数据到属性文件中去:
    void store(Writer writer, String comments)
    可以加载属性文件中的数据到Properties对象中来:
    void load(Reader reader)
 
补充知识: IO框架
commons-io概述
    commons-io是apache开源基金组织提供的一组有关IO操作的类库,可以提高IO功能开发的效率。
    commons-io工具包提供了很多有关io操作的类。有两个主要的类FileUtils, IOUtils 
FileUtils主要有如下方法:
|   方法名  |   说明  | 
|   String readFileToString(File file, String encoding)  |   读取文件中的数据, 返回字符串  | 
|   void copyFile(File srcFile, File destFile)  |   复制文件。  | 
|   void copyDirectoryToDirectory(File srcDir, File destDir)  |   复制文件夹。  | 
导入commons-io-2.6.jar做开发
需求
    使用commons-io简化io流读写
分析
    在项目中创建一个文件夹:lib
    将commons-io-2.6.jar文件复制到lib文件夹
    在jar文件上点右键,选择 Add as Library -> 点击OK
    在类中导包使用
 
import org.apache.commons.io.FileUtils;
import java.io.File;
/**
    目标:Commons-io包的使用介绍。
    什么是Commons-io包?
            commons-io是apache开源基金组织提供的一组有关IO操作的类库,
            可以挺提高IO功能开发的效率。commons-io工具包提供了很多有关io操作的类,
    见下表:
         | 包                                  | 功能描述                                     |
         | ----------------------------------- | :------------------------------------------- |
         | org.apache.commons.io               | 有关Streams、Readers、Writers、Files的工具类 |
         | org.apache.commons.io.input         | 输入流相关的实现类,包含Reader和InputStream  |
         | org.apache.commons.io.output        | 输出流相关的实现类,包含Writer和OutputStream |
         | org.apache.commons.io.serialization | 序列化相关的类
    步骤:
         1. 下载commons-io相关jar包;http://commons.apache.org/proper/commons-io/
         2. 把commons-io-2.6.jar包复制到指定的Module的lib目录中
         3. 将commons-io-2.6.jar加入到classpath中
    小结:
         IOUtils和FileUtils可以方便的复制文件和文件夹!!
 */
public class Test {
    public static void main(String[] args) throws Exception {
        // 1.完成文件复制!
//        IOUtils.copy(new FileInputStream("D:\\resources\\hushui.jpeg"),
//                new FileOutputStream("D:\\resources\\hushui2.jpeg"));
        // 2.完成文件复制到某个文件夹下!
//        FileUtils.copyFileToDirectory(new File("D:\\resources\\hushui.jpeg"), new File("D:/"));
          // 3.完成文件夹复制到某个文件夹下!
//          FileUtils.copyDirectoryToDirectory(new File("D:\\resources") , new File("D:\\new"));
//           FileUtils.deleteDirectory(new File("D:\\new"));
         // JDK1.7 自己也做了一些一行代码完成复制的操作:New IO的技术
         // Files.copy(Path.of("D:\\resources\\hushui.jpeg"), Path.of("D:\\resources\\hushui3.jpeg"));
        FileUtils.deleteDirectory(new File("D:\\new"));
    }
} 
                
























