File类与IO流

文章目录

  • 计算机I/O体系
    • 常见的I/O设备
    • 计算机的存储体系
    • 软件方面认识
      • OS+文件系统(FileSystem)统一管理文件
  • File类
    • 1.1概述
    • 1.2判断功能的方法
    • 1.3创建删除文件\文件夹方法
    • 1.4目录的遍历
    • 1.5 综合练习
  • I/O流
    • 2.1 什么是IO
    • 2.2 IO的分类
    • 2.3 顶级父类们
  • 字节流
    • 3.1 一切皆为字节
    • 3.2 字节输出流【OutputStream】
      • 3.2.1 FileOutputStream类
        • 构造方法
      • 3.2.2 字节流写数据的3种方式
      • 3.2.3 字节流写数据的两个小问题
    • 3.3文件字节输入流【InputStream】
      • 3.3.1 FileInputStream
        • 构造方法
      • 3.3.2字节流读数据的两种方式
      • 3.3.3利用字节流复制照片
    • 3.4关于字节流异常处理
  • 字符流
    • 4.1 字符输入流【Reader】
      • 4.1.1 FileReader类
        • 构造方法
      • 4.1.2 利用字符流读取数据
    • 4.2字符输出流【Writer】
      • 4.2.1 FileWriter类
        • 构造方法
      • 4.2.2利用字符流写出数据
      • 4.2.3 关闭和刷新
  • 缓冲流
    • 5.1概述
    • 5.2字节缓冲流
      • 5.2.1构造方法
    • 5.3字符缓冲流
      • 5.3.1 构造方法
      • 5.3.2 特有方法
      • 总结
  • I/O练习
    • 6.1 文件夹的拷贝
    • 6.2对文件内容进行排序
    • 6.3 练习:文本排序
  • 转换流
    • 7.1 InputStreamReader类
      • 构造方法
      • 指定编码读取
    • 7.2 OutputStreamWriter类
      • 构造方法
      • 指定编码写出
    • 练习:转换文件编码
      • 案例分析
      • 案例实现
  • 序列流
    • 8.1ObjectOutputStream类
      • 构造方法
      • 序列化操作
    • 8.2 ObjectInputStream类
      • 构造方法
      • 反序列化操作1
      • 反序列化操作2
    • 练习:序列化集合
      • 案例分析
  • 打印流
    • 字节打印流
      • 构造方法
    • 字符打印流
      • 构造方法
  • 标准输入输出流
    • 标准输入流
    • 标准输出流
  • Properties
    • Properties特有方法
    • Properties和IO流相结合的方法

计算机I/O体系

File类与IO流_第1张图片

  • 输入设备,就是用相关的硬件把信息输入到电脑中,比如有键盘,鼠标,麦克风等,标准输入设备是键盘(将现实中的物理信号,比如光信号,电信号,波信号,变为数字,然后存储到内存中)
  • 输出设备,比如音响,打印机等,标准的输出设备是屏幕(将数字变为物理信号)
  • 内存,也称运行内存,比如我们买手机时的8+256G,其中8G就是内存,而程序中处理的信息都是要放在内存中的,它的容量较小,但是运行速度是很快的,而且它如果断电,那么数据就会消失,如果想做到关机之后数据不消失,就得看外存了
  • 外存,比如硬盘,U盘这些都是外部存储器,手机的256G就是外存的容量,外存的大小比较大,但是运行速度比较慢,断电后数据不会消失,操作系统的文件都是在硬盘中存储
  • CPU,是电脑的中央处理器,类似人的大脑,它是来控制计算机的,比如运行什么程序,和该如何运行,它是跟内存来打交道的,是从内存中去读取和输出数据的,不能直接从硬盘读取数据

常见的I/O设备

  • 显示器(O),触屏显示器(I/O),鼠标(I),键盘(I),摄像头(I),扬声器(O),硬盘(I/O)

计算机的存储体系

File类与IO流_第2张图片

易失存储

  • 断电后不存在
  • 跨进程管理,被抽象出的变量和对象(本质是编程语言对硬件中内存的抽象)

持久化存储

  • 断电后一般仍旧能保存
  • 通常可以跨进程读写

存储速度的差异

  • 自上而下,存储速度递减,并且读写速度越快,价格越高,所以弄出专门的存储体系
  • 内存(Memory)又被称为RAM,支持以O(1)时间复杂度,根据下标(内存地址)访问的存储

硬盘的实现

  • 磁盘(利用磁性实现的一种存储方式)

  • 固态硬盘(Soild State Disk SSD)

  • 闪存(Flash Memory)

  • 主要硬盘都是指磁盘

软件方面认识

  • 软件方面不去考虑硬盘本身,只考虑硬盘中的数据(有数据存储的,没有数据存储的)
  • 文件是对硬盘中数据的抽象概念
  • 所以硬盘的读写问题变成文件的读写问题

OS+文件系统(FileSystem)统一管理文件

  • 文件以树形结构进行管理(不是二叉树)
  • 文件分为两种
    • 存储数据的文件——普通文件(俗称的文件),在Window OS 下,以文件后缀(file suffiex)来标记这个文件存储的内容是什么内容(*.txt 普通文本 *.docx Word 文档)
    • 管理树形结构组织数据的文件——目录/文件夹(directory/dir),以/结束,代表这个结点是目录

File类与IO流_第3张图片

这个文件树只是一个逻辑结构,而不是硬盘上的物理结构

文件的路径

  • 关于文件的路径(Path):根据一个规则,从文件树上唯一确定一个位置这个位置一定对应某个结点,但是这个结点可以不存在

路径的分类

  • 绝对路径(absolute path):从盘符开始的路径,这是一个完整的路径。
  • 相对路径(relative path) :从我们所在的位置出发,所描述的路径(我们的位置一定是一个目录,不能处在一个文件上)
    • 每个进程都有一个当前工作目录,一般一个进程的启动目录就是当前的工作目录

File类

1.1概述

  • File:它是文件和目录路径名的抽象表示

    • 文件和目录是可以通过File封装成对象的

    • 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的。

