File,IO流,字符集

文章目录

存储数据的方案

内存中的数据

在这里插入图片描述
这些数据容器都在内存中,一旦程序结束,或者断电,数据就没有了!

长久保存的数据

在这里插入图片描述
文件可以长久保存数据。
文件在电脑磁盘中保存,即便断电,或者程序终止,文件中的数据也不会丢失。

初识File,IO流

什么是File
File是java.io.包下的类, File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)。

功能
获取文件信息(大小,文件名,修改时间)
创建文件/文件夹
删除文件/文件夹
判断文件的类型

注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据。读取数据使用IO流

IO流
用于读写数据的(可以读写文件,或网络中的数据…)
File 其对象代表具体的文件,只能操作文件本身
IO流 读写数据

File

什么是File

File类的对象可以代表文件/文件夹,并可以调用其提供的方法对象文件进行操作。

创建File类的对象

下面是Flie的构造方法。有这三种创建方法。第一个常用。

构造器说明
public File(String pathname)根据文件路径创建文件对象
public File(String parent, String child)根据父路径和子路径名字创建文件对象
public File(File parent, String child)根据父路径对应文件对象和子路径名字创建文件对象

File f1 = new File("E:/resource/hsh.jpg");
注意

  • File对象既可以代表文件、也可以代表文件夹。
  • File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的。

绝对路径、相对路径

绝对路径:从盘符开始File file1 = new File(“D:\\itheima\\a.txt”);
相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
File file3 = new File(“模块名\\a.txt”);

File常用方法。

File提供的判断文件类型、获取文件信息功能。

方法名称说明
public boolean exists()判断当前文件对象,对应的文件路径是否存在,存在返回true
public boolean isFile()判断当前文件对象指代的是否是文件,是文件返回true,反之。
public boolean isDirectory()判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
public String getName()获取文件的名称(包含后缀)
public long length()获取文件的大小,返回字节个数
public long lastModified()获取文件的最后修改时间。
public String getPath()获取创建文件对象时,使用的路径
public String getAbsolutePath()获取绝对路径

File类创建和删除文件的方法

File类创建文件的功能

方法名称说明
public boolean createNewFile()创建一个新的空的文件
public boolean mkdir()只能创建一级文件夹
public boolean mkdirs()可以创建多级文件夹

File类删除文件的功能

方法名称说明
public boolean delete()删除文件、空文件夹

注意:delete方法默认只能删除文件和空文件夹,删除后的文件不会进入回收站。

import java.io.File;

public class FileDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:创建File创建对象代表文件(文件/目录),搞清楚其提供的对文件进行操作的方法。
        // 1、创建File对象,去获取某个文件的信息
//        File f1 = new File("E:\\resource\\dlei.jpg");
        File f1 = new File("E:/resource/hsh.jpg");

        System.out.println(f1.length()); // 字节个数
        System.out.println(f1.getName());
        System.out.println(f1.isFile()); // true
        System.out.println(f1.isDirectory()); // false

        // 2、可以使用相对路径定位文件对象
        // 只要带盘符的都称之为:绝对路径 E:/resource/dlei.jpg
        // 相对路径:不带盘符,默认是到你的idea工程下直接寻找文件的。一般用来找工程下的项目文件的。
        File f2 = new File("day03-file-io\\src\\hsh01.txt");
        System.out.println(f2.length());
        System.out.println(f2.getAbsoluteFile()); // 获取绝对路径

        // 3、创建对象代表不存在的文件路径。
        File f3 = new File("E:\\resource\\hsh01.txt");
        System.out.println(f3.exists()); // 判断是否存在
        System.out.println(f3.createNewFile()); // 把这个文件创建出来

        // 4、创建对象代表不存在的文件夹路径。
        File f4 = new File("E:\\resource\\aaa");
        System.out.println(f4.mkdir()); // mkdir只能创建一级文件夹

        File f5 = new File("E:\\resource\\kkk\\jjj\\mmm");
        System.out.println(f5.mkdirs()); // mkdir可以创建多级文件夹,很重要!

        // 5、创建File对象代表存在的文件,然后删除它
        File f6 = new File("E:\\resource\\dlei01.txt");
        System.out.println(f6.delete()); // 删除文件

        // 6、创建File对象代表存在的文件夹,然后删除它
        File f7 = new File("E:\\resource\\aaa");
        System.out.println(f7.delete());  // 只能删除空文件,不能删除非空文件夹

        File f8 = new File("E:\\resource");
        System.out.println(f8.delete());  // 只能删除空文件,不能删除非空文件夹

        
    }
}

File类提供的遍历文件夹的方法

方法名称说明
public String[] list()获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。
public File[] listFiles()获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)

使用listFiles方法时的注意事项:

  1. 当主调是文件,或者路径不存在时,返回null
  2. 当主调是空文件夹时,返回一个长度为0的数组
  3. 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
  4. 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
  5. 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null
import java.io.File;
import java.util.Arrays;

public class FileDemo2 {
    public static void main(String[] args) {
        // 可以获取某个目录下的全部一级文件名称
        File f9 = new File("E:");
        String[] names = f9.list();
        for (String name : names) {
            System.out.println(name);
        }

        File[] files = f9.listFiles();
        for (File file : files) {
            System.out.println(file.getAbsoluteFile()); // 获取绝对路径
        }
        
        // 注意事项。
        File dir = new File("E:/resource/dlei.txt");
        File dir2 = new File("E:\\resource\\eee777");
    
        /**
         * 当主调是文件,或者路径不存在时,返回null
         * 当主调是空文件夹时,返回一个长度为0的数组
         * 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回
         * 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件
         * 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null
         */
        File[] files = dir2.listFiles();
        System.out.println(Arrays.toString(files));
    }
}

递归

什么是递归

递归是一种算法,在程序设计语言中广泛应用。
从形式上说:方法调用自身的形式称为方法递归( recursion)。

递归的形式

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

使用方法递归时需要注意的问题:
递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出错误。

public class RecursionDemo1 {
    public static void main(String[] args) {
        // 目标:认识递归的形式。
        printA();
    }

    public static void printA() {
        System.out.println("===A执行了===");
        printA(); // 直接递归:自己调用自己  .  递归可能出现死循环,导致出现栈内存溢出现象。
    }
}

n的阶乘

需求:计算n的阶乘,5的阶乘=12345; 6的阶乘=12345*6;

