JavaEE之文件操作 字符集 IO流

File

引入

  • 存储数据的方案
  • 上面这些方式存储数据都是存储在内存条中
  • 缺点:这些数据容器都在内存中 ,一旦程序结束,或者断电,数据就没有了 ! 那么,如何永久的存储呢?采用磁盘文件存储
  • 文件可以长久保存数据。
  • 文件在电脑磁盘中保存,即便断电,或者程序终止,文件中的数据也不会丢失。
  • 那么,在jdk中如何操作磁盘文件呢?

介绍

  • File是java.io.包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)。
  • 作用
    • 获取文件信息 (大小,文件名,修改时间)
    • 判断文件的类型
    • 创建文件/文件夹
    • 删除文件/文件夹
    • 注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据。

API使用

  • File类的对象可以代表文件/文件夹,并可以调用其提供的方法对象文件进行操作。
创建File类的对象
构造器 说明
public File(String pathname) 根据文件路径创建File对象
public File(String parent, String child) 根据父路径和子路径名字创建File对象
public File(File parent, String child) 根据父路径对应File对象和子路径名字创建File对象
  • 注意
    • File对象既可以代表文件 、也可以代表文件夹
    • File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的。
绝对路径、相对路径
  • 绝对路径:从盘符开始
java 复制代码
File file1 = new File ("D:\itheima\a.txt"); 
  • 相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
java 复制代码
File file3 = new File ("模块名\a.txt"); 
其他API
  • File提供的判断文件类型、获取文件信息功能、创建文件和文件夹

    方法名称 说明
    public boolean exists() 判断当前文件对象,对应的文件路径是否存在,存在返回true
    public boolean isFile() 判断当前文件对象指代的是否是文件,是文件返回true,反之。
    public boolean isDirectory() 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
    public String getName() 获取文件的名称(包含后缀)
    public long length() 获取文件的大小,返回字节个数
    public boolean createNewFile() 创建一个新的空的文件
    public boolean mkdir() 只能创建一级文件夹
    public boolean mkdirs() 可以创建多级文件夹
  • 代码示例:

    java 复制代码
    package com.itheima._01File;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.Arrays;
    
    // 目标:创建File创建对象代表文件(文件/目录),搞清楚其提供的对文件进行操作的方法。
    // 1、创建File对象,去获取某个文件的信息(绝对路径)
    //绝对路径:D:\code\itheima200\javase-advance\day03-io-file\a.txt
    //在构造器中路径"\"需要写成"\",因为有特殊符号"\n",所以需要转义。"\n"代表路径,不代表换行
    //   如果是"/"不需要转义
    // 2、可以使用相对路径定位文件对象。并 file.length()输出字节数
    //相对路径:要从项目根目录下的模块名写起(推荐方式)
    // 3、exists()方法,创建文件对象,不存在的文件路径。
    // 4、创建文件 file.createNewFile();
    // 5、创建文件夹(目录)
    //   mkdir()创建一级目录
    //   mkdirs()创建多级目录
    // 6、判断File对象是文件isFile()还是文件夹isDirectory()。
    
    public class Demo011 {
    
        public static void main(String[] args) throws IOException {
            // 目标:创建File创建对象代表文件(文件/目录),搞清楚其提供的对文件进行操作的方法。
             // 1、创建File对象,去获取某个文件的信息(绝对路径)
             //绝对路径:D:\code\itheima204\javase-advance\day03-file-charactor-io\01.txt
             //在构造器中路径"\"需要写成"\"是为了转义告诉jvm不是格式化字符是路径,因为有特殊符号"\n"是格式化字符换行,"\n"代表路径,不代表换行
             
            File file1 = new File("D:\code\itheima204\javase-advance\day03-file-charactor-io\01.txt");
            System.out.println("绝对路径获取文件对象:"+file1);
            // 2、可以使用相对路径定位文件对象。并 file.length()输出字节数
            //相对路径:要从项目根目录下的模块名写起(推荐方式)
            
            File file2 = new File("day03-file-charactor-io/01.txt");
            System.out.println("相对路径获取文件对象:"+file2);
            System.out.println("文件字节数:"+ file2.length());
            //Ctrl+F12,用于快速查找当前类的所有成员,如果启用了Fn还需要Fn一起才可以。
            
             // 3、exists()方法,创建文件对象,不存在的文件路径。
            System.out.println(file2.getPath()+"是否存在:"+file2.exists());
            //true
            
             // 4、创建文件 file.createNewFile();
            File file6 = new File("day03-file-charactor-io/03.txt");
            file6.createNewFile();
            
            //注意:这里有编译时异常,抛出即可,因为jvm担心文件的父路径不存在就会抛出这个异常。
             // 5、创建文件夹(目录)
             // mkdir()创建一级目录
            File file7 = new File("day03-file-charactor-io/c");
            file7.mkdir();
            //   mkdirs()创建多级目录
            File file8 = new File("day03-file-charactor-io/d/01/02");
            file8.mkdirs();
            // 6、判断File对象是文件isFile()还是文件夹isDirectory()。
            System.out.println("day03-file-charactor-io/c是否是目录:"+file7.isDirectory());
            System.out.println("day03-file-charactor-io/03.txt是否是文件:"+file6.isFile());
        }
    }