小贴士:

  1. 一个File对象代表硬盘中实际存在的一个文件或者目录。
  2. 无论该路径下是否存在文件或者目录,都不影响File对象的创建。
方法名 说明
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的File实例
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的File实例
public class Demo1 {
    public static void main(String[] args) {
        //1相对路径字符串
        File file = new File("a.txt");
        System.out.println(file.getAbsoluteFile());
        //2绝对路径字符串
        File file1 = new File("C:\\JavaCode\\FileAndIO\\b.txt");
        System.out.println(file1.getAbsoluteFile());
        //3父路径名字符串+子路径字符串
        File file2 = new File("C:\\JavaCode\\FileAndIO", "c.txt");
        System.out.println(file2.getAbsoluteFile());
        //4父抽象路径名+子路径字符串
        File file3 = new File("C:\\JavaCode\\FileAndIO");
        File file4 = new File(file3, "d.txt");
        System.out.println(file4.getAbsoluteFile());

    }
}
C:\JavaCode\FileAndIO\a.txt
C:\JavaCode\FileAndIO\b.txt
C:\JavaCode\FileAndIO\c.txt
C:\JavaCode\FileAndIO\d.txt
  • 在JAVA字符串中\不能直接写\,因为在字符串中反斜杠,表示转义的意思,所以在JAVA中\应该写成\\ 比如"E:\\JAVA代码"
  • Windows中使用\作为路径分隔符,Linux使用/作为路基分隔符
    • JAVA是跨平台的语言,所以我们在代码中写/或者\都是可以的,比较推荐使用/ E:/JAVA代码"
    • 因为我们写的代码在Windows上编写,但是可能在Linux上运行,所以我们不推荐用+这种方式去进行字符串拼接,而是直接调用父+子的的构造方法去生产File
  • 其中.表示当前位置(目录),…表示回到当前位置的父节点(目录)
    • 根节点的父节点还是自己
  • 在相对路径中:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
    • – IDEA 中, main 中的文件的相对路径,是相对于"当前工程"
    • – IDEA 中,单元测试方法中的文件的相对路径,是相对于"当前 module"

在介绍文件操作的方法,先来了解文件数据的组成

File类与IO流_第4张图片

1.2判断功能的方法

对于判断功能,无非就是判断文件的数据信息和元信息

File类与IO流_第5张图片
public class Demo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("test\\a.txt");
        //1判断file对象对应的文件是否存在
        System.out.println(file.exists()); //输出false
        //2对于不存在的文件进行判断是否是文件/文件夹
        System.out.println(file.isFile());//输出false
        System.out.println(file.isDirectory());//输出false
        //3利用file的方法进行创建文件
        boolean newFile = file.createNewFile();//输出true
        //4对于存在的文件进行判断是否是文件/文件夹
        System.out.println(file.isFile());//输出true
        System.out.println(file.isDirectory());//输出false
        //5对存在的文件进行相关信息的查看
        System.out.println(file.getAbsoluteFile());//查看绝对路径
        //  C:\JavaCode\FileAndIO\test\a.txt
        System.out.println(file.getPath());//获得创建file对象时的路径
       	 //  test\a.txt
        System.out.println(file.getName());//获得文件名带后缀
        //  a.txt
        System.out.println(file.length());//获得文件的内容数据的长度,单位是字节数
        //  0
        System.out.println(file.lastModified());//返回文件的最后修改时间

    }
}

1.3创建删除文件\文件夹方法

File类与IO流_第6张图片
public class Demo3 {
    public static void main(String[] args) throws IOException {
        //前提条件C:\JavaCode\FileAndIO\test 这个目录存在
        File file1 = new File("test\\b.txt"); 
        File file3 = new File("text\\aaa\\c.txt");
        //1创建一个新的空的文件 此时路径是一个文件路径
        System.out.println(file1.getName()+"父路径是否存在"+new File("test").exists());
        System.out.println(file1.getName()+"是否存在"+file1.exists());
        System.out.println(file1.createNewFile());
        System.out.println(file1.getName()+"是否存在"+file1.exists());
        //3创建一个父路径不存在的file对象
        System.out.println(file3.getName()+"父路径是否存在"+new File("test\\aaa").exists());
        System.out.println(file3.getName()+"是否存在"+file3.exists());
        System.out.println(file3.createNewFile());
        System.out.println(file3.getName()+"是否存在"+file3.exists());
    }
}
b.txt父路径是否存在true
b.txt是否存在true
false
b.txt是否存在true
c.txt父路径是否存在false
c.txt是否存在false
Exception in thread "main" java.io.IOException: 系统找不到指定的路径。
	at java.base/java.io.WinNTFileSystem.createFileExclusively(Native Method)
	at java.base/java.io.File.createNewFile(File.java:1043)
	at Demo3.main(Demo3.java:26)
  • 如果父路径不存在,创建文件会报异常
public class Demo3 {
    public static void main(String[] args) throws IOException {
        //前提条件C:\JavaCode\FileAndIO\test 这个目录存在
        //4 mkdir创建一个新的空的文件目录 父路径存在
        File file4 = new File("test\\bbb");
        System.out.println(file4.getName()+"父路径是否存在"+new File("test").exists());
        System.out.println(file4.getName()+"是否存在"+file4.exists());
        System.out.println(file4.mkdir());
        System.out.println(file4.getName()+"是否存在"+file4.exists());
        //5 mkdir创建一个新的空的文件目录 父路径不存在
        File file5 = new File("test\\ccc\\ddd");
        System.out.println(file5.getName()+"父路径是否存在"+new File("test\\ccc").exists());
        System.out.println(file5.getName()+"是否存在"+file5.exists());
        System.out.println(file5.mkdir());
        System.out.println(file5.getName()+"是否存在"+file5.exists());
    }
}
bbb父路径是否存在true
bbb是否存在false
true
bbb是否存在true
ddd父路径是否存在false
ddd是否存在false
false
ddd是否存在false
  • 由此可知,我们的mkdir只能创建单级目录