分析
假如我们认为存在一个公式是 f(n) = 1234567*…(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

代码

public class RecursionDemo2 {
    public static void main(String[] args) {
        System.out.println("5的阶乘:" + f(5));
    }
    public static int f(int n){
        if(n == 1){
            return 1;
        }
        return f(n - 1) * n; // 递归调用
    }
}

使用递归求阶乘代码的执行流程

  1. 第一步
    在这里插入图片描述
  2. 第二部
    在这里插入图片描述
  3. 第三步
    在这里插入图片描述
  4. 第四步
    在这里插入图片描述
  5. 第五步
    在这里插入图片描述
  6. 第六步
    在这里插入图片描述
  7. 第七部
    在这里插入图片描述
  8. 第八步
    在这里插入图片描述
  9. 第九步
    在这里插入图片描述

递归算法的三要素

递归的公式: 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的和

需求:计算1-n的和的结果,使用递归思想解决。
分析: 我们先从数学思维上理解递归的流程和核心点
假如我们认为存在一个公式是 f(n) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + …(n-1) + n;
那么公式等价形式就是: f(n) = f(n-1) + n
递归的终结点:f(1) = 1
递归的方向
f(5) = f(4) + 5
f(4) = f(3) + 4
f(3) = f(2) + 3
f(2) = f(1) + 2
f(1) = 1

public class RecursionDemo2 {
    public static void main(String[] args) {
        System.out.println("1-5的和:" + f2(5));
    }
    public static int f2(int n){
        if(n == 1){
            return 1;
        }
        return f2(n - 1) +  n; // 递归调用
    }
}

递归求猴子吃桃

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

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

分析:
整体来看,每一天都是做同一个事件,典型的规律化问题,考虑递归三要素:
递归公式:
递归终结点:
递归方向:

public class RecursionDemo3 {
    public static void main(String[] args) {
        // 公式:
        //       f(n + 1) =  f(n) - f(n) / 2 - 1
        // 变形: 2f(n + 1) = 2f(n) - f(n) - 2
        // 变形: f(n) = 2f(n + 1) + 2

        // 终结点: f(10) = 1

        // 递归的方向: 没有问题的

        System.out.println(f(1));
        System.out.println(f(2));
        System.out.println(f(3));
        System.out.println(f(4));
    }

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

文件搜索

需求:从D:盘中,搜索“QQ.exe” 这个文件,找到后直接输出其位置。
分析:
先找出D:盘下的所有一级文件对象
遍历全部一级文件对象,判断是否是文件
如果是文件,判断是否是自己想要的
如果是文件夹,需要继续进入到该文件夹,重复上述过程

import java.io.File;
public class FileSearchTest4 {
    public static void main(String[] args) {
        try {
            File dir = new File("D:/");
            searchFile(dir , "QQ.exe");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 搜索文件
     * @param dir 搜索的目录
     * @param fileName 搜索的文件名称
     */
    public static void searchFile(File dir, String fileName) throws Exception {
        // 1、判断极端情况
        if(dir == null || !dir.exists() || dir.isFile()){
            return; // 不搜索
        }

        // 2、获取目录下的所有一级文件或者文件夹对象
        File[] files = dir.listFiles();

        // 3、判断当前目录下是否存在一级文件对象,存在才可以遍历
        if(files != null && files.length > 0){
            // 4、遍历一级文件对象
            for (File file : files) {
                // 5、判断当前一级文件对象是否是文件
                if(file.isFile()){
                    // 6、判断文件名称是否和目标文件名称一致
                    if(file.getName().contains(fileName)){
                        System.out.println("找到目标文件:" + file.getAbsolutePath());
                        // 获取虚拟机,并打开QQ
                        Runtime r = Runtime.getRuntime();
                        r.exec(file.getAbsolutePath());
                    }
                }else{
                    // 7、如果当前一级文件对象是文件夹,则继续递归调用
                    searchFile(file, fileName);
                }
            }
        }
    }
}

常见字符集

什么是字符集

计算机只认识 0 1 。于是美国人发明了用不同序号代表不同的字母以及数字。如下图。注意:一个字节是八位数二进制
在这里插入图片描述

标准ASCII字符集

ASCII(American Standard Code for Information Interchange): 美国信息交换标准代码,包括了英文、符号等。
标准ASCII使用1个字节存储一个字符,首位是0,因此,总共可表示128个字符,对美国佬来说完全够用。
在这里插入图片描述

中国汉字的字符集

GBK(汉字内码扩展规范,国标)
汉字编码字符集,包含了2万多个汉字等字符,GBK中一个中文字符编码成两个字节(2^15=3万多字)的形式存储。
注意:GBK兼容了ASCII字符集。
GBK规定:汉字的第一个字节的第一位必须是1。

// 我a你  的GBK编码。
1xxxxxxx  xxxxxxxx  | 0xxxxxxx  | 1xxxxxxx  xxxxxxxx 
       我                 a                你
计算机遇到1 说明是汉字, 则前两个字节是一个汉字。遇见0说明这是英文字母

美国,中国有了自己的字符集。那么各个国家同样都有自己的字符集代表自家的文字。但是这么多国家的字符集需要大一统。

Unicode字符集(统一码,也叫万国码)

Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集。

UTF-32

是Unicode字符集的一种编码方案。这个编码是四个字节表示一个字。这样那个国家都可以用,因为一个国家字符再多,也不会超过 2^31个字。
但是这个虽然大,但是很奢侈,占存储空间,通信效率变低!
比如中国字只需要两个字节,剩下的字节就浪费了。

于是有了UTF-8编码

UTF-8编码

是Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节
英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节。

使用UTF-8编码计算机如何区分不同国的字呢?
以中国字为例。

UTF-8编码方式(二进制)
0xxxxxxx (ASCII码)
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx (中国字使用这个)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
a我m
a      97        0110000125105     110 001000 010001
m      109       01101101
  
01100001 | 1110xxxx 10xxxxxxx 10xxxxxx | 01101101
   a                  我                     m
"我"这个字是 1110+11010+00100010+010001  
于是1110+ 0(不够的用0补充) +110 ,10001000,1010+010001
==>      于是得到了       111001101000100010010001  
==> 使用补码运算 先减一,  111001011000011110010000
==> 去反                  000110100111100001101111
==>  			            26       120       111
⇒     添负号              -26-120-111
所以我这个字是(-26, -120, -111)组成。

字符集的编码、解码操作

对字符的编码

String提供了如下方法说明
byte[] getBytes()使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes(String charsetName)使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中

对字符的解码

String提供了如下方法说明
String(byte[] bytes)通过使用平台的默认字符集解码指定的字节数组来构造新的 String
String(byte[] bytes, String charsetName)通过指定的字符集解码指定的字节数组来构造新的 String
import java.util.Arrays;
public class CharSetDemo1 {
    public static void main(String[] args) throws Exception {
        
        // 1、编码
        String name = "我爱你中国abc666";

        System.out.println(Arrays.toString("a我m".getBytes()));
        // [97, -26, -120, -111, 109] 和上面一样

        // byte[] bytes = name.getBytes(); // 平台的UTF-8编码的。
        byte[] bytes = name.getBytes("GBK"); // 指定GBK进行编码。
        System.out.println(bytes.length);// 16
        System.out.println(Arrays.toString(bytes));
        // [-50, -46, -80, -82, -60, -29, -42, -48, -71, -6, 97, 98, 99, 54, 54, 54]

        // 2、解码
        // String name2 = new String(bytes); // 平台的UTF-8解码的。
        String name2 = new String(bytes, "GBK");// 指定GBK进行解码
        System.out.println(name2);
        // 我爱你中国abc666
    }
}

IO流

认识IO流

什么是IO

I指Input,称为输入流:负责把数据读到内存中去
O指Output,称为输出流:负责写数据出去

IO流的常见应用场景

读取文件数据,记录数据到文件中,复制图片

io流的分类

按照流方向:IO流分为

  1. 输入流
  2. 输出流

按照流的内容:IO流分为

  1. 字节流:适合操作所有类型的文件,比如:音频、视频、图片
    文本文件的复制,转移等。
  2. 字符流:只适合操作纯文本文件,比如:读写txt、java文件等。

字节是指一个八位数的二进制。
字符是指一个字,比如
中文的一个字就是一个字符,以utf-8为例,中文字符占用三个字节,一个字节就是八位二进制数。
英文的一个字母就是一个字符,以utf-8为例,英文字符占用一个字节,一个字节就是八位二进制数。

由上可知 字节输入流分为

  1. 字节输入流
  2. 字节输出流
  3. 字符输入流
  4. 字符输出流
    在这里插入图片描述
    在这里插入图片描述

字节流

FileInputStream

FileInputStream(文件字节输入流)

作用

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

读取字节的使用
构造器说明
public FileInputStream(File file)创建字节输入流管道与源文件接通
public FileInputStream(String pathname)创建字节输入流管道与源文件接通
方法名称说明
public int read()每次读取一个字节返回,如果发现没有数据可读会返回-1.
public int read(byte[] buffer)每次用一个字节数组去读取数据,返回字节数组读取了多少个字节,如果发现没有数据可读会返回-1.

使用FileInputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码。
使用FileInputStream每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码。

import java.io.FileInputStream;
import java.io.InputStream;

public class FileInputStreamDemo1 {
    public static void main(String[] args) throws Exception {
        // 1、创建文件字节输入流管道于源文件接通
        // InputStream is = new FileInputStream(new File("day-13\src\com\hsh\demo4fileinputStream\hsh01.txt"));
        // 等价于下面这行代码
        InputStream is = new FileInputStream("day-13\\src\\com\\hsh\\demo4fileinputStream\\hsh01.txt"); // 简化写法

        // 2、开始读取文件中的字节并输出: 每次读取一个字节
        // 定义一个变量记住每次读取的一个字节
        int b;
        while ((b = is.read()) != -1) {
            System.out.print((char) b);
        }
        //  结果 abc666
        // 记住由于这是一个字节一个字节的读。 因为Utf-8的一个汉字是三字节组成。
        // 每次读取一个字节的问题:性能较差,读取汉字输出一定会乱码!
        // 关闭字节输入流
        is.close();
    }
}
hsh01.txt
abc666
import java.io.FileInputStream;
import java.io.InputStream;
public class FileInputStreamDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:掌握文件字节输入流读取文件中的字节数组到内存中来。
        // 1、创建文件字节输入流管道于源文件接通
        InputStream is = new FileInputStream("day-13\\src\\com\\hsh\\demo4fileinputStream\\hsh02.txt"); // 简化写法

        // 2、开始读取文件中的字节并输出: 每次读取多个字节
        // 定义一个字节数组用于每次读取字节
        byte[] buffer = new byte[3];
        // 定义一个变量记住每次读取了多少个字节。
        int len;
        while ((len = is.read(buffer)) != -1) {
            // String str = new String(buffer);// 123456756
            // 上面这行代码为什么是123456756而不是123456?
            // 因为每次读取三个字节,当读取到456后下一次读取的时候,只剩一个7,
            // 56是上一次读取的数字!这个数字并没有被覆盖所以被打印出来了,下面是解决方案
            // 3、把读取到的字节数组转换成字符串输出,读取多少倒出多少
            String str = new String(buffer,0, len);// 1234567
            System.out.print(str);
        }
        // 4、关闭流资源
        is.close();

        // 拓展:每次读取多个字节,性能得到提升,因为每次读取多个字节,可以减少硬盘和内存的交互次数,从而提升性能。
        // 依然无法避免读取汉字输出乱码的问题:存在截断汉字字节的可能性!
    }
}
hsh02.txt
1234567

1、使用字节流读取中文,如何保证输出不乱码,怎么解决?
定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。

一次读取完全部字节

Java官方为InputStream提供了如下方法,可以直接把文件的全部字节读取到一个字节数组中返回。

方法名称说明
public byte[] readAllBytes() throws IOException直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回

注意事项
直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
如果文件过大,创建的字节数组也会过大,可能引起内存溢出。
读取文本适合用字符流;字节流适合做数据的转移,比如:文件复制

import java.io.FileInputStream;
import java.io.InputStream;
public class FileInputStreamDemo3 {
    public static void main(String[] args) throws Exception {
        // 目标:掌握文件字节输入流读取文件中的字节数组到内存中来。
        // 1、创建文件字节输入流管道于源文件接通
        InputStream is = new FileInputStream("day-13\\src\\com\\hsh\\demo4fileinputStream\\hsh04.txt"); // 简化写法

        // 2、一次性读完文件的全部字节:可以避免读取汉字输出乱码的问题。
        byte[] bytes = is.readAllBytes();

        String rs = new String(bytes);
        System.out.println(rs);
        // kshfk好几十累进税率二我lLjfjl
        // efnkl;s
        // djfgljue4dv&^49l
        // 3、关闭流资源
        is.close();
    }
}
hsh04.txt
kshfk好几十累进税率二我lLjfjl
efnkl;s
djfgljue4dv&^49l

FileOutputSteam

作用

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

使用
构造器说明
public FileOutputStream(File file)创建字节输出流管道与源文件对象接通
public FileOutputStream(String filepath)创建字节输出流管道与源文件路径接通
public FileOutputStream(File file,boolean append)创建字节输出流管道与源文件对象接通,可追加数据
public FileOutputStream(String filepath,boolean append)创建字节输出流管道与源文件路径接通,可追加数据
方法名称说明
public void write(int a)写一个字节出去
public void write(byte[] buffer)写一个字节数组出去
public void write(byte[] buffer , int pos , int len)写一个字节数组的一部分出去。
public void close() throws IOException关闭流。
import java.io.FileOutputStream;
import java.io.OutputStream;

public class FileOutputStreamDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:学会使用文件字节输出流。
        // 1、创建文件字节输出流管道与目标文件接通
        // OutputStream os = new FileOutputStream("day03-file-io/src/dlei05-out.txt"); // 覆盖管道
        OutputStream os = new FileOutputStream("day-13\\src\\com\\hsh\\demo5fileoutputStream\\hsh.txt", true); // 追加数据

        // 2、写入数据
        //  public void write(int b)
        os.write(97); // 写入一个字节数据
        os.write('b'); // 写入一个字符数据
        //  os.write('徐'); // 写入一个字符数据 会乱码
        os.write("\r\n".getBytes()); // 换行
        // hsh.txt文件中有  
        // ab
        // 

        // 3、写一个字节数组出去
        // public void write(byte[] b)
        byte[] bytes = "我爱你中国666".getBytes();
        os.write(bytes);
        os.write("\r\n".getBytes()); // 换行
        // hsh.txt文件中有 
        // ab
        // 我爱你中国666
        // 

        // 4、写一个字节数组的一部分出去
        // public void write(byte[] b, int pos, int len)
        os.write(bytes, 0, 3);
        os.write("\r\n".getBytes()); // 换行
        // hsh.txt文件中有 
        // ab
        // 我爱你中国666
        // 我
        // 

        os.close(); // 关闭管道 释放资源
    }
}
hsh.txt
ab
我爱你中国666