递归查找文件
  • 在磁盘目录中查找文件,父路径文件不存在,递归进入子路径查找文件

    java 复制代码
    import java.io.File;
    
    public class SearchRecursion {
        public static void main(String[] args) {
            if (searchFile(new File("E:\CodingProgramsForE\Microsoft VS Code"),"Code.exe")){
                System.out.println("文件已找到!");
            }else{
                System.out.println("没有找到文件");
            }
    
        }
    
        private static boolean searchFile(File dir, String fileName) {
            // 判断目录是否有效
            // 分析: 对象为空,目录不存在,对象为文件,无法进入搜索
            // 健壮性
            boolean flag = false;
            if (dir == null || fileName == null || !dir.exists() || dir.isFile())return flag;
    
    
            //获取当前目录的一级子文件或文件夹
            File[] files = dir.listFiles();
            if (files == null || files.length == 0) return flag;
    
            // 遍历文件或文件夹,并将数组的每个元素进行匹配
            for (File file : files) {
                if (file.isFile()){
                    if (file.getName().equals(fileName)){
                        System.out.println("路径:" + file.getAbsoluteFile());
                        // 找到文件就打印文件的绝对路径
                        flag = true;
                    }else{
                        // 不是文件就进行递归,进入文件夹查询文件
                        searchFile(file,fileName);
                    }
                }
            }
            return flag;
        }
    }

字符集

常见字符集

标准ASCII字符集

  • ASCII(American Standard Code for Information Interchange): 美国信息交换标准代码,包括了英文、符号等。
  • 标准ASCII使用1个字节存储一个字符,首位是0,因此,总共可表示128个字符,对美国人来说完全够用。 只有1个字节,最多2的8次幂0~255共256个字符,上面已经使用了128,剩余128个描述汉字字符根本不够塞牙缝的,所以国人制作了GBK字符集。

GBK( 汉字内码扩展规范,国标 )

  • 汉字编码字符集,包含了2万多个汉字等字符,GBK中一个中文字符编码成两个字节的形式存储。
  • 注意:GBK兼容了ASCII字符集。
    • GBK规定:汉字的第一个字节的第一位必须是1
    • ASCII规定:字节的第一位必须是0
    • 例子:"我a你" 产生的GBK字节数据如下
  • 其实还有其他国家的字符集,还有很多,我们到底使用哪个字符集呢?

UTF-8字符集(万国网)

  • 是Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节,不同的语言占用不同的字节数,其中中文被分配到了3个字节的区域
  • 英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节。

IDEA统一设置字符集

编码与解码

介绍

  • 为什么要编码与解码?因为数据从一个软件传输给另一个软件会经历编码与解码
  • 软件A会采用指定字符集将字符串编码传输数据给软件B,软件B按照指定相同字符集解码就可以看到字符串数据
  • 容易产生疑问的点:
    • 疑问:为什么不同软件传递数据不是原始传递是编码后传递的?因为速度更快
    • 疑问:为什么解码的时候明明文件是UTF-8,采用GBK接收并解码使用GBK编码的数据不会乱码?这是一个非常容易无解的点,编码和解码就像是两个人约定好了摩斯密码打电话,接收解码后就变为了字符串对象,进行输出是打印函数sout会将字符串按照文件当前的编码输出
  • 注意:字符编码时使用的字符集,和解码时使用的字符集必须一致 ,否则会出现乱码,即为用gbk编码,那么解码函数必须也指定gbk进行解码,和文件的默认字节码无关,解码和编码前都是String字符串

api语法

  • 对字符的编码
String提供了如下方法 说明
byte[] getBytes( ) 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes(String charsetName) 使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
  • 对字符的解码
String提供了如下方法 说明
String(byte[] bytes) 通过使用平台的默认字符集解码指定的字节数组来构造新的 String
String(byte[] bytes, String charsetName) 通过指定的字符集解码指定的字节数组来构造新的 String
java 复制代码
public class Demo021 {

    public static void main(String[] args) throws Exception {
        //1.字符集不一致会乱码
        //编码采用GBK
        String str="黑马";
        byte[] bytes = str.getBytes("GBK"); 
        //编码:将字符串转换为字节数组
        //解码采用UTF-8
        String str2 = new String(bytes, "UTF-8"); 
        //解码:将字节数组转换为字符串原始数据
        System.out.println(str2);

        //2.字符集一致不会乱码
        //编码采用UTF-8
        String str3="黑马";
        // byte[] bytes2 = str.getBytes("UTF-8");//编码:将字符串转换为字节数组
        byte[] bytes2 = str3.getBytes(); 
        //不用设置字符集,默认就是UTF-8,因为全局已经设置为UTF-8
         //解码采用UTF-8
        String str4 = new String(bytes2, "UTF-8"); 
        //解码:将字节数组转换为字符串原始数据
        System.out.println(str4);
    }
}

IO流

认识IO流

I是什么,O是什么

  • I指的是Input,称为输入流:负责把数据读到内存中去
  • O指的是Output,称为输出流,负责写数据出去
  • 从计算机组成原理的角度来说流就是软件寻址操作内存通过总线(BUS)和磁盘做读写交互的过程 ,故流是一种总线资源 ,需要通过申请 获得,使用完毕后要释放 资源,否则总线资源被占满导致死机等问题。

IO应用场景

  • 所有需求数据永久存储,后续重启程序再次使用都要使用IO流与磁盘文件进行数据读写操作

IO流分类

  • 按照流的方向:
    • 输入流(Input):从内存流向磁盘
    • 输出流(Output):从磁盘流向内存
  • 按照流的内容:
    • 字节流:一个一个字节读写,比如中文一个字符三个字节,则需要读三次
    • 字符流:一个一个字符读写
  • 按照使用方式:
    • 节点流(基本流):又称低级流、原始流,可以直接使用,直接与数据源连接
    • 处理流(包装流):又称高级流、包装流、装饰流,必须嵌套在其他流上,增强已有流的功能
  • 各种类型实际在原生Java类中的位置可以概括为