public class Demo3 {
    public static void main(String[] args) throws IOException {
        //4 mkdirs创建一个新的空的文件目录 父路径存在
        File file5 = new File("test\\bbb");
        System.out.println(file5.getName()+"父路径是否存在"+new File("test").exists());
        System.out.println(file5.getName()+"是否存在"+file5.exists());
        System.out.println(file5.mkdirs());
        System.out.println(file5.getName()+"是否存在"+file5.exists());
        //5 mkdirs创建一个新的空的文件目录 父路径不存在
        File file6 = new File("test\\ccc\\ddd");
        System.out.println(file6.getName()+"父路径是否存在"+new File("test\\ccc").exists());
        System.out.println(file6.getName()+"是否存在"+file6.exists());
        System.out.println(file6.mkdirs());
        System.out.println(file6.getName()+"是否存在"+file6.exists());
    }
}
bbb父路径是否存在true
bbb是否存在true
false
bbb是否存在true
ddd父路径是否存在false
ddd是否存在false
true
ddd是否存在true
  • mkdirs可以创建多级目录
//删除功能
public class Demo4 {
    public static void main(String[] args) throws IOException {
        File f1 = new File("java1.txt");  //创建在项目文件下
        System.out.println(f1.createNewFile());

        //删除
        System.out.println(f1.delete());

        //创建和删除目录
        File f2 = new File("itcast");
        System.out.println(f2.mkdir());
        System.out.println(f2.delete());
        System.out.println("----------------------");

        //创建cast目录,接着在目录下创建java.txt文件
        File f3 = new File("cast");
        System.out.println(f3.mkdir());
        File f4 = new File("cast\\java.txt");
        System.out.println(f4.createNewFile());
        System.out.println("----------------------------");

        //再次删除:注意得先删除路径下的内容,才能删除文件夹
        System.out.println(f3.delete());
        System.out.println(f4.delete());
        System.out.println(f3.delete());
    }
}
true
true
true
true
----------------------
true
true
----------------------------
false
true
true
  • 如果目录中有其他文件,那么就不能直接删除这个目录,只能删除空目录
  • 返回true,删除成功
  • 返回false,对应文件不存在导致的删除失败,非空目录的删除
  • IOException其他情况失败(比如其他进程打开文件)

1.4目录的遍历

File类与IO流_第7张图片
  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
  • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。
public class Demo5 {
    public static void main(String[] args) {
        File file1 = new File("test");
        File[] files = file1.listFiles();
        for (File file: files ) {
            System.out.println(file.getAbsolutePath());
        }
    }
}
C:\JavaCode\FileAndIO\test\a.txt
C:\JavaCode\FileAndIO\test\bbb
C:\JavaCode\FileAndIO\test\ccc

小贴士:

调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。

遍历一个目录下所有文件(包括子目录中的文件)

public class Demo6 {
    public static void main(String[] args) {
        File file = new File("test");
        getAllFilePath(file);
    }
    public static void  getAllFilePath(File file){
        //说明当前是个文件夹
        File[] files = file.listFiles();
        for (File one: files) {
            if (one.isDirectory()){
                //当前文件是目录
                System.out.println(one.getAbsolutePath());
                getAllFilePath(one);
            }else {
                //当前文件是一个文件
                System.out.println(one.getAbsolutePath());
            }
        }
    }
}

1.5 综合练习

在当前模块下的aaa文件夹中创建一个a.txt文件

public class Practice1 {
    public static void main(String[] args) throws IOException {
        //在当前模块下的aaa文件夹中创建一个a.txt文件
        File parent = new File("aaa");
        //如果没有父路径 则创建 如果有 则不创建
        parent.mkdirs();
        File file = new File(parent, "a.txt");
        boolean newFile = file.createNewFile();
        if (newFile){
            System.out.println("创建成功");
        }else {
            System.out.println("创建失败");
        }

    }
}

找到电脑中所有以avi结尾的电影。(需要考虑子文件夹)

public class Practice1 {
    public static void main(String[] args) throws IOException {
        //获取该主机下的所有盘符
        File[] files = File.listRoots();
        for (File file : files) {
            findEndWithAVI(file);
        }
    }
    private static void findEndWithAVI(File file){
        //获得该目录下所有的文件信息
        File[] files = file.listFiles();
        for (File one: files) {
            //如果是目录,则进目录继续寻找
            if(one.isDirectory()){
                findEndWithAVI(one);
            }else {
                //说明当前文件是普通文件 进行判断
                if (one.getName().endsWith(".avi")){
                    System.out.println(one.getAbsolutePath());
                }
            }
        }
    }
}

删除多级文件夹

需求: 如果我们要删除一个有内容的文件夹

  • 先删除文件夹里面所有的内容
  • 再删除自己
public class Test4 {
    public static void main(String[] args) {
        /*
           删除一个多级文件夹
           如果我们要删除一个有内容的文件夹
           1.先删除文件夹里面所有的内容
           2.再删除自己
        */

        File file = new File("D:\\aaa\\src");
        delete(file);

    }

    /*
    * 作用:删除src文件夹
    * 参数:要删除的文件夹
    * */
    public static void delete(File src){
        //1.先删除文件夹里面所有的内容
        //进入src
        File[] files = src.listFiles();
        //遍历
        for (File file : files) {
            //判断,如果是文件,删除
            if(file.isFile()){
                file.delete();
            }else {
                //判断,如果是文件夹,就递归
                delete(file);
            }
        }
        //2.再删除自己
        src.delete();
    }
}

需求:统计一个文件夹中每种文件的个数并打印。(考虑子文件夹)