文件复制

在这里插入图片描述

任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!

资源释放的方案

try-catch-finally
// try-catch-finally
try {   ...
    ... 	
} catch (IOException e) {
    e.printStackTrace();
}
finally{   }

finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。
作用:一般用于在程序执行完成后进行资源的释放操作(专业级做法)。

InputStream is = null ;
OutputStream os = null;
try{  ... }
catch (Exception e){   e.printStackTrace();} 
finally {   
	// 防止在上面的try中有除了IO异常的其他异常导致fos或者fis为null
    // 释放资源时,需要判断是否为null
	// 关闭资源!
	try {if(os != null) os.close();} 
	catch (Exception e) { e.printStackTrace(); }
	// 关闭资源!
    try { if(is != null) is.close();   } 
    catch (Exception e) {     e.printStackTrace(); }
}

上面臃肿不优雅,

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class CopyDemo1 {
    public static void main(String[] args) {
        // 目标:使用字节流完成文件的复制操作。
        // 源文件:E:\resource\jt.jpg
        // 目标文件:D:\jt_new.jpg (复制过去的时候必须带文件名的,无法自动生成文件名。)
        copyFile("day-13\\src\\com\\hsh\\demo6copy\\oldImg\\1.jpg",
                "day-13\\src\\com\\hsh\\demo6copy\\newImg\\new.jpg");
    }

    // 复制文件
    public static void copyFile(String srcPath, String destPath)  {
        // 1、创建一个文件字节输入流管道与源文件接通
        InputStream fis = null;
        OutputStream fos = null;
        try {
            fis = new FileInputStream(srcPath);
            fos = new FileOutputStream(destPath);
            // 2、读取一个字节数组,写入一个字节数组  1024 + 1024 + 3
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len); // 读取多少个字节,就写入多少个字节
            }
            System.out.println("复制成功!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 最后一定会执行一次:即便程序出现异常!
            // 3、释放资源
            // 防止在上面的try中有除了IO异常的其他异常导致fos或者fis为null
            // 释放资源时,需要判断是否为null
            try {
                if(fos != null) fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if(fis != null) fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

上面臃肿不优雅,于是有了下面的方法。

try-with-resource

JDK 7开始提供了更简单的资源释放方案:try-with-resource
即在try后加个()在这里释放资源。

try(定义资源1;定义资源2;…){
	可能出现异常的代码;
}catch(异常类名 变量名){
    异常的处理代码;
} 

该资源使用完毕后,会自动调用其close()方法,完成对资源的释放!
() 中只能放置资源,否则报错

import java.io.*;
public class CopyDemo2 {
    public static void main(String[] args) {
        // 源文件:E:\resource\jt.jpg
        // 目标文件:D:\jt_new.jpg (复制过去的时候必须带文件名的,无法自动生成文件名。)
        copyFile("day-13\\src\\com\\hsh\\demo6copy\\oldImg\\1.jpg",
                "day-13\\src\\com\\hsh\\demo6copy\\newImg\\new.jpg");
    }

    // 复制文件
    public static void copyFile(String srcPath, String destPath)  {
        // 1、创建一个文件字节输入流管道与源文件接通
        try (
                 // 这里只能放置资源对象,用完后,最终会自动调用其close方法关闭!!
                 InputStream fis = new FileInputStream(srcPath);
                 OutputStream fos = new FileOutputStream(destPath);
                ){
            // 2、读取一个字节数组,写入一个字节数组  1024 + 1024 + 3
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len); // 读取多少个字节,就写入多少个字节
            }
            System.out.println("复制成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

什么是资源呢?
资源一般指的是最终实现了AutoCloseable接口。

public abstract class InputStream implements Closeable{ }

public abstract class OutputStream implements Closeable, Flushable { }

public interface Closeable extends AutoCloseable { }

下面举一个实现Closeable并且重写close的例子

import java.io.*;

public class CopyDemo2 {
    public static void main(String[] args) {
        copyFile("day-13\\src\\com\\hsh\\demo6copy\\oldImg\\1.jpg",
                "day-13\\src\\com\\hsh\\demo6copy\\newImg\\new.jpg");
    }
    // 复制文件
    public static void copyFile(String srcPath, String destPath)  {
        try (
           // 这里只能放置资源对象,用完后,最终会自动调用其close方法关闭!!
           InputStream fis = new FileInputStream(srcPath);
           OutputStream fos = new FileOutputStream(destPath);
           MyConn conn = new MyConn(); // 自定义的资源对象 最终会自动调用其close方法关闭!!// 输出资源关闭了!
       ){
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, len); 
            }
            System.out.println("复制成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
// 自定义方法
class MyConn implements Closeable{
    @Override
    public void close() throws IOException {
        System.out.println("资源关闭了!");
    }
}

字符流

在这里插入图片描述

FileReader

作用

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

使用
构造器说明
public FileReader**(File file)**创建字符输入流管道与源文件接通
public FileReader**(String pathname)**创建字符输入流管道与源文件接通
方法名称说明
public int read()每次读取一个字符返回,如果发现没有数据可读会返回-1.
public int read(char[] buffer)每次用一个字符数组去读取数据,返回字符数组读取了多少个字符,如果发现没有数据可读会返回-1.
import java.io.FileReader;
import java.io.Reader;

public class FileReaderDemo1 {
    public static void main(String[] args) {
        // 目标:掌握文件字符输入流读取字符内容到程序中来。
        try (
                // 1、创建文件字符输入流与源文件接通
                Reader fr = new FileReader("day03-file-io\\src\\dlei06.txt");
        ) {
            // 2、定义一个字符数组,每次读多个字符
            char[] chs = new char[3];
            int len; // 用于记录每次读取了多少个字符
            while ((len = fr.read(chs)) != -1){
                // 3、每次读取多个字符,并把字符数组转换成字符串输出
                String str = new String(chs,0,len);
                System.out.print(str);
            }
            // 拓展:文件字符输入流每次读取多个字符,性能较好,而且读取中文
            // 是按照字符读取,不会出现乱码!这是一种读取中文很好的方案。
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

FileWriter

作用

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

使用
构造器说明
public FileWriter(File file)创建字节输出流管道与源文件对象接通
public FileWriter(String filepath)创建字节输出流管道与源文件路径接通
public FileWriter(File file,boolean append)创建字节输出流管道与源文件对象接通,可追加数据
public FileWriter(String filepath,boolean append)创建字节输出流管道与源文件路径接通,可追加数据
方法名称说明
void write(int c)写一个字符
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分
void write(char[] cbuf)写入一个字符数组
void write(char[] cbuf, int off, int len)写入字符数组的一部分

字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效

方法名称说明
public void flush() throws IOException刷新流,就是将内存中缓存的数据立即写到文件中去生效!
public void close() throws IOException关闭流的操作,包含了刷新!

为什么要刷新
因为我们的数据先写入内存的缓存区,当我们要把数据输入到文件中需要刷新把数据存到文件中。
注意当我们调用close方法时会自动帮我们调用flush方法把数据存入文件中。

import java.io.FileWriter;
import java.io.Writer;

public class FileWriterDemo1 {
    public static void main(String[] args) {
        try (
                // 1. 创建一个字符输出流对象,指定写出的目的地。
//                Writer fw = new FileWriter("day03-file-io/src/dlei07-out.txt"); // 覆盖管道
                Writer fw = new FileWriter("day03-file-io/src/dlei07-out.txt", true); // 追加数据
                ){

            // 2. 写一个字符出去:  public void write(int c)
            fw.write('a');
            fw.write(98);
            fw.write('磊');
            fw.write("\r\n"); // 换行

            // 3、写一个字符串出去:  public void write(String str)
            fw.write("java");
            fw.write("我爱Java,虽然Java不是最好的编程之一,但是可以挣钱");
            fw.write("\r\n"); // 换行

            // 4、写字符串的一部分出去:  public void write(String str, int off, int len)
            fw.write("java", 1, 2);
            fw.write("\r\n"); // 换行

            // 5、写一个字符数组出去:  public void write(char[] cbuf)
            char[] chars = "java".toCharArray();
            fw.write(chars);
            fw.write("\r\n"); // 换行

            // 6、写字符数组的一部分出去:  public void write(char[] cbuf, int off, int len)
            fw.write(chars, 1, 2);
            fw.write("\r\n"); // 换行

//             fw.flush(); // 刷新缓冲区,将缓冲区中的数据全部写出去。
            // 刷新后,流可以继续使用。
            // fw.close(); // 关闭资源,关闭包含了刷新!关闭后流不能继续使用!

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

缓冲流

在这里插入图片描述

缓冲流有几种?

  1. 字节缓冲输入流: BufferedInputStream
  2. 字节缓冲输出流:BufferedOutputStream
  3. 字符缓冲输入流:BufferedReader
  4. 字符缓冲输出流:BufferedWriter

缓冲字节流

字节缓冲输入流: BufferedInputStream 继承FilterInputStream
字节缓冲输出流:BufferedOutputStream 继承FilterOutputStream

作用以及原理

作用:可以提高字节输入流读取数据的性能
原理:缓冲字节输入流自带了8KB缓冲池;缓冲字节输出流也自带了8KB缓冲池。

假设16KB 使用字节流需要16次读和16次写。而有了缓冲池就是装满8KB再去读或者写。
在这里插入图片描述
在这里插入图片描述

使用
构造器说明
public BufferedInputStream(InputStream is)把低级的字节输入流包装成一个高级的缓冲字节输入流,从而提高读数据的性能
public BufferedOutputStream(OutputStream os)把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能

使用之前的复制图片的例子为例。由于是继承那么之前的方法可以继续用。

import java.io.*;

public class CopyDemo1 {
    public static void main(String[] args) {
        copyFile("day-13\\src\\com\\hsh\\demo6copy\\oldImg\\1.jpg",
                "day-13\\src\\com\\hsh\\demo6copy\\newImg\\new.jpg");
    }

    // 复制文件
    public static void copyFile(String srcPath, String destPath)  {
        // 1、创建一个文件字节输入流管道与源文件接通
        try (
                // 这里只能放置资源对象,用完后,最终会自动调用其close方法关闭!!
                InputStream fis = new FileInputStream(srcPath);
                // 把低级的字节输入流包装成高级的缓冲字节输入流
                InputStream bis = new BufferedInputStream(fis);

                OutputStream fos = new FileOutputStream(destPath);
                // 把低级的字节输出流包装成高级的缓冲字节输出流
                OutputStream bos = new BufferedOutputStream(fos);
                ){
            // 2、读取一个字节数组,写入一个字节数组  1024 + 1024 + 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();
        }
    }
}

缓冲字符流

在这里插入图片描述

BufferedReader作用以及使用

作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能。
这个也是8KB的读
在这里插入图片描述

构造器说明
public BufferedReader(Reader r)把低级的字符输入流包装成字符缓冲输入流管道,从而提高字符输入流读字符数据的性能

字符缓冲输入流新增的功能:按照行读取字符

方法说明
public String readLine()读取一行数据返回,如果没有数据可读了,会返回null
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;

public class BufferedReaderDemo1 {
    public static void main(String[] args) {
        // 目标:搞清楚缓冲字符输入流读取字符内容:性能提升了,多了按照行读取文本的能力。
        try (
                // 1、创建文件字符输入流与源文件接通
                Reader fr = new FileReader("day03-file-io\\src\\hsh08.txt");
                // 2、创建缓冲字符输入流包装低级的字符输入流
                BufferedReader br = new BufferedReader(fr);
        ) {
//            // 2、定义一个字符数组,每次读多个字符
//            char[] chs = new char[1024];
//            int len; // 用于记录每次读取了多少个字符
//            while ((len = br.read(chs)) != -1){
//                // 3、每次读取多个字符,并把字符数组转换成字符串输出
//                String str = new String(chs,0,len);
//                System.out.print(str);
//            }

//            System.out.println(br.readLine());
//            System.out.println(br.readLine());
//            System.out.println(br.readLine());
//            System.out.println(br.readLine());
//            System.out.println(br.readLine());
//            System.out.println(br.readLine()); // null

            // 使用循环改进,来按照行读取数据。
            // 定义一个字符串变量用于记住每次读取的一行数据
            String line;
            while ((line = br.readLine()) != null){
                System.out.println(line);
            }
            // 目前读取文本最优雅的方案:性能好,不乱码,可以按照行读取。
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

BufferedWriter作用以及使用

作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能。
这个也是8KB的写
在这里插入图片描述

构造器说明
public BufferedWriter(Writer r)把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能

字符缓冲输出流新增的功能:换行

方法说明
public void newLine()换行
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;

public class BufferedWriterDemo1 {
    public static void main(String[] args) {
        // 目标:搞清楚缓冲字符输出流的使用:提升了字符输出流的写字符的性能,多了换行功能
        try (
                // 1. 创建一个字符输出流对象,指定写出的目的地。
//                Writer fw = new FileWriter("day03-file-io/src/hsh07-out.txt"); // 覆盖管道
                Writer fw = new FileWriter("day03-file-io/src/hsh07-out.txt", true); // 追加数据

                // 2. 创建一个缓冲字符输出流对象,把字符输出流对象作为构造参数传递给缓冲字符输出流对象。
                BufferedWriter bw = new BufferedWriter(fw);
        ){

            // 2. 写一个字符出去:  public void write(int c)
            bw.write('a');
            bw.write(98);
            bw.write('磊');
            bw.newLine(); // 换行

            // 3、写一个字符串出去:  public void write(String str)
            bw.write("java");
            bw.write("我爱Java,虽然Java不是最好的编程之一,但是可以挣钱");
            bw.newLine(); // 换行

            // 4、写字符串的一部分出去:  public void write(String str, int off, int len)
            bw.write("java", 1, 2);
            bw.newLine(); // 换行

            // 5、写一个字符数组出去:  public void write(char[] cbuf)
            char[] chars = "java".toCharArray();
            bw.write(chars);
            bw.newLine(); // 换行

            // 6、写字符数组的一部分出去:  public void write(char[] cbuf, int off, int len)
            bw.write(chars, 1, 2);
            bw.newLine(); // 换行

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

缓冲流案例

需求:把《出师表》的文章顺序进行恢复到一个新文件中。

分析:

  1. 定义一个缓存字符输入流管道与源文件接通。
  2. 定义一个List集合存储读取的每行数据。
  3. 定义一个循环按照行读取数据,存入到List集合中去。
  4. 对List集合中的每行数据按照首字符编号升序排序。
  5. 定义一个缓存字符输出管道与目标文件接通。
  6. 遍历List集合中的每个元素,用缓冲输出管道写出并换行。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BufferedTest2 {
    public static void main(String[] args) {
        // 目标:完成出师表的案例。
        try (
                // 1、创建一个字符缓冲输入流对象与源文件链接。
                BufferedReader br = new BufferedReader(new FileReader("day03-file-io\\src\\csb.txt"));
                // 6、创建一个字符缓冲输出流对象与目标文件链接。
                BufferedWriter bw = new BufferedWriter(new FileWriter("day03-file-io\\src\\csb_out.txt"));
        ) {
            // 2、提前准备一个List集合存储每段内容
            List<String> data = new ArrayList<>();

            // 3、按照行读取数据,存入到data集合中去
            String line;
            while ((line = br.readLine()) != null){
                data.add(line);
            }

            // 4、给集合中的每段内容,按照首字符排序
            Collections.sort(data);
            System.out.println(data);

            // 5、遍历集合,将每段内容写入到目标文件中。
            for (String s : data) {
                bw.write(s);
                bw.newLine(); // 换行
            }
            System.out.println("处理完毕!");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
csb.txt
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。

性能分析

原始流、缓冲流的性能分析[重点]

测试用例:

分别使用原始的字节流,以及字节缓冲流复制一个很大视频。

测试步骤:

  1. 使用低级的字节流按照一个一个字节的形式复制文件。
  2. 使用低级的字节流按照字节数组的形式复制文件。
  3. 使用高级的缓冲字节流按照一个一个字节的形式复制文件。
  4. 使用高级的缓冲字节流按照字节数组的形式复制文件。

推荐使用哪种方式提高字节流读写数据的性能?

建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。

并不是桶越大越好,桶越大占用内存多也会变慢。

import java.io.*;

public class TimeTest3 {
    private static final String SRC_FILE = "E:\\磊哥面授\\AI+Java基础加强课程\\day02-集合架构\\视频\\16、Stream流的终结方法.avi";
    private static final String DEST_FILE = "D:\\";
    public static void main(String[] args) {
        // 目标:缓冲流,低级流的性能分析。
        //使用低级的字节流按照一个一个字节的形式复制文件: 非常的慢,简直让人无法忍受,直接淘汰,禁止使用!!
        // copyFile1();
        //使用低级的字节流按照字节数组的形式复制文件: 是比较慢的,还可以接受。
        copyFile2();
        //使用高级的缓冲字节流按照一个一个字节的形式复制文件:虽然是高级管道,但一个一个字节的复制还是太慢了,无法忍受,直接淘汰!
        // copyFile3();
        //使用高级的缓冲字节流按照字节数组的形式复制文件: 非常快!推荐使用!
        copyFile4();
    }

    //使用高级的缓冲字节流按照字节数组的形式复制文件。
    private static void copyFile4() {
        long start = System.currentTimeMillis();
        try (
                InputStream fis = new FileInputStream(SRC_FILE);
                InputStream bis = new BufferedInputStream(fis);
                OutputStream fos = new FileOutputStream(DEST_FILE + "4.avi");
                OutputStream bos = new BufferedOutputStream(fos);
        ) {
            byte[] bytes = new byte[1024*32];
            int len;
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
        } catch (Exception e){
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("高级的缓冲字节流按照字节数组的形式复制文件,耗时:" + (end - start) / 1000.0 + "s");
    }

    //使用高级的缓冲字节流按照一个一个字节的形式复制文件。
    private static void copyFile3() {
        long start = System.currentTimeMillis();
        try (
                InputStream fis = new FileInputStream(SRC_FILE);
                InputStream bis = new BufferedInputStream(fis);
                OutputStream fos = new FileOutputStream(DEST_FILE + "3.avi");
                OutputStream bos = new BufferedOutputStream(fos);
        ) {
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("高级的缓冲字节流按照一个一个字节的形式复制文件,耗时:" + (end - start) / 1000.0 + "s");
    }

    //使用低级的字节流按照字节数组的形式复制文件。
    private static void copyFile2() {
        long start = System.currentTimeMillis();
        try (
                InputStream fis = new FileInputStream(SRC_FILE);
                OutputStream fos = new FileOutputStream(DEST_FILE + "2.avi");
        ) {
            byte[] bytes = new byte[1024*32];
            int len;
            while ((len = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, len);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("低级的字节流按照字节数组的形式复制文件,耗时:" + (end - start) / 1000.0 + "s");
    }

    //使用低级的字节流按照一个一个字节的形式复制文件。
    public static void copyFile1() {
        // 拿系统当前时间
        long start = System.currentTimeMillis(); // 此刻时间毫秒值: 从1970-1-1 00:00:00开始走到此刻的总毫秒值  1s = 1000ms
        try (
                InputStream fis = new FileInputStream(SRC_FILE);
                OutputStream fos = new FileOutputStream(DEST_FILE + "1.avi");
        ) {
            int b;
            while ((b = fis.read()) != -1) {
                fos.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();  // 此刻时间毫秒值: 从1970-1-1 00:00:00开始走到此刻的总毫秒值  1s = 1000ms
        System.out.println("低级字节流按照一个一个字节的形式复制文件,耗时:" + (end - start) / 1000.0 + "s");
    }
}

其他流

  1. 字符输入转换流 InputStreamReader
  2. 打印流
  3. 特殊数据流

字符输入转换流

在这里插入图片描述

InputStreamReader(字符输入转换流)作用

  1. 解决不同编码时,字符流读取文本内容乱码的问题。
  2. 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了。

当代码是UTF-8去读GBK文件

构造器说明
public InputStreamReader(InputStream is)把原始的字节输入流,按照代码默认编码转成字符输入流(与直接用FileReader的效果一样)
public InputStreamReader(InputStream is ,String charset)把原始的字节输入流,按照指定字符集编码转成字符输入流(重点)

演示乱码情况

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;

public class Demo1 {
    public static void main(String[] args) {
        // 目标:演示一个问题:不同编码读取乱码的问题、
        // 代码:UTF-8   文件 UTF-8  读取不乱码
        // 代码:UTF-8   文件 GBK  读取乱码

        try (
                // 1、创建文件字符输入流与源文件接通
                Reader fr = new FileReader("day03-file-io\\src\\dlei09.txt");
                // 2、创建缓冲字符输入流包装低级的字符输入流
                BufferedReader br = new BufferedReader(fr);
        ) {
            // 定义一个字符串变量用于记住每次读取的一行数据
            String line;
            while ((line = br.readLine()) != null){
                System.out.println(line);
                // 虽然是一行但是 编码不同会报错。
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

使用InputStreamReader指定编码读取。

import java.io.*;

public class Demo2 {
    public static void main(String[] args) {
        // 目标:使用字符输入转换流InputStreamReader解决不同编码读取乱码的问题、
        // 代码:UTF-8   文件 UTF-8  读取不乱码
        // 代码:UTF-8   文件 GBK  读取乱码
        try (
                // 先提取文件的原始字节流
                InputStream is = new FileInputStream("day03-file-io\\src\\dlei09.txt");
                // 指定字符集把原始字节流转换成字符输入流
                Reader isr = new InputStreamReader(is, "GBK");
                // 2、创建缓冲字符输入流包装低级的字符输入流
                BufferedReader br = new BufferedReader(isr);
        ) {
            // 定义一个字符串变量用于记住每次读取的一行数据
            String line;
            while ((line = br.readLine()) != null){
                System.out.println(line);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

打印流

在这里插入图片描述

PrintStream/PrintWriter(打印流)
作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。

PrintStream提供的打印数据的方案
构造器说明
public PrintStream(OutputStream/File/String)打印流直接通向字节输出流/文件/文件路径
public PrintStream(String fileName, Charset charset)可以指定写出去的字符编码
public PrintStream(OutputStream out, boolean autoFlush)可以指定实现自动刷新
public PrintStream(OutputStream out, boolean autoFlush, String encoding)可以指定实现自动刷新,并可指定字符的编码
方法说明
public void println**(**Xxx xx)打印任意类型的数据出去
public void write(int/byte[]/byte[]一部分)可以支持写字节数据出去
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;

public class PrintStreamDemo1 {
    public static void main(String[] args) {
        // 目标:打印流的使用。
       try (
//               PrintStream ps = new PrintStream("day03-file-io/src/ps.txt");
               PrintStream ps = new PrintStream(new FileOutputStream("day03-file-io/src/ps.txt", true));
//               PrintWriter ps = new PrintWriter("day03-file-io/src/ps.txt");
               ){
           ps.println(97); // 打印出来就是97 而不是a
           ps.println('a');
           ps.println("黑马");
           ps.println(true);
           ps.println(99.9);
       }catch (Exception e){
           e.printStackTrace();
       }
    }
}
PrintWriter提供的打印数据的方案
构造器说明
public Print****Writer(OutputStream/Writer/File/String)打印流直接通向字节输出流/文件/文件路径
public PrintWriter(String fileName, Charset charset)可以指定写出去的字符编码
public PrintWriter(OutputStream out/Writer, boolean autoFlush)可以指定实现自动刷新
public PrintWriter(OutputStream out, boolean autoFlush, String encoding)可以指定实现自动刷新,并可指定字符的编码
方法说明
public void println**(**Xxx xx)打印任意类型的数据出去
public void write(int/String/char[]/…)可以支持写字符数据出去
PrintStream和PrintWriter的区别

打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)
PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法。
PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。

打印流有几种?各有什么特点?

  • 打印流一般是指:PrintStream,PrintWriter两个类。
  • 打印功能2者是一样的使用方式
  • PrintStream继承自字节输出流OutputStream,支持写字节
  • PrintWrite继承自字符输出流Writer,支持写字符

打印流的优势是什么?

  • 两者在打印功能上都是使用方便,性能高效(核心优势)

特殊数据流

在这里插入图片描述

DataOutputStream(数据输出流)

允许把数据和其类型一并写出去。

构造器说明
public PrintWriter (OutputStream/Writer/File/String)打印流直接通向字节输出流/文件/文件路径
public PrintWriter(String fileName, Charset charset)可以指定写出去的字符编码
public PrintWriter(OutputStream out/Writer, boolean autoFlush)可以指定实现自动刷新
public PrintWriter(OutputStream out, boolean autoFlush, String encoding)可以指定实现自动刷新,并可指定字符的编码
方法说明
public void println(Xxx xx)打印任意类型的数据出去
public void write(int/String/char[]/…)可以支持写字符数据出去

在控制台的结果是乱码,这很正常,因为我们不管他内存中长什么样子,只要我们使用下一节的输出,能输出来就行。

import java.io.DataOutputStream;
import java.io.FileOutputStream;

public class DataStreamDemo2 {
    public static void main(String[] args) {
        // 目标:特殊数据流的使用。
       try (
               DataOutputStream dos = new DataOutputStream(new FileOutputStream("day03-file-io\\src\\data.txt"));
               ){
           dos.writeByte(34);
           dos.writeUTF("你好");
           dos.writeInt(3665);
           dos.writeDouble(9.9);
       }catch (Exception e){
           e.printStackTrace();
       }
    }
}
DataInputStream(数据输入流)

用于读取数据输出流写出去的数据。

构造器说明
public DataOutputStream(OutputStream out)创建新数据输出流包装基础的字节输出流
方法说明
public final void writeByte(int v) throws IOExceptionbyte类型的数据写入基础的字节输出流
public final void writeInt(int v) throws IOExceptionint类型的数据写入基础的字节输出流
public final void writeDouble(Double v) throws IOExceptiondouble类型的数据写入基础的字节输出流
public final void writeUTF(String str) throws IOException字符串数据以UTF-8编码成字节写入基础的字节输出流
public void write(int/byte[]/byte[]一部分)支持写字节数据出去

1、数据输入流和数据输出流分别是谁,各自的作用是啥?
DataInputStream、DataOutputStream
直接写数据和其类型出去

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class DataStreamDemo3 {
    public static void main(String[] args) {
        // 目标:特殊数据流的使用。
       try (
               DataInputStream dis = new DataInputStream(new FileInputStream("day03-file-io\\src\\data.txt"));
               ){
           System.out.println(dis.readByte());
           System.out.println(dis.readUTF());
           System.out.println(dis.readInt());
           System.out.println(dis.readDouble());
       }catch (Exception e){
           e.printStackTrace();
       }
    }
}

IO框架

什么是框架

框架(Framework)是一个预先写好的代码库或一组工具,旨在简化和加速开发过程
框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去。

和lombok不一样,lombok已经被idea集成在了一起,能直接导入直接用。
在这里插入图片描述

IO框架
封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。

如何下载和导入commons-io框架

  1. 搜索 commons-io下载
  2. Apache下面的
    在这里插入图片描述
  3. 解压,这个才是我们要的,文件夹docs是说明文档。
    在这里插入图片描述

导入commons-io-2.11.0.jar框架到项目中去。

  1. 在项目中创建一个文件夹:lib
  2. 将commons-io-2.6.jar文件复制到lib文件夹
  3. 在jar文件上点右键,选择 Add as Library -> 点击OK
  4. 在类中导包使用

Commons-io框架是什么

Commons-io是apache开源基金组织提供的一组有关IO操作的小框架,目的是提高IO流的开发效率。

FileUtils类提供的部分方法展示说明
public static void copyFile(File srcFile, File destFile)复制文件。
public static void copyDirectory(File srcDir, File destDir)复制文件夹
public static void deleteDirectory(File directory)删除文件夹
public static String readFileToString(File file, String encoding)读数据
public static void writeStringToFile(File file, String data, String charname, boolean append)写数据
IOUtils类提供的部分方法展示说明
public static int copy(InputStream inputStream, OutputStream outputStream)复制文件。
public static int copy(Reader reader, Writer writer)复制文件。
public static void write(String data, OutputStream output, String charsetName)写数据
import org.apache.commons.io.FileUtils;

import java.io.File;

public class CommonsIoDemo1 {
    public static void main(String[] args) throws Exception {
        // 目标:IO框架
        FileUtils.copyFile(new File("day-12\\src\\csb_out.txt"), new File("day-12\\src\\csb_out2.txt"));

        //JDK 7提供的
//        Files.copy(Path.of("day03-file-io\\src\\csb_out.txt"), Path.of("day03-file-io\\src\\csb_out3.txt"));


//        FileUtils.deleteDirectory(new File("E:\\resource\\图片服务器 - 副本 (2)"));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值