字节流

  • 字节流:一个一个字节读写,比如中文一个字符三个字节,则需要读三次
    • 因为在字节码中英文和中文的字长不同,比如在utf-8中中文占三个字节,英文占一个字节,所以字节流在读取时会出现像中文这种占三个字节的字符会无法一次性完全读取的情况出现解析读取的字节时候会导致乱码 ,所以用字节流读取文件是相对不安全的IO流,不建议字节流读取并解析文件时使用。
    • 虽然一次性用字符流读取磁盘文件中的全部字符可以解决中文乱码,但这种做法是最危险的,因为你不确定你的文件大小是多大,一但读取的文件过大,其内容占满堆空间,会造成内存溢出,在生产环境中严禁使用。
    • 因为按照字节读取相比按照字符读取在底层代码中少了许多判断,所以按照字节流读写效率较高,在进行文件剪切,复制/剪切到另一个存储空间,建议使用字节流(因为这不涉及读取并解析文件)

字节流输入流

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

IO流体系中的位置
语法
  • 创建字节流对象,可以通过File对象和文件路径进行创建

    构造器 说明
    public FileInputStream(File file) 传入File对象创建字节输入流管道与源文件接通
    public FileInputStream(String pathname) 传入文件路径创建字节输入流管道与源文件接通
  • 进行读操作

    方法名称 说明
    public int read() 每次读取一个字节返回,如果发现没有数据可读会返回-1
    public int read(byte[] buffer) 每次用一个字节数组去读取数据,返回字节数组读取了多少个字节,如果发现没有数据可读会返回-1
    public byte[] readAllBytes() 一次性读取全部磁盘文件中的所有字符(危险!!!严禁在生成环境使用)
  • 创建一个文件用于操作举例,命名demo01.txt

    txt 复制代码
    abc你好a
  • 代码示例

    java 复制代码
    package com.itheima._03字节流;
    import java.io.FileInputStream;
    import java.io.InputStream;
    public class Demo031 {
    
        public static void main(String[] args) throws Exception {
        //目标:采用字节输入流读取数据, 读取dmeo1.txt文件,内容"abc你好a"
         //1.建立输入流对象
        InputStream in = new FileInputStream("file-charactor-io/demo1.txt");
        //2.读取数据
         //读取1个字节
        int data1 = in.read();
        System.out.println((char)data1); //a
        int data2 = in.read();
        System.out.println((char)data2); //b
        int data3 = in.read();
        System.out.println((char)data3); //c
         // int data4 = in.read();
         // System.out.println((char)data4);
         // é,由于"你"3个字节,这里只读1个字节就会不正确,出现乱码
         // 注意:字节流不适合读取字符文本文件
    
         //读取1个字节数组(多个字节)
         byte[] bytes = new byte[3];
         int len = -1;
    
         while((len=in.read(bytes)!=-1){
            // System.out.print(new String(bytes));
            // 因为每次都是用新读到的数据覆盖旧数据在同一个数组对象中
            // 所以如果最后读的数据大小没有三个字节就结束了,最后一次循环只读取了一个字节
            // 那么就只会在数组的第一位写入,后两位则是上一次读取的残留
            // 比如读取"abc你好abca",会分别读出"abc" "你" "好" "abc" "abc"
            // 解决方案为用一个变量len记录读取到的数据字节长度,并在将字节数组转换为字符串时限制截至位置
            System.out.print(new String(bytes,0,len));
        }
        //3.关闭资源
        in.close(); 
        //流是一种资源,使用完后要关闭,否则会占用资源别人无法使用。
        }
    }
    • 流是一种总线BUS资源,需要申请和释放
    • 即使我们有循环读取这一方案但仍然无法避免中英文占用字节数不同引发的数据断裂,在字节流中唯一能解决的方案就是一次性读取所有内容,但这在生产环境是不被允许的,因为你无法知道你读取的文件会有多大,一旦过大会造成内存溢出(不能一口吃成大胖子)。
    java 复制代码
    public byte[] readAllBytes()

字节流输出流

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

IO流体系中的位置
语法
  • 创建字节输出流对象

    构造器 说明
    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 关闭流。
代码演示
java 复制代码
package com.itheima._03字节流;

import java.io.FileOutputStream;
import java.io.OutputStream;

//目标:使用字节输出流输出数据到目标文件 demo2.txt //1.创建文件输出流对象
//2.输出数据
//输出一个字节
//输出字节数组全部数据
//输出字节数组一部分数据
//3.关闭资源

public class Demo032 {

    public static void main(String[] args) throws Exception {
        //目标:使用字节输出流输出数据到目标文件 demo2.txt
        //1.创建文件输出流对象
        //采用覆盖方式输出数据
        // OutputStream out = new FileOutputStream("day03-file-charactor-io/demo2.txt");
        //采用追加方式输出数据
        OutputStream out = new FileOutputStream("day03-file-charactor-io/demo2.txt",true); 
   
        out.write("\n".getBytes());// 输出换行
        //2.输出数据
        //输出一个字节
        out.write(97); //a
        //输出字节数组全部数据
        String str = "黑马";
        byte[] bytes = str.getBytes(); //6个字节,因为1个汉字是3个字节
        out.write(bytes);

        //输出字节数组一部分数据
        // 0 指定从字节数字下标多少开始输出
        // 3 指定输出字节的个数
        out.write(bytes,0,3); //输出一个字"黑"
        
        //3.关闭资源
        out.close();

    }
}
字节流案例:文件复制
  • 需求
  • 代码实现,读的同时进行写操作(输入-输出)
java 复制代码
public class Demo033 {

    public static void main(String[] args)throws Exception {
        //目标:文件复制,将 b/1.png 复制到 c/1.png

        //1.创建输入流对象用于连接源文件连接通道
        InputStream in = new FileInputStream("day03-file-charactor-io/b/01.txt");

        //2.创建输出流对象用于连接目标文件连接通道
        OutputStream out = new FileOutputStream("day03-file-charactor-io/c/01.txt");

        //3.从源文件读取数据写入到目标文件中
        //分析:可以循环读取,每次读取1个字节或多个字节,直到读取完成
        //in.read(bytes) 返回读取到的字节个数,如果返回个数为-1代表读完了,没有数据了
        //疑问:循环每次读取一个字节好不好?答:不好,太慢了,不推荐,推荐使用字节数组多字节循环读取并写出
        //定义字节数组,长度1024个字节
        byte[] bytes = new byte[1024];
        //定义读取字节个数变量
        int length = -1;
        //循环读取,直到读到的字节个数为-1结束
        while ((length = in.read(bytes))!=-1) {
        // length = in.read(bytes);
        // if(length==-1){
        //     break;
        // }
        //读取的数据通过输出流输出到目标文件中
        // out.write(bytes); 这个不推荐,因为循环最后一次读取可能读取的字节数装不满这个数字,导致字节数组中遗留了上次读取的数据
        out.write(bytes,0,length); //推荐,读多少写出多少
        }
        //4.关闭资源(注意:关闭顺序和创建的顺序的正好相反)
        out.close();
        in.close();
        System.out.println("文件复制完成");
   }
}

专业级IO资源释放方案

  • 当我们生成一个IO流对象时候,根据计算机组成原理的知识点可知,相当于在总线BUS申请了一部分物理资源,如果在使用完成后不及时释放,会导致资源泄露,会引发整个计算机系统不稳定甚至死机,所以我们要确保无论是否成功执行IO流的读写操作,都要释放IO资源,这个时候就需要用到异常捕获了
  • 在释放资源时要遵循,释放顺序和申请的顺序的正好相反,因为后申请的资源可能依赖于先创建的资源

  • 这里就是没有处理异常的做法,非常不推荐

    java 复制代码
    public class Demo034 {
    
        public static void main(String[] args)throws Exception {
             //目标:演示资源不能正确关闭的问题
    
             //1.创建输入流对象用于连接源文件连接通道
            InputStream in = new FileInputStream("day03-file-charactor-io/b/01.txt");
    
            //2.创建输出流对象用于连接目标文件连接通道
            OutputStream out = new FileOutputStream("day03-file-charactor-io/c/01.txt");
    
            //3.从源文件读取数据写入到目标文件中
            byte[] bytes = new byte[1024];
            //定义读取字节个数变量
            int length = -1;
    
            int a = 1/0; //这里会发生异常,导致后面关闭释放资源失败
    
             //循环读取,直到读到的字节个数为-1结束
            while ((length = in.read(bytes))!=-1) {
                        out.write(bytes,0,length); //推荐,读多少写出多少
            }
            //4.关闭资源(注意:关闭顺序和创建的顺序的正好相反,因为后创建的资源依赖于先创建的资源)
            out.close();
            in.close();
            System.out.println("文件复制完成");
            }
        }
  • jdk8之前的做法

    java 复制代码
    package com.itheima._03字节流;
    import java.io.*;
    public class Demo035 {
    
    public static void main(String[] args)throws Exception {
        //目标:演示使用专业释放资源方式1 try-catch-finally
    
        //1.创建输入流对象用于连接源文件连接通道
        InputStream in = null;
    
        //2.创建输出流对象用于连接目标文件连接通道
        OutputStream out = null;
    
        //ctrl+alt+T 先选中代码,然后使用这个快捷键快速生成try-catch-finally
        try {
                in = new FileInputStream("day03-file-charactor-io/b/01.txt");
                out = new FileOutputStream("day03-file-charactor-io/c/01.txt");
                //3.从源文件读取数据写入到目标文件中
                byte[] bytes = new byte[1024];
                //定义读取字节个数变量
                int length = -1;
                int a = 1/0; //这里会发生异常,导致后面关闭释放资源失败
    
                //循环读取,直到读到的字节个数为-1结束
                while ((length = in.read(bytes))!=-1) {
                    out.write(bytes,0,length); //推荐,读多少写出多少
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                //finally作用:无论try里面是否发生异常,最终都会执行finally代码块,推荐在这里释放资源,资源可以保证释放
                //4.关闭资源(注意:关闭顺序和创建的顺序的正好相反) 
                if(out!=null) out.close();
                if(in!=null) in.close();
            }
    
        System.out.println("文件复制完成");
        }
    }
  • jdk8之后的做法,使用Try-With,可以理解为在try-catch作用于码块中申请的资源,使用完毕就依次释放

    java 复制代码
     public class Demo036 {
    
         public static void main(String[] args)throws Exception {
             //目标:演示使用专业释放资源方式2 try(资源){}catch(){} jdk8的新特性
    
             //这种方式会自动调用close方法, 前提资源必须实现了Closeable接口
             try(
             //1.创建输入流对象用于连接源文件连接通道
             InputStream in = new FileInputStream("day03-file-charactor-io/b/01.txt");
             //2.创建输出流对象用于连接目标文件连接通道
             OutputStream out = new FileOutputStream("day03-file-charactor-io/c/01.txt");
              ) {
    
                 //3.从源文件读取数据写入到目标文件中
                 byte[] bytes = new byte[1024];
                 //定义读取字节个数变量
                 int length = -1;
    
                 int a = 1/0; //这里会发生异常,导致后面关闭释放资源失败
    
                  //循环读取,直到读到的字节个数为-1结束
                 while ((length = in.read(bytes))!=-1) {
                     out.write(bytes,0,length);
                     //推荐,读多少写出多少
                 }
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
    
             System.out.println("文件复制完成");
         }
     }

字符流

  • 以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。
  • 字符流:一个一个字符读写
    • 因为字符流按照字符来读写磁盘中的数据,所以读写效率较低,但因为是按照字符读取,解析时不会出现乱码,所以是相对安全的IO流,通常用来读取会在代码中解析的文件。
    • 虽然也可以用字符流复制剪切文件,但因为是按照字符读取,所以需要对字符占用的自己数进行判断,故效率较低,不建议用于复制剪切等对文件操作的业务场景。

字符输入流

  • 以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。
IO流体系
语法
  • 创建字符输入流对象
构造器 说明
public FileReader​(File file) 创建字符输入流管道与源文件接通
public FileReader​(String pathname) 创建字符输入流管道与源文件接通
  • 读操作
方法名称 说明
public int read() 每次读取一个字符返回,如果发现没有数据可读会返回-1.
public int read(char[] buffer) 每次用一个字符数组去读取数据,返回字符数组读取了多少个字符,如果发现没有数据可读会返回-1.
代码
java 复制代码
package com.itheima._04字符流;

import java.io.FileReader;
import java.io.Reader;
public class Demo041 {

    public static void main(String[] args) {
        //目标:使用字符输入流读取文件数据demo1.txt
        //1.创建输入流对象
        try(
            Reader r = new FileReader("day03-file-charactor-io/demo1.txt")
        ){

            //2.读取数据
            //读取一个字符
            int data1 = r.read();
            System.out.println((char)data1); //a
            data1 = r.read();
            System.out.println((char)data1); //b
            data1 = r.read();
            System.out.println((char)data1); //c
            data1 = r.read();
            System.out.println((char)data1); //黑

            //读取一个字符数组
            char[] chars = new char[10];
            int len = -1;
            while((len = in.read(chars)) != -1){
                System.out.print(new String(chars,0,len));
            }

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

字符输出流

  • 以内存为基准,把内存中的数据以字符的形式写出到文件中去
IO流体系
语法
  • 创建字符输入流对象
构造器 说明
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 关闭流的操作,包含了刷新!
代码
java 复制代码
import java.io.FileWriter;
import java.io.Writer;
public class Demo042 {

    public static void main(String[] args) throws Exception {
        // 目标:使用字符输出流输出数据到目标文件 demo3.txt
        try (
            // 1.创建文件输出流对象
            Writer out = new FileWriter("day03-file-charactor-io/demo3.txt"); 
            // 采用覆盖方式输出数据

        ) {
            // 2.输出数据
            // 输出一个字符
            out.write(97); //a
            // 输出一个字符串
            out.write("\nhello");
            // 输出一个字符串的一部分
            out.write("黑马程序员",2,2);
            // 输出一个字符数组
            char[] chars = {'a','黑','d'};
            out.write(chars);  
            out.flush();
            // 刷新流。确保数据羞辱到文件(字符输出流可能有部分数据缓存没有写入文件,这时候要刷新流或关闭流)
            // 输出一个字符数组的一部分
            out.write("\r\n");
            out.write(chars,0,2);
        } catch (Exception ex) {
            ex.printStackTrace();
        }


    }
}

缓冲流

缓冲字节流

  • 作用:可以提高字节流读写数据的性能
  • 原理:缓冲字节输入流自带了8KB缓冲池;缓冲字节输出流也自带了8KB缓冲池。
IO流体系
缓冲字节流构造器
构造器 说明
public BufferedInputStream​(InputStream is) 把低级的字节输入流包装成一个高级的缓冲字节输入流,从而提高读数据的性能
public BufferedOutputStream​(OutputStream os) 把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能
代码示例
java 复制代码
import java.io.*;
public class Demo051 {

    public static void main(String[] args) {
    //目标:会使用高级流字节缓冲流文件复制, 缓冲流自带了8KB的缓冲池

        try(
            //1.创建输入流对象用于连接源文件连接通道
            InputStream in = new FileInputStream("day03-file-charactor-io/b/01.txt");
            //2.创建输出流对象用于连接目标文件连接通道
            OutputStream out = new FileOutputStream("day03-file-charactor-io/c/01.txt");
            //3.创建高级流输入流
            BufferedInputStream bi = new BufferedInputStream(in);
            //4.创建高级流输出流
            BufferedOutputStream bo = new BufferedOutputStream(out);
            ) {

            //5.从源文件读取数据写入到目标文件中
            byte[] bytes = new byte[1024];
            //定义读取字节个数变量
            int length = -1;

            //循环读取,直到读到的字节个数为-1结束
            while ((length = bi.read(bytes))!=-1) {
                bo.write(bytes,0,length); 
                //推荐,读多少写出多少
            }
            System.out.println("文件复制完成");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

缓存字符流

缓冲字符输入流
  • 作用:自带8K(8192)的字符缓冲池,可以提高字符输入流读取字符数据的性能

  • 语法

    构造器 说明
    public BufferedReader​(Reader r) 把低级的字符输入流包装成字符缓冲输入流管道,从而提高字符输入流读字符数据的性能
  • 字符缓冲输入流新增的功能:按照行读取字符

    方法 说明
    public String readLine() 读取一行数据返回,如果没有数据可读了,会返回null
缓冲字符输出流
  • 作用:自带8K的字符缓冲池,可以提高字符输出流写字符数据的性能。

  • 语法

    构造器 说明
    public BufferedWriter​(Writer r) 把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能
  • 字符缓冲输出流新增的功能:换行;目的是为了能适配不同操作系统的文本规范(不同操作系统的换行符不同)

    方法 说明
    public void newLine() 换行
代码示例
java 复制代码
import java.io.*;
public class Demo052 {

    public static void main(String[] args) {
        //目标:会使用高级流字符缓冲流文件复制, 缓冲流自带了8KB的缓冲池

        try(
            //1.创建输入流对象用于连接源文件连接通道
            Reader in = new FileReader("day03-file-charactor-io/b/01.txt");
            //2.创建输出流对象用于连接目标文件连接通道
            Writer out = new FileWriter("day03-file-charactor-io/c/01.txt");
            //3.创建高级流输入流
            BufferedReader bi = new BufferedReader(in,1024*16);
            //4.创建高级流输出流
            BufferedWriter bo = new BufferedWriter(out,1024*16); 
            //自定义缓存池大小,默认8KB,这里是16KB
        ) {

             //5.从源文件读取数据写入到目标文件中
             //方式1
             // char[]  chars = new char[1024];
             // //定义读取字节个数变量
             // int length = -1;
             //
             // //循环读取,直到读到的字节个数为-1结束
             // while ((length = bi.read(chars))!=-1) {
             //     bo.write(chars,0,length);//推荐,读多少写出多少
             // }
             //方式2:每次读取一行数据,读不到为null
            String data = null;
            while((data = bi.readLine())!=null){
                bo.write(data);
                //注意:每次输出的数据没有换行
                // bo.write("\n");//换行方式1
                bo.newLine(); //换行方式2
            }
            System.out.println("文件复制完成");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

原始流、缓冲流的性能分析(重要)

  • 测试用例:

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

    • 使用低级的字节流按照一个一个字节的形式复制文件。
    • 使用低级的字节流按照字节数组的形式复制文件。
    • 使用低级的字节流按照字节数组的形式复制文件。
    • 使用高级的缓冲字节流按照字节数组的形式复制文件。
  • 测试代码

    java 复制代码
    import java.io.*;
    
    public class Demo {
        private static final String SRC_FILE = "F:\微力云共享文件夹1\02-JavaEE加强内容\day03-FIle、字符集、IO流\视频\20-IO流:字符流输出流.wmv";
        private static final String DEST_FILE = "";
    
        public static void main(String[] args) {
    
            copy01();// 低级的字节流一个一个字节复制;慢的简直无法忍受,直接淘汰,禁止使用;
            copy02();// 使用低级的字节流按照字节数组的形式复制文件;相对较慢!可以接收
            copy03();// 使用高级的缓冲字节流按照一个一个字节的形式复制文件;超级慢,直接淘汰
            copy04();// 使用高级的缓冲字节流按照字节数组的形式复制文件;较快,推荐使用
        }
    
        public static void copy01() {
            long start = System.currentTimeMillis();//开始时间:返回的是1970-1-1 00:00:00 走到此刻的总毫秒数
            try (FileInputStream fis = new FileInputStream(SRC_FILE);
                 FileOutputStream fos = new FileOutputStream(DEST_FILE + "1.wmv")) {
                 int b;
                 while ((b = fis.read()) != -1) {
                     fos.write(b);
                 }
            } catch (Exception e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();// 结束时间
            System.out.println("低级的字节流一个一个字节的形式复制文件耗时:" + (end - start) + "毫秒!");
        }
    
        public static void copy02() {
            long start = System.currentTimeMillis();
            try (FileInputStream fis = new FileInputStream(SRC_FILE);
                 FileOutputStream fos = new FileOutputStream(DEST_FILE+"2.wmv")) {
                 byte[] buffer = new byte[1024];
                 int len;
                 while ((len = fis.read(buffer)) != -1) {
                     fos.write(buffer, 0, len);
                 }
            } catch (Exception e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("低级的字节流按照字节数组的形式复制文件耗时:" + (end - start) + "毫秒!");
        }
        public static void copy03() {
            long start = System.currentTimeMillis();
            try (InputStream is = new FileInputStream(SRC_FILE);
                 BufferedInputStream bis = new BufferedInputStream(is);
                 FileOutputStream os = new FileOutputStream(DEST_FILE + "3.wmv");
                 BufferedOutputStream bos = new BufferedOutputStream(os);
            ) {
                int b;
                while ((b = bis.read()) != -1) {
                    bos.write(b);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("高级的缓冲字节流按照字节数组的形式复制文件耗时:" + (end - start) + "毫秒!");
        }
    
        public static void copy04(){
            long start = System.currentTimeMillis();
            try (InputStream is = new FileInputStream(SRC_FILE);
                 BufferedInputStream bis = new BufferedInputStream(is);
                 FileOutputStream os = new FileOutputStream(DEST_FILE + "4.wmv");
                 BufferedOutputStream bos = new BufferedOutputStream(os);
            ) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = bis.read(buffer)) != -1) {
                    bos.write(buffer, 0, len);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("高级的缓冲字节流按照字节数组的形式复制文件耗时:" + (end - start) + "毫秒!");
        }
    }
  • 运行结果

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

  • 除了流和收集流的方式不同的组合方案对读写速度会有影响,在最好的缓冲流结合数组收集的方案中自定义数组的大小也会影响性能的好坏,需要在数组大小和IO资源消耗中找到平衡才能找到最佳的优化方案。

    • 测试代码,和运行结果如下
    • 分析性能
    • 原理分析:可以把磁盘看作桶,数组看作碗,用碗从桶里面盛饭到内存中,碗太小要跑几千趟才能盛满一碗,大部分时间花在路上了,太慢! ;太大,确实跑得少,但端着大碗容易手酸(撑爆内存/总线IO压力 ),反而卡顿;刚刚好, 能减少路途奔波,又不会压垮系统,效率最高

字符输入转换流

  • 用于解决不同编码时,字符流读取文本内容乱码的问题。

  • 在IO流体系中的位置

  • 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了

    构造器 说明
    public InputStreamReader(InputStream is) 换行
    public InputStreamReader(InputStream is ,String charset) 把原始的字节输入流,按照指定字符集编码转成字符输入流(重点)
  • 代码示例

    java 复制代码
    import java.io.*;
    public class Demo092 {
        public static void main(String[] args) {
            //目标:使用字符输入转换流指定码表读取文件
            //当前文件 Demo091.java (utf-8) 使用的 指明以GBK码表读取at.txt (GBK) 文件不会乱码
            try (
                //1.定义字节输入流(原始流,里面的字节没有被破坏)
                InputStream inputStream = new FileInputStream("day03-io-file\\a6.txt");
                //2.定义字符输入转换流
                Reader r = new InputStreamReader(inputStream, "GBK");
                //3.缓冲流提供性能
                BufferedReader br = new BufferedReader(r);
            ) {
                char[] chs = new char[1024];
                int len = -1;
                while ((len = br.read(chs)) != -1) {
                    System.out.println(new String(chs, 0, len));
                }
            } catch (Exception e) {
                 e.printStackTrace();
            }
        }
    }

打印流

  • 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去

在IO流体系中的位置

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[]一部分) 可以支持写字节数据出去
  • 代码

    java 复制代码
    public class Demo101 {
    
        public static void main(String[] args) {
            //目标:使用打印字节流PrintSteam输出数据
            //作用:更加高效,更加方便,打印啥就输出啥
    
            try(
                    //1.定义输出流(注意不要多态,否则无法使用独有方法)
                    PrintStream printStream = new PrintStream("a.txt")
            ){
    
                printStream.println(97);
                printStream.println(true);
                printStream.println(66.6);
                printStream.println('a');
    
            }catch(Exception e){
                e.printStackTrace();
            }
    
        }
    }
  • PrintStream是继承于OutputStream的处理流,虽然可以直接和IO资源建立连接,但其实是因为其内部有语法糖直接创建了FileOutputStream节点流对象

  • PrintStream继承自字节输出流OutputStream,支持写字节

PrintWriter提供的打印数据的方案

  • 语法

    构造器 说明
    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[]/..) 可以支持写字符数据出去
  • 代码

    java 复制代码
    package src;
    import java.io.PrintWriter;
    public class Demo101{
        public static void main(String[] args) {
            //目标:使用打印字符流PrintWriter输出数据(tomcat (web) 服务器响应数据就是使用这个输出流)
            //作用:更加高效,更加方便,打印啥就输出啥
            try(
                    //1.定义输出流(注意不要多态,否则无法使用独有方法)
                    PrintWriter printWriter = new PrintWriter("day03-io-file\a8.txt")
            ){
                printWriter.println(97);
                printWriter.println(true);
                printWriter.println(66.6);
                printWriter.println('a');
    
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
  • PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。

  • 虽然可以直接和IO资源建立连接,但其实是因为其内部有语法糖直接创建了Filewirter节点流对象

特殊数据流

  • 特殊数据流:允许把数据和其类型一并读写。
  • 在IO中的位置

DataOutputStream(数据输出流)

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

  • 语法

    • 构造器

      构造器 说明
      public DataOutputStream(OutputStream out) 创建新数据输出流包装基础的字节输出流
    • 方法

      方法 说明
      public final void writeByte(int v) throws IOException 将byte类型的数据写入基础的字节输出流
      public final void writeInt​(int v) throws IOException 将int类型的数据写入基础的字节输出流
      public final void writeDouble(Double v) throws IOException 将double类型的数据写入基础的字节输出流
      public final void writeUTF(String str) throws IOException 将字符串数据以UTF-8编码成字节写入基础的字节输出流
      public void write(int/byte[]/byte[]一部分) 支持写字节数据出去
  • 代码示例

    java 复制代码
    import java.io.DataOutputStream;
    import java.io.FileOutputStream;
    import java.io.OutputStream;
    public class Demo111 {
        public static void main(String[] args) {
            //目标:使用特殊数据输出流写出数据和数据类型
            try(
                    //1.定义资源
                    OutputStream out = new FileOutputStream("./a9.txt");
                    //2.特殊数据输出流
                    DataOutputStream dataOutputStream = new DataOutputStream(out)
            ){
                dataOutputStream.writeInt(100);
                dataOutputStream.writeBoolean(true);
                dataOutputStream.writeDouble(66.6);
                dataOutputStream.writeUTF("程序员");
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
  • 运行结果

DataInputStream(数据输入流)

  • 语法

    • 构造器

      构造器 说明
      public DataInputStream(InputStream is) 创建新数据输入流包装基础的字节输入流
    • 方法

      方法 说明
      Public final byte readByte() throws IOException 读取字节数据返回
      public final void writeInt(int v) throws IOException 读取int类型的数据返回
      public final double readDouble() throws IOException 读取double类型的数据返回
      public final String readUTF() throws IOException 读取字符串数(UTF-8)据返回
      public int readInt()/read(byte[]) 支持读字节数据进来
  • 代码示例

    java 复制代码
    import java.io.DataInputStream;
    import java.io.FileInputStream;
    import java.io.InputStream;
    
    public class Demo112 {
        public static void main(String[] args) {
            //目标:使用特殊数据输入流读取数据和数据类型
            try(
                    //1.定义资源
                    InputStream in = new FileInputStream("./a9.txt");
    
                    //2.特殊数据输入流
                    DataInputStream dataInputStream = new DataInputStream(in)
            ){
                //注意:一定要按照写的顺序读取
                int a = dataInputStream.readInt();
                boolean b = dataInputStream.readBoolean();
                double c = dataInputStream.readDouble();
                String d = dataInputStream.readUTF();
    
                System.out.println(a);
                System.out.println(b);
                System.out.println(c);
                System.out.println(d);
    
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
  • 运行结果

IO框架

什么是IO框架

  • 什么是框架(Framework):是一个预先写好的代码库或一组工具,旨在简化和加速开发过程
  • 框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去。(jar就是java编译后文件打的包,本质上是一个可执行编译文件zip包,jar,除了jar还有war,war专为 Web 应用设计,在 JAR 基础上增加 Web 特有目录)
  • IO框架commons-io-2.11.0.jar 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。

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

  • 在项目中创建一个文件夹:lib

  • 将commons-io-2.6.jar文件复制到lib文件夹

  • 在jar文件上点右键,选择 Add as Library -> 点击OK

  • 在类中导包使用

  • 语法

    • FileUtils类提供的部分方法展示(用于直接连接文件)

      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类提供的部分方法展示

      FileUtils类提供的部分方法展示 说明
      public static int copy(InputStream inputStream, OutputStream outputStream) 复制文件。
      public static int copy(Reader reader, Writer writer) 复制文件
      public static void deleteDirectory(File directory) 删除文件
      public static void write(String data, OutputStream output, String charsetName) 读数据
  • 代码演示

    java 复制代码
    public static void main(String[] args) throws Exception {
    
        //目标:commons-io包下的IO流框架的使用
        //1.一句代码文件复制
    //    FileUtils.copyFile(
    //            new File("day03-io-file\\a5.txt"),
    //            new File("day03-io-file\\a10.txt")
    //    );
        // //2.一句代码目录复制
    //    FileUtils.copyDirectory(
    //            new File("D:\\demo2"),
    //            new File("D:\\demo3")
    //    );
        //3.一句代码文件夹删除
        //FileUtils.deleteDirectory(new File("D:\\demo3"));
    
        //3.jdk7提供新的方式一句代码文件复制
        Files.copy(Path.of("day03-io-file\\a5.txt"), Path.of("day03-io-file\\a11.txt"));
    
        //4.IOUtils
        IOUtils.copy(new FileInputStream("day03-io-file\\a5.txt"), new FileOutputStream("day03-io-file\\a11.txt"));
    }
    java 复制代码
    public static void main(String[] args) throws Exception {
    
        //目标:commons-io包下的IO流框架的使用
        //1.一句代码文件复制
        FileUtils.copyFile(
                new File("day03-io-file\\a5.txt"),
               new File("day03-io-file\\a10.txt")
        );
         //2.一句代码目录复制
        FileUtils.copyDirectory(
                new File("D:\\demo2"),
                new File("D:\\demo3")
        );
        //3.一句代码文件夹删除
        FileUtils.deleteDirectory(new File("D:\\demo3"));
    }
    java 复制代码
    public static void main(String[] args) throws Exception {
        //4.jdk7提供新的方式一句代码文件复制
        Files.copy(Path.of("day03-io-file\\a5.txt"), Path.of("day03-io-file\\a11.txt"));
    
        //5.IOUtils
        IOUtils.copy(new FileInputStream("day03-io-file\\a5.txt"), new FileOutputStream("day03-io-file\\a11.txt"));
    }
  • FileUtils类提供的方法和IOUtils类提供的方法分别什么时候使用

    • FileUtils(适合操作"文件对象"):该类的方法参数多为 File类型,主要用于处理磁盘上的文件路径。当你已经明确了文件的来源和去向,不需要手动去处理底层的字节流或字符流时,使用这个类最为便捷。
    • IOUtils(适合操作"输入输出流"):该类的方法参数多为 InputStreamOutputStreamReader流对象 。当你需要处理的数据来源不是直接的本地文件(例如来自网络请求下载的流、内存中的字节数组流 ByteArrayInputStream,或者需要自定义过滤/加密逻辑的流),或者在更底层的网络编程中,就需要先将数据转为流,然后交给 IOUtils来处理数据的传输与复制。
相关推荐
用户78937733908531 小时前
前端转后端生存指南(中):化身架构师,用 ORM 魔法掌控数据库
后端·python
传说之后1 小时前
GO 语言单元测试入门
后端
古城小栈1 小时前
Bun从Zig迁移至Rust:有何重大意义?
开发语言·后端·rust
虎子_layor1 小时前
给 Agent 接入新模型的推理模式:从配置开关到协议适配
后端·架构
IT_陈寒1 小时前
Java的Stream.peek()千万别乱用,血泪教训
前端·人工智能·后端
小江的记录本2 小时前
【MySQL】MySQL日志体系:redo log/undo log/binlog 三者区别、两阶段提交、如何保证数据一致性
java·数据库·后端·python·sql·mysql·面试
Jul1en_3 小时前
【Spring Cloud】Spring Cloud Config详解
后端·spring·spring cloud
Wy_编程3 小时前
go语言面向对象和异常处理
开发语言·后端·golang
易安说AI10 小时前
Codex 直接住进 JetBrains IDE 里:AI Agent 正在接管熟悉的开发入口
后端