public class Practice1 {
    public static void main(String[] args) throws IOException {
//        需求:统计一个文件夹中每种文件的个数并打印。(考虑子文件夹)
//        打印格式如下:
//        txt:3个
//        doc:4个
//        jpg:6个
        HashMap<String, Integer> map = new HashMap<>();
        File file = new File("test");
        map = getCount(file);
    }
    public static HashMap<String,Integer> getCount(File src){
        File[] files = src.listFiles();
        HashMap<String, Integer> map = new HashMap<>();
        for (File file: files) {
            if(file.isFile()){
                //说明当前是一个文件 进行统计
                String name = file.getName();
                String[] arr = name.split("\\.");
                if (arr.length>=2){
                    String endName = arr[arr.length - 1];
                    if (map.containsKey(endName)){
                        //说明已经存在这个类型
                        map.put(endName,map.get(endName)+1);
                    }else {
                        //说明还不存在这个类型
                        map.put(endName,1);
                    }
                }
            }else{
                //5.判断,如果是文件夹,递归
                //sonMap里面是子文件中每一种文件的个数
                HashMap<String, Integer> sonMap = getCount(file);
                //hm:  txt=1  jpg=2  doc=3
                //sonMap: txt=3 jpg=1
                //遍历sonMap把里面的值累加到hm当中
                Set<Map.Entry<String, Integer>> entries = sonMap.entrySet();
                for (Map.Entry<String, Integer> entry : entries) {
                    String key = entry.getKey();
                    int value = entry.getValue();
                    if(map.containsKey(key)){
                        //存在
                        int count = map.get(key);
                        count = count + value;
                        map.put(key,count);
                    }else{
                        //不存在
                        map.put(key,value);
                    }
                }
            }
        }
        return map;
    }

I/O流

2.1 什么是IO

生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了ctrl+s ,可能文件就白白编辑了。当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是在哪些设备上的呢?键盘、内存、硬盘、外接设备等等。

我们把这种数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input输出output ,即流向内存是输入流,流出内存的输出流。

Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。

  • 这里对于Java如何去划分输入输出呢?我们划分输入输出是要确定一个主体,对于Java的IO来说主体是程序
  • 程序读取本地文件叫输入
  • 程序将数据放入本地文件叫输出

2.2 IO的分类

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到其他设备上的流。

格局数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
    • 对于字节流可以操作任意类型的数据文件
  • 字符流 :以字符为单位,读写数据的流。
    • 字符流只能操作纯文本文件

这里纯文本文件的区分——如果能够通过Windows自带的记事本打开的文件,能够看懂其中的内容就是纯文本文件

2.3 顶级父类们

File类与IO流_第8张图片

字节流

3.1 一切皆为字节

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

3.2 字节输出流【OutputStream】

java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  • public abstract void write(int b) :将指定的字节输出流。

小贴士:

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

3.2.1 FileOutputStream类

OutputStream有很多子类,我们从最简单的一个子类开始。

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。

  • File表示操作什么,File表示本地文件
  • Output表示是输出操作

File类与IO流_第9张图片

public class FileOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("test\\a.txt");
        //1创建输出流对象
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        //2进行输出操作
        fileOutputStream.write(97);
        //3资源关闭
        fileOutputStream.close();
    }
}
  • 如果对应的文件不存在,会创建这个文件,但是必须保证这个文件的父路径必须存在
  • 如果对应的文件存在了,会把这个文件内容清空,再进行写入
构造方法
  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

3.2.2 字节流写数据的3种方式

File类与IO流_第10张图片

public class WriteDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("test\\a.txt");
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(97);//写入数据为a
        byte [] arr = {97,98,99,100};
        fileOutputStream.write(arr);//写入数据abcd
        fileOutputStream.write(arr,1,2);//写入数据bc
        fileOutputStream.close();
    }
}
  • 对应write来说,如果参数是整型,对应的是ASCII对应的字符数据

3.2.3 字节流写数据的两个小问题

  • 如何解决续写
    • 在我们之前是练习中,如果文件存在,就会清空文件再写入
    • 如果想续写,在创建输出流对象的时候给构造方法传入一个true,开启续写模式
  • 如何解决换行写入
    • 对于不同的操作系统会有不同的换行符,对于Java是跨平台,后面会学习跨平台的操作
    • windows的换行符是/r/n
    • linux是/n
    • mac是/r
public class WriteDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("test\\a.txt");
        //开启续写模式
        FileOutputStream fileOutputStream = new FileOutputStream(file, true);
        String str = "abcdefge";
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        System.out.println(Arrays.toString(bytes));//[97, 98, 99, 100, 101, 102, 103, 101]
        fileOutputStream.write(bytes);
        String str1 = "\r\n";
        byte[] bytes1 = str1.getBytes();
        fileOutputStream.write(bytes1);//写入换行符
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }
}

File类与IO流_第11张图片

  • 总结
    • FileOutputStream是可以将程序的数据写入到本地文件上,是字节流的基本流
    • 基本步骤
      • 创建对象
        • 文件存在(覆盖),文件不存在(创建),追加写入
      • 写出数据
        • 写出整数,写成字节数组,换行写
      • 释放资源
        • 关闭通道

3.3文件字节输入流【InputStream】

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
  • public abstract int read(): 从输入流读取数据的下一个字节。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

小贴士:

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

3.3.1 FileInputStream

  • File表示操作本地文件
  • Input表示输入,是从本地文件输入到程序中
构造方法
  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException

  • 构造举例,代码如下:
public class FileInputStreamConstructor throws IOException{
    public static void main(String[] args) {
   	 	// 使用File对象创建流对象
        File file = new File("a.txt");
        FileInputStream fos = new FileInputStream(file);
      
        // 使用文件名称创建流对象
        FileInputStream fos = new FileInputStream("b.txt");
    }
}

3.3.2字节流读数据的两种方式

  • 读取字节read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1,代码使用演示
  • 使用字节数组读取read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1 ,代码使用演示:
public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("a.txt");//文件内容是abcdefg
        FileInputStream fileInputStream = new FileInputStream(file);
        int b;
        while ((b=fileInputStream.read())!=-1){
            //fileInputStream.read()每次读取一个字节的数据,读取到末尾返回-1
            System.out.print((char) b);
        }
        fileInputStream.close();
    }
}

小贴士:

  1. 虽然读取了一个字节,但是会自动提升为int类型。
  2. 流操作完毕后,必须释放系统资源,调用close方法,千万记得。
public class FISRead {
    public static void main(String[] args) throws IOException{
      	// 使用文件名称创建流对象.
       	FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
      	// 定义变量,作为有效个数
        int len ;
        // 定义字节数组,作为装字节数据的容器   
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b))!=-1) {
           	// 每次读取后,把数组变成字符串打印
            System.out.println(new String(b));
        }
		// 关闭资源
        fis.close();
    }
}

输出结果:
ab
cd
ed
  • 按理说最后一次读取的数据应该是e,而不是ed
    • 因为我们的字节数组中存的是上一次读取的cd,而最后一次只读取了应该e,只是将c覆盖了成了e,所以数组内数据是ed

3.3.3利用字节流复制照片

public class JDPCopy {
    public static void main(String[] args) throws IOException {
        File oldFile = new File("R-C.jpg");
        File newFile = new File("new.jpg");
        FileInputStream fileInputStream = new FileInputStream(oldFile);
        FileOutputStream fileOutputStream = new FileOutputStream(newFile);
        byte[] bytes = new byte[1024];
        int len;
        while ((len=fileInputStream.read(bytes))!=-1){
            fileOutputStream.write(bytes,0,len);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }
}
  • 因为可能数组最后一次读取并没有装满,所以需要每次读取字节的长度

小贴士:

流的关闭原则:先开后关,后开先关。

3.4关于字节流异常处理

File类与IO流_第12张图片

public class ExceptionHandle {
    public static void main(String[] args) {
        File file = new File("a.txt");
        //1普通写法
        FileOutputStream fileOutputStream = null;
        try {
             fileOutputStream = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //2 jdk7语法糖写法
        try (FileInputStream fileInputStream = new FileInputStream(file)){

        }catch (IOException e) {
            e.printStackTrace();
        }
        //3 jdk9语法糖写法
        FileInputStream fileInputStream1 = new FileInputStream(file);
        try (fileInputStream1){
            
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符流

当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。

  • 字符流底层就是字节流,字符流其实就是字节流+字符集

4.1 字符输入流【Reader】

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • public void close() :关闭此流并释放与此流相关联的任何系统资源。
  • public int read(): 从输入流读取一个字符。
  • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。

4.1.1 FileReader类

java.io.FileReader 类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

小贴士:

  1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。idea中UTF-8

  2. 字节缓冲区:一个字节数组,用来临时存储字节数据。

构造方法

image-20230706133828955

public class FileReaderConstructor throws IOException{
    public static void main(String[] args) {
   	 	 // 使用File对象创建流对象
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("b.txt");
    }
}

4.1.2 利用字符流读取数据

File类与IO流_第13张图片

  • 按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数
  • 读到文件末尾,read方法返回-1
public class CharStreamDemo {
    public static void main(String[] args) throws IOException {
       FileReader fileReader = new FileReader("a.txt");
       int ch;
       //read()默认也是一个一个字节进行读取,如果遇到中文就会一次读取多个
        //在读取之后,会进行解码,将二进制的字节解码为对应的码点,也就是二进制
        //这个整数也就是字符集上对应的数字
        //英文: 文件乱码二进制数据 0110 0001
        //       read方法进行读取,解码为十进制的97
        //中文: 文件里面的二进制数据 11100110 10110001 10001001
        //       read方法进行读取,解码为十进制的27721
       while ((ch=fileReader.read())!=-1){
           System.out.println((char) ch);
       }
       fileReader.close();
    }
}
public class CharStreamDemo {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("a.txt");
        char []arr =new char[2];//一个char是2个字节 代表一个代码单元(Code Unit)
        int len;
        while ((len=fileReader.read(arr))!=-1){
            for (char c: arr) {
                System.out.print(c);
            }
        }
        fileReader.close();
    }
  • read(char []buffer)其实就是空参的read+强制类型转换

4.2字符输出流【Writer】

java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • void write(int c) 写入单个字符。
  • void write(char[] cbuf) 写入字符数组。
  • abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
  • void write(String str) 写入字符串。
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
  • void flush() 刷新该流的缓冲。
  • void close() 关闭此流,但要先刷新它。

4.2.1 FileWriter类

java.io.FileWriter 类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

构造方法

File类与IO流_第14张图片

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。
    • 跟我们的字节流差不多,文件不存在则创建一个新的文件,但是要保证父级路径存在
    • 如果文件已经存在,则会清空文件,如果不想清空可以打开续写开发

当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。

  • 构造举例,代码如下:
public class FileWriterConstructor {
    public static void main(String[] args) throws IOException {
   	 	// 使用File对象创建流对象
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);
      
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("b.txt");
    }
}

4.2.2利用字符流写出数据

File类与IO流_第15张图片
public class WriteDemo {
    public static void main(String[] args) throws IOException {
         FileWriter fileWriter = new FileWriter("b.txt");
         FileOutputStream fileOutputStream = new FileOutputStream("a.txt");
        //1写入一个整数
         fileWriter.write(25105);//25105表示的是"我"的码点 写入一个字母文件中显示 我
         fileOutputStream.write(25105);//如果通过字节流写入,则写入的是一个字节 文件中则会乱码
         //2写入一个字符串
         fileWriter.write("abcd");
         fileWriter.write("abcd",0,2);
         //3写入一个字符数组
         fileWriter.write(new char[]{'你','好','啊'});
         fileWriter.write(new char[]{'你','好','啊'},0,2);
         fileWriter.close();
    }
}

4.2.3 关闭和刷新

File类与IO流_第16张图片
  • 对于我们的FileReader,我们去读取数据的时候,会一次最多读取8192字节的数据放入内存,如果没有这么多数据,就读取最大数据,下次read,如果缓冲区中有数据,就直接从缓冲区中取
File类与IO流_第17张图片
  • 对于我们的FileWriter写入数据,先写入缓冲区,有两种情况会真正写入文件
    • 缓冲区满了
    • 手动进行刷新

因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

  • flush :刷新缓冲区,流对象可以继续使用。
  • close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

代码使用演示:

public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 写出数据,通过flush
        fw.write('刷'); // 写出第1个字符
        fw.flush();
        fw.write('新'); // 继续写出第2个字符,写出成功
        fw.flush();
      
      	// 写出数据,通过close
        fw.write('关'); // 写出第1个字符
        fw.close();
        fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
        fw.close();
    }
}

小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。

缓冲流

5.1概述

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

5.2字节缓冲流

5.2.1构造方法

  • public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
  • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
  • 为什么构造方法需要的是字节流,因为字节缓冲流仅仅提供缓冲取,而真正的读写数据还是依靠基本的字节流对象进行操作

普通字节流

public class BufferedDemo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        try(FileInputStream fileInputStream = new FileInputStream("video\\test.mp4");
            FileOutputStream fileOutputStream = new FileOutputStream("video\\test1.mp4")) {
            int b;
            while ((b=fileInputStream.read())!=-1){
                fileOutputStream.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("普通流复制时间:"+(end - start)+" 毫秒");
    }
}
  • 过了很久都没有响应

缓冲流

public class BufferedDemo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        try( BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("video\\test.mp4"));
           BufferedOutputStream  bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("video\\test1.mp4"))) {
            int b;
            while ((b=bufferedInputStream.read())!=-1){
                bufferedOutputStream.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("复制时间:"+(end - start)+" 毫秒");
    }
}
  • 耗时3326毫秒

利用数组更快

public class BufferedDemo {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        try( BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("video\\test.mp4"));
             BufferedOutputStream  bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("video\\test2.mp4"))) {
            int len;
            byte[] bytes = new byte[8*1024];
            while ((len=bufferedInputStream.read(bytes))!=-1){
                bufferedOutputStream.write(bytes,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("复制时间:"+(end - start)+" 毫秒");
    }
}
  • 耗时1125毫秒

5.3字符缓冲流

5.3.1 构造方法

  • public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
  • public BufferedWriter(Writer out): 创建一个新的缓冲输出流。

构造举例,代码如下:

// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

5.3.2 特有方法

字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。

  • BufferedReader:public String readLine(): 读一行文字。
  • BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号。
public class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
      	 // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
		// 定义字符串,保存读取的一行文字
        String line  = null;
      	// 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) {
            System.out.print(line);
            System.out.println("------");
        }
		// 释放资源
        br.close();
    }
}
public class BufferedWriterDemo throws IOException {
    public static void main(String[] args) throws IOException  {
      	// 创建流对象
		BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
      	// 写出数据
        bw.write("黑马");
      	// 写出换行
        bw.newLine();
        bw.write("程序");
        bw.newLine();
        bw.write("员");
        bw.newLine();
		// 释放资源
        bw.close();
    }
}
输出效果:
黑马
程序
员

总结

  • 为什么缓冲流能够提高性能
    • 缓冲流自带长度为8192的缓冲区
      • 对于字符流来说是8192单位的char缓冲区
      • 对于字节流来说是8192字节的缓冲区
    • 可以显著提高字节流的读写性能
    • 对于字符流提升不明显,对于字符缓冲流而言关键是两个特有的方法

File类与IO流_第18张图片

I/O练习

6.1 文件夹的拷贝

public class Test1 {
    public static void main(String[] args) throws IOException {
        //文件夹的拷贝
        File src = new File("test");
        File dest = new File("demo");
        copyDir(src,dest);
    }

    private static void copyDir(File src, File dest) throws IOException {
        dest.mkdirs();
        File[] files = src.listFiles();
        for (File file : files) {
            if (file.isFile()){
                //是文件 进行拷贝
                FileInputStream fileInputStream = new FileInputStream(file);
                FileOutputStream fileOutputStream = new FileOutputStream(new File(dest,file.getName()));
                byte[] bytes = new byte[1024];
                int len;
                while ((len=fileInputStream.read(bytes))!=-1){
                    fileOutputStream.write(bytes,0,len);
                }
                fileInputStream.close();
                fileOutputStream.close();
            }else{
                //是文件夹 进行递归
                copyDir(file,new File(dest,file.getName()));
            }
        }


    }
}
  • 对于拷贝这种,我们想到的应该是字节流,因为字节流能操作任何数据,而字符流只能操作文本文件

6.2对文件内容进行排序

public class Test2 {
    public static void main(String[] args) throws IOException {
          /*
            文本文件中有以下的数据:
                2-1-9-4-7-8
            将文件中的数据进行排序,变成以下的数据:
                1-2-4-7-8-9
        */
        FileInputStream fileInputStream = new FileInputStream("a.txt");
        StringBuilder sb = new StringBuilder();
        int b;
        while ((b=fileInputStream.read())!=-1){
            sb.append((char) b);
        }
        String s = sb.toString();
        System.out.println(s);
        String[] split = s.split("-");
        FileOutputStream fileOutputStream = new FileOutputStream("a.txt");
        List collect = Arrays.stream(split)
                .map(str -> Integer.parseInt(str))
                .sorted((o1, o2) -> o1 - o2)
                .map(integer -> String.valueOf(integer))
                .collect(Collectors.toList());
        for (int i = 0; i < collect.size(); i++) {
            if(i==collect.size()-1){
                fileOutputStream.write(collect.get(i).getBytes());
            }else {
                fileOutputStream.write((collect.get(i)+"-").getBytes());
            }
        }
        fileInputStream.close();
        fileOutputStream.close();

    }
}

6.3 练习:文本排序

请将文本信息恢复顺序。

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

案例分析

  1. 逐行读取文本信息。
  2. 把读取到的文本存储到集合中
  3. 对集合中的文本进行排序
  4. 遍历集合,按顺序,写出文本信息。
public class Test3 {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("b.txt"));
        ArrayList<String> list = new ArrayList<>();
        String line;
        while ((line=bufferedReader.readLine())!=null){
            list.add(line);
        }
        Collections.sort(list, (o1, o2) -> o1.charAt(0)-o2.charAt(0));
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("b.txt"));
        for (String s : list) {
            bufferedWriter.write(s);
            bufferedWriter.newLine();
        }
        bufferedReader.close();
        bufferedWriter.close();
    }
}

转换流

  • 指定字符集读写——淘汰了
  • 字节流想要使用字符流中的方法

File类与IO流_第19张图片

File类与IO流_第20张图片

在IDEA中,使用FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。

public class Demo {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("gbkfile.txt");
        int b;
        while ((b= fileReader.read())!=-1){
            System.out.print((char) b);
        }
        fileReader.close();
    }
}
输出结果:
����Ϊ��һ�������긲������
�ݻ�л����һ��
�������£�ϷԺ�������廨Ū��

那么如何读取GBK编码的文件呢?

7.1 InputStreamReader类

转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法

  • InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
  • InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

构造举例,代码如下:

InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");

指定编码读取

public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
          创建流对象,指定GBK编码
        InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("gbkfile.txt"), "GBK");
        int b;
    
        while ((b=inputStreamReader.read())!=-1){
            System.out.print((char) b);
        }
        inputStreamReader.close();
    }
}
  • 但是这种在JDK11时就被废用了,采用FileReader原生的构造方法来指定编码方式

7.2 OutputStreamWriter类

转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法

  • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
  • OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

构造举例,代码如下:

OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");

指定编码写出

public class OutputDemo {
    public static void main(String[] args) throws IOException {
      	// 定义文件路径
        String FileName = "E:\\out.txt";
      	// 创建流对象,默认UTF8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
        // 写出数据
      	  osw.write("你好"); // 保存为6个字节
        osw.close();
      	
		// 定义文件路径
		String FileName2 = "E:\\out2.txt";
     	// 创建流对象,指定GBK编码
        OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
        // 写出数据
      	 osw2.write("你好");// 保存为4个字节
        osw2.close();
    }
}

练习:转换文件编码

将GBK编码的文本文件,转换为UTF-8编码的文本文件。

案例分析

  1. 指定GBK编码的转换流,读取文本文件。
  2. 使用UTF-8编码的转换流,写出文本文件。

案例实现

public class TransDemo {
   public static void main(String[] args) {      
    	// 1.定义文件路径
     	String srcFile = "file_gbk.txt";
        String destFile = "file_utf8.txt";
		// 2.创建流对象
    	// 2.1 转换输入流,指定GBK编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
    	// 2.2 转换输出流,默认utf8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
		// 3.读写数据
    	// 3.1 定义数组
        char[] cbuf = new char[1024];
    	// 3.2 定义长度
        int len;
    	// 3.3 循环读取
        while ((len = isr.read(cbuf))!=-1) {
            // 循环写出
          	osw.write(cbuf,0,len);
        }
    	// 4.释放资源
        osw.close();
        isr.close();
  	}
}

序列流

对象序列化:就是把对象保存到磁盘中,或者在网络中传输对象
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息。
字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化

要实现序列化和反序列化就要使用对象序列化流和反序列化流:

  • 对象序列化流:ObjectOutputStream
  • 对象反序列化流:ObjectInputStream

File类与IO流_第21张图片

8.1ObjectOutputStream类

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。

构造方法

  • public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。

构造举例,代码如下:

FileOutputStream fileOut = new FileOutputStream("student.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);

序列化操作

  1. 一个对象要想序列化,必须满足两个条件:
  • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
public class Student implements Serializable {
    public String name;
    public int age;
    public transient String address;
    public void addressCheck() {
        System.out.println("Address  check : " + name + " -- " + address);
    }
}

2.写出对象方法

  • public final void writeObject (Object obj) : 将指定的对象写出。
private static void readStduent() throws IOException, ClassNotFoundException {
       ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("student.txt"));
       Student student = (Student)objectInputStream.readObject();
       System.out.println(student);
}
Serialized data is saved

File类与IO流_第22张图片

8.2 ObjectInputStream类

ObjectInputStream类ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。

构造方法

  • public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("student.txt"));

反序列化操作1

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject () : 读取一个对象。
private static void readStduent() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("student.txt"));
        Student student = (Student)objectInputStream.readObject();
        System.out.println(student);
}
Student{name='lsc', age=23, address='null'}
  • 对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。

反序列化操作2

**另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常。**发生这个异常的原因如下:

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型
  • 该类没有可访问的无参数构造方法

Serializable 接口给需要序列化的类,提供了一个序列版本号。serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。

public class Student implements Serializable {
     // 加入序列版本号
    private static final long serialVersionUID = 1L;
    public String name;
    public int age;
    public transient String address;
    // 添加新的属性 ,重新编译, 可以反序列化,该属性赋为默认值.
    public int sex;
    public Student(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
}

练习:序列化集合

  1. 将存有多个自定义对象的集合序列化操作,保存到list.txt文件中。
  2. 反序列化list.txt ,并遍历集合,打印对象信息。

案例分析

  1. 把若干学生对象 ,保存到集合中。
  2. 把集合序列化。
  3. 反序列化读取时,只需要读取一次,转换为集合类型。
  4. 遍历集合,可以打印所有的学生信息
public class ObjectListStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Student student = new Student("lsc", 23, "武汉");
        Student student1 = new Student("lsc1", 23, "武汉1");
        Student student2 = new Student("lsc2", 23, "武汉2");
        ArrayList<Student> students = new ArrayList<>();
        students.add(student);
        students.add(student1);
        students.add(student2);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("list.txt"));
        objectOutputStream.writeObject(students);
        objectOutputStream.close();
        // 反序列化
        ObjectInputStream ois  = new ObjectInputStream(new FileInputStream("list.txt"));
        // 读取对象,强转为ArrayList类型
        ArrayList<Student> list  = (ArrayList<Student>)ois.readObject();

        for (int i = 0; i < list.size(); i++ ){
            Student s = list.get(i);
            System.out.println(student);
        }
    }
}

打印流

  • 字节打印流:PrintStream
  • 字符打印流:PrintWriter

打印流特点:只负责输出数据,不负责读取数据

字节打印流

构造方法

File类与IO流_第23张图片

例:字节打印流

File类与IO流_第24张图片

//字节打印流
public class Demo03 {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream ps = new PrintStream("ps.txt");

        //写数据
        //字节输出流有的方法
        ps.write(97);   //a

        //使用特有方法写数据
        ps.print(97);   //97
        ps.println(98);

        //释放文件
        ps.close();
    }
}

字符打印流

构造方法

File类与IO流_第25张图片



//字符打印流
public class Demo04 {
    public static void main(String[] args) throws IOException {
//        PrintWriter pw = new PrintWriter("pw.txt");

//        pw.write("hello");
//        pw.write("\r\n");
//        pw.flush();
//        pw.write("world");
//        pw.flush();

//        pw.println("hello");
//        pw.flush();
//        pw.println("world");
//        pw.flush();

        PrintWriter pw = new PrintWriter(new FileWriter("pw.txt"), true); //true实现自动刷新
        pw.println("hello");
        pw.println("world");

        pw.close();
    }
}

  • 因为字符流底层存在缓冲区,所以需要打开自动刷新

标准输入输出流

System类中有两个静态的成员变量

  • public static final InputStream in 标准输入流,通常该流对于键盘输入或者主机环境或者用户指定的另一个输入源
  • public static final PrintStream out 标准输出流,通常该流对应显示输出或者主机环境或用户指定的另一个输出目标

标准输入流

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //标准输入流,数据来自键盘输入
        InputStream is = System.in;

        //字节读数据
        int by;
        while ((by = is.read()) != -1) {
            System.out.println((char) by);
        }

        //如何把字节流转换为字符流?:转换流
        InputStreamReader isr = new InputStreamReader(is);
        //使用字符流能不能够实现一次读取一行数据?:这是字符缓冲输入流的特有方法
        BufferedReader br = new BufferedReader(isr);
        String ch;
        while ((ch=br.readLine())!=null){
            System.out.println(ch);
        }
//        //自己实现键盘录入数据太麻烦了,所以java就提供了一个类供我们使用
//        Scanner sc = new Scanner(System.in);
    }
}

标准输出流

package myOtherStream;

import java.io.PrintStream;
//标准输出流
public class Demo02 {
    public static void main(String[] args) {
        //public static final  PrintStream out:标准输出流
        PrintStream ps = System.out;

        //能够方便地打印各种数据值
//        ps.print("hello");
//        ps.print(100);

//        ps.println("world");
//        ps.println(200);

        //System.out的本质是一个字节输出流
        System.out.println("hello");
        System.out.println(100);
    }
}

Properties

  • 是一个Map体系的集合类
  • Properties可以保存在流中或者从流中加载
package myOtherStream;

import java.util.Properties;
import java.util.Set;

//Properties作为Map集合的使用
public class Demo09 {
    public static void main(String[] args) {
        //创建集合对象
//        Properties prop = new Properties();
        Properties prop = new Properties();

        //存储元素
        prop.put("01", "林青霞");
        prop.put("02", "张曼玉");
        prop.put("03", "王祖贤");

        //遍历集合
        Set<Object> keySet = prop.keySet();
        for (Object key : keySet) {
            Object value = prop.get(key);
            System.out.println(key + "," + value);
        }
    }
}

Properties特有方法

File类与IO流_第26张图片
package myOtherStream;

import java.util.Properties;
import java.util.Set;

//Properties特有方法
public class Demo10 {
    public static void main(String[] args) {
        //创建集合对象
        Properties prop = new Properties();
        prop.setProperty("01", "林青霞");  //接收String类型 ,put是Object类型
        prop.setProperty("02", "张曼玉");
        prop.setProperty("03", "王祖贤");

        //根据键获取值
//        System.out.println(prop.getProperty("01"));
//        System.out.println(prop.getProperty("04"));
//        System.out.println(prop);

        //获取键
        Set<String> names = prop.stringPropertyNames();
        for (String key : names) {  //String类型,上个例子是Object类型
//            System.out.println(key);
            String value = prop.getProperty(key);
            System.out.println(key + "," + value);
        }
    }
}

Properties和IO流相结合的方法

File类与IO流_第27张图片

//集合中的数据保存到文件
public class Demo11 {
    public static void main(String[] args) throws IOException {
        //把集合在中的数据保存到文件
        myStore();

        //把文件中的数据加载到集合
        myLoad();
    }

    private static void myLoad() throws IOException {
        Properties prop = new Properties();
        //load加载
        FileReader fr = new FileReader("D:\\itcast\\fw.txt");
        prop.load(fr);
        fr.close();
        System.out.println(prop);
    }

    private static void myStore() throws IOException {
        Properties prop = new Properties();

        prop.setProperty("01", "林青霞");
        prop.setProperty("02", "张曼玉");
        prop.setProperty("03", "王祖贤");

        FileWriter fw = new FileWriter("D:\\itcast\\fw.txt");
        prop.store(fw, null);  //null指的是描述信息
        fw.close();
    }
}

va
package myOtherStream;

import java.util.Properties;
import java.util.Set;

//Properties特有方法
public class Demo10 {
public static void main(String[] args) {
//创建集合对象
Properties prop = new Properties();
prop.setProperty(“01”, “林青霞”); //接收String类型 ,put是Object类型
prop.setProperty(“02”, “张曼玉”);
prop.setProperty(“03”, “王祖贤”);

    //根据键获取值

// System.out.println(prop.getProperty(“01”));
// System.out.println(prop.getProperty(“04”));
// System.out.println(prop);

    //获取键
    Set names = prop.stringPropertyNames();
    for (String key : names) {  //String类型,上个例子是Object类型

// System.out.println(key);
String value = prop.getProperty(key);
System.out.println(key + “,” + value);
}
}
}




## Properties和IO流相结合的方法

在这里插入图片描述

```java

//集合中的数据保存到文件
public class Demo11 {
    public static void main(String[] args) throws IOException {
        //把集合在中的数据保存到文件
        myStore();

        //把文件中的数据加载到集合
        myLoad();
    }

    private static void myLoad() throws IOException {
        Properties prop = new Properties();
        //load加载
        FileReader fr = new FileReader("D:\\itcast\\fw.txt");
        prop.load(fr);
        fr.close();
        System.out.println(prop);
    }

    private static void myStore() throws IOException {
        Properties prop = new Properties();

        prop.setProperty("01", "林青霞");
        prop.setProperty("02", "张曼玉");
        prop.setProperty("03", "王祖贤");

        FileWriter fw = new FileWriter("D:\\itcast\\fw.txt");
        prop.store(fw, null);  //null指的是描述信息
        fw.close();
    }
}

你可能感兴趣的:(Java核心技术卷1,计算机外设)