Java SE(四)IO流

File类

概述

File类介绍:来自于java.io包

1.它是文件和目录路径名的抽象表示。

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

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

路径:用于描述文件或者文件夹的所在位置的字符串。举例 : D:\0308系统班\day18\笔记

路径分类:

​ 绝对路径:是一个完整的路径,从盘符开始。举例 : D:\0308系统班\Idea资料

​ 相对路径:是一个简化的路径,相对当前项目下的路径。举例: a\b\ok.txt

​ idea中, 默认的路径: 当前项目工程根目录

File类型的构造方法
1、File(String path)://把字符串的路径,封装成一个File对象

2、File(String parent, String child)://将父级路径和子级路径封装成一个File对象,其实描述的是父级路径和子级路径拼接后的路径

3、File(File parent, String child)://将父级File对象路径和字节路径封装成一个File对象,其实描述的也是父级路径和子级路径拼接后的路径
import java.io.File;

public class Demo01_FileConstructor {
    public static void main(String[] args) {
        // 1. 创建出一个File类型对象 : 就表示一个文件或者一个文件夹
        // 1) File(String path)
        File f = new File("D:\\0308系统班");
        System.out.println(f);// D:\0308系统班
        // 2) File(String fu,String zi) : 将fu+zi拼接成一个完整的路径
        File f1 = new File("D:\\0308系统班","day02");// D:\0308系统班\day02
        System.out.println(f1);
        // 3) File(File fu,String zi) : 将fu+zi拼接成一个完整的路径
        File f2 = new File(f1,"笔记\\JAVASE_day02.docx");
        System.out.println(f2);// D:\0308系统班\day02\笔记\JAVASE_day02.docx
    }
}
File类型的创建方法
1、boolean createNewFile()://创建当前File对象所描述的路径的文件
2、boolean mkdir()://创建当前File对象所描述的路径的文件夹(如果父级路径不存在,那么不会自动创建父级路径)
3、boolean mkdirs()://创建当前File对象所描述的路径的文件夹(如果父级路径不存在,那么自动创建父级路径)
import java.io.File;
import java.io.IOException;

public class Demo02_FileCreate {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个File类型对象,表示文件
        File f = new File("D:\\abc.txt");
        // 2. 调用createNewFile() : 创建当前File对象所描述的路径的文件
        // 注意 : 文件所在文件夹路径需要存在; 文件不存在
        boolean boo = f.createNewFile();
        System.out.println(boo);

        // 3. 创建出一个File类对象, 表示一个文件夹
        File f2 = new File("D:\\myDirectory");
        // 4. mkdir() :创建当前File对象所描述的路径的文件夹
        // (如果父级路径不存在,那么不会自动创建父级路径)
        boolean boo1 = f2.mkdir();
        System.out.println(boo1);

        // 5. 一次性需要创建出多级文件夹
        // mkdirs() : 创建当前File对象所描述的路径的文件夹
        // (如果父级路径不存在,那么自动创建父级路径)
        File f3 = new File("D:\\a\\b\\h\\myDirectory2");
        boolean boo2 = f3.mkdirs();
        System.out.println(boo2);
    }
}
File类型的删除方法

1、

delete()://删除调用者描述的文件或者文件夹, 文件存在或者文件夹为空才能删除成功

2、注意事项:

  1. delete在删除文件夹的时候,只能删除空文件夹, 是为了尽量保证安全性

  2. delete方法不走回收站

import java.io.File;
public class Demo03_FileDelete {
    public static void main(String[] args) {
        // 1. 创建出一个存在的文件, 删除, 成功
        File f = new File("D:\\abc.txt");
        boolean boo = f.delete();
        System.out.println(boo);

        // 2. 创建出一个存在的,空的文件夹,删除, 成功
        File f1 = new File("D:\\myDirectory");
        System.out.println(f1.delete());

        // 3. 创建出一个存在的, 非空文件夹, 删除, 失败
        File f2 = new File("D:\\a");
        System.out.println(f2.delete());
    }
}
File类型常用的判断功能
1、exists()://判断当前调用者File对象所表示文件或者文件夹,是否真实存在, 存在返回true,不存在返回false

2、isFile()://判断当前调用者File对象,是否是文件

3、isDirectory()://判断当前调用者File对象,是否是文件夹
import java.io.File;

public class Demo04_FileCheck {
    public static void main(String[] args) {
        // idea中给出相对路径: 不带有盘符的路径
        // 默认给提供的路径添加一个项目工程根目录作为完整路径
        File f = new File("myFirstFile.txt");
        System.out.println(f.exists());// true
        System.out.println(f.isFile());// true
        System.out.println(f.isDirectory());// fasle

        System.out.println("-----------------");

        File f1 = new File("D:\\a");
        System.out.println(f1.exists());// true
        System.out.println(f1.isFile());// false
        System.out.println(f1.isDirectory());// true

        File f2 = new File("Q:\\a");
        System.out.println(f2.exists());// false
    }
}
File类型的获取功能
1、String getAbsolutePath()://获取当前File对象的绝对路径
2、String getPath()://获取的就是在构造方法中封装的路径
3、String getName()://获取最底层的简单的文件或者文件夹名称(不包含所造目录的路径)
4、String[] list()://获取当前文件夹下的所有文件和文件夹的名称,到一个字符串数组中
5、File[] listFiles()://获取当前文件夹下的所有文件和文件夹的File对象,到一个File对象数组中
import java.io.File;
import java.util.Arrays;

public class Demo05_FileGet {
    public static void main(String[] args) {
        File f = new File("D:\\a");
        // 1) String getAbsolutePath():获取当前File对象的绝对路径
        String s = f.getAbsolutePath();
        System.out.println(s);// D:\a

        // D:\IdeaProjects\project\myFirstFile.txt
        File f1 = new File("myFirstFile.txt");
        System.out.println(f1.getAbsolutePath());

        // 2) String getPath():获取的就是在构造方法中封装的路径
        System.out.println(f.getPath());// D:\a
        System.out.println(f1.getPath());// myFirstFile.txt

        // 3) String getName():获取最底层的简单的文件或者文件夹名称(不包含所造目录的路径)
        System.out.println(f.getName());// a
        System.out.println(f1.getName());// myFirstFile.txt

        // 4) String[] list():获取当前文件夹下的所有文件和文件夹的名称,到一个字符串数组中
        File f2 = new File("D:\\0308系统班\\day01");
        String[] sArr = f2.list();
        System.out.println(Arrays.toString(sArr));

        // 5) File[] listFiles():获取当前文件夹下的所有文件和文件夹的File对象,到一个File对象数
        File[] fileArr = f2.listFiles();
        System.out.println(Arrays.toString(fileArr));
    }
}

IO流

1.IO流介绍:

​ I和O,分别是Input和Output两个单词的缩写,Input是输入,Output是输出。

​ 流:是一种抽象概念,是对数据传输的总称.也就是说数据在设备间的传输称为流,流的本质是数据传输。

​ IO流就是用来处理设备间数据传输问题的。常见的应用: 文件复制、文件上传、 文件下载等。
image

2.IO流分类:

​ 按照数据的流向:

​ 输入流:读数据

​ 输出流:写数据

​ 按照数据类型:

​ 字节流:

​ 字节输入流和字节输出流

​ 字符流:

​ 字符输入流和字符输出流

字节流和字符流的使用场景:

​ 如果操作的是纯文本文件,优先使用字符流

​ 如果操作的是图片、视频、音频等二进制文件,优先使用字节流

​ 如果不确定文件类型,优先使用字节流.字节流是万能的流

3.IO流程序书写流程:

​ 1、在操作之前,要导包,java.io包

​ 2、在操作流对象的时候,要处理解决异常(IOException)

​ 3、在操作完流对象之后,必须关闭资源, 所有流资源的关闭 close();

字节流

字节流抽象基类: 以字节读写文件

	InputStream://这个抽象类是表示字节输入流的所有类的超类

	OutputStream://这个抽象类是表示字节输出流的所有类的超类

根据交互设备的不同,有不同的具体子类

字节输入流FileInputStream

1、FileInputStream是InputStream一个具体子类,用于和磁盘上的文件进行交互

2、FileInputStream不仅可以一次读取一个字节,也可以一次读取很多个字节;不仅可以读取纯文本文件,也可以读取图片、视频、音频等非纯文本文件。一切数据在计算机中都是以字节的形式在存储和计算

3、构造方法:

 FileInputStream(File f)://将一个File对象所表示的文件路径封装在一个字节输入流中,未来从文件中以字节方式读取文件内容

 FileInputStream(String path)://将一个字符串所表示的文件路径封装在一个字节输入流中,未来从文件中以字节方式读取文件内容

注意事项:无论是哪个构造方法,都只能封装文件的路径,封装文件夹的路径没有任何意义,因为文件夹本身没有任何数据,所以也不能使用流对象读取数据

  1. 读取文件的方法:
int read()://从当前的字节输入流中,读取并返回一个字节,返回值结果int类型, 表示读取到的字节对应的整数结果, 如果返回-1表示证明文件读取完毕

int read(byte[] arr)://将最多arr.length个字节,读取到的字节放置到arr中,返回值结果int类型, 表示本次读取到的字节的个数, 如果读到-1,证明文件读取完毕
注意 : 数组读取效率远远优于单个字节读取效能
void close():关闭该流对象

image

单个字节读取代码

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Demo01_FileInputStream {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个字节输入流,绑定数据源
        FileInputStream fis = new FileInputStream("D:\\a\\1.txt");
        // FileInputStream fis2 = new FileInputStream(new File("D:\\a\\1.txt"));
        //2. 读取文件:
        /*int first = fis.read();
        System.out.println(first + "--" + (char)first);// a

        int second = fis.read();
        System.out.println(second + "--" + (char)second);// b

        int third = fis.read();
        System.out.println(third + "--" + (char)third);// c

        int four = fis.read();
        System.out.println(four + "--" + (char)four);// d

        int five = fis.read();
        System.out.println(five + "--" + (char)five);// e

        int six = fis.read();
        System.out.println(six + "--" + (char)six);// f

        int end = fis.read();
        System.out.println(end + "--" + (char)end);// -1*/

        // len表示每次读取到的字节的整数结果
        int len;
        while((len = fis.read()) != -1){
            System.out.print((char)len);
        }

        // 使用完毕流资源资源关闭
        fis.close();
    }
}

字节数组读取文件

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;

public class Demo02_FileInputStreamReadArr {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个字节输入流, 绑定数据源
        FileInputStream fis = new FileInputStream("D:\\a\\1.txt");
        // 2. 定义出一个字节数组, 用于存储从文件中读取出的字节数据
        // 注意 : 实际开发中, 通常数组大小设计成1024的倍数
        // 1024   1024 * 4   1024 * 8
        byte[] b = new byte[4];
        // 3. 2)int read(byte[] arr):将最多arr.length个字节,
        // 读取到的字节放置到arr中,返回值结果int类型,
        // 表示本次读取到的字节的个数, 如果读到-1,证明文件读取完毕
        //注意 : 数组读取效率远远优于单个字节读取效能
        /*int i = fis.read(b);
        System.out.println("读取到了"+i + "个字节");
        System.out.println(new String(b));// abcd

        int j = fis.read(b);
        System.out.println("读取到了"+j + "个字节");
        System.out.println(new String(b));// efcd

        int z = fis.read(b);
        System.out.println("读取到了"+z + "个字节");
        System.out.println(new String(b));*/

        // len表示每次读取到的字节的个数
        int len;
        while((len = fis.read(b)) != -1){
            // 查看数组中内容,将字节数组转换成字符串
// 查看b数组中的内容,从0索引开始,查看len个字节
            System.out.print(new String(b,0,len));
        }

        // 关闭资源
        fis.close();
    }
}
字节输出流FileOutputStream
  1. 说明:可以将字节数据写出到指定的文件中

  2. 构造方法:

FileOutputStream(File f)://将f描述的路径文件封装成字节输出流对象

FileOutputStream(String path)://将path描述的文件路径封装成字节输出流对象

FileOutputStream(String path,boolean append)://如果第二个参数为true,则字节将写入文件的末尾而不是开头

FileOutputStream(File path,boolean append)://如果第二个参数为true,则字节将写入文件的末尾而不是开头
  1. 字节流写数据的方式:
void write(int b): 将指定的字节写入此文件输出流一次写一个字节数据

void write(byte[] b): 将b.length字节从指定的字节数组写入此文件输出流

void write(byte[] b, int off, int len): 将len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流
  1. 字节流写数据实现换行
windows:\r\n
linux:\n
mac:\r
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo03_FileOutputStream {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个字节的输出流, 绑定一个数据目的
        FileOutputStream fos = new FileOutputStream("myFirstFile.txt");
        // 2) write(int b): 将指定的字节写入此文件输出流一次写一个字节数据
        fos.write(97);// a
        // 3)write(byte[] b): 将b.length字节从指定的字节数组写入此文件输出流
        byte[] b = {65,66,67,68};
        fos.write(b);// ABCD
        // 4) void write(byte[] b, int off, int len):
        // // 将len个字节从指定的字节数组开始,从索引off开始写入此文件输出流
        // 将b数组中从1索引位置开始, 写2个字节到文件中
        fos.write(b,1,2);
        // 5) 向文件中写入字符串
        fos.write("今天天气阴,明天要下雨".getBytes());

        // 关闭输出流资源 : 资源关闭之后, 不能使用
        fos.close();
    }
}
追加写入和加入回车换行代码
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo04_FileOutputStream2 {
    public static void main(String[] args) throws IOException {
        // 向文件中追加写入数据(从源文件末尾继续写)
        FileOutputStream fos = new FileOutputStream("myFirstFile.txt",true);
        // 向文件中接入一个回车换行
        fos.write("\r\n".getBytes());

        fos.write("我是后追加内容".getBytes());
        fos.close();
    }
}
文件复制

image

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo05_CopyPicture {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个字节输入流, 绑定一个数据源
        FileInputStream fis = new FileInputStream("D:\\a\\图片1.png");
        // 2. 创建出一个字节输出流, 绑定一个数据目的
        FileOutputStream fos = new FileOutputStream("D:\\copy1.png");
        // 3. 边读边写
        // len表示每次读取到的字节对应的整数结果
        long begin =  System.currentTimeMillis();
        int len;
        while((len = fis.read()) != -1){
            // 向目标文件中写入数据
            fos.write(len);
        }
        long end =  System.currentTimeMillis();
        System.out.println(end - begin);// 453


        // 4. 使用字节数组进行图片的复制,目的就是为了加快复制的效率
       /* byte[] b = new byte[1024 * 4];
        // len表示每次读取到的字节的个数
        long begin =  System.currentTimeMillis();
        int len;
        while((len = fis.read(b)) != -1){
            // 特别在文件复制, 使用数组, 记得, 读到什么就写入什么
            // 读到几个字节, 就写入几个字节
            fos.write(b,0,len);
        }
        long end =  System.currentTimeMillis();
        System.out.println(end - begin);// 1*/
        fos.close();
        fis.close();
    }
}
缓冲(高效)字节流
  1. 字节缓冲流介绍:
BufferedOutputStream://是OutputStream的子类, 表示高效字节输出流。流资源在创建对象时, 会在代码底层默认创建出一个大小为8192(1024 * 8)的字节数组, 提供数组缓冲区, 如何按照字节数组进行文件写, 那么效率高

BufferedInputStream://是InputStream的子类, 表示高效字节输入流。流资源创建对象时, 会带代码底层默认创建出一个大小为8192(1024 * 8)的字节数组, 接下来通过字节数组进行文件内容的读, 如此提高效率

​ 注意:这两个流是包装类型:本身不具备读写的功能,只是在某个具体的流对象的基础上,对其进行加强,例如FileInputStream和FileOutputStream,原本效率较低,加强之后,就效率较高。

  1. 构造方法
BufferedOutputStream(OutputStream out): //创建字节缓冲输出流对象

BufferedInputStream(InputStream in): //创建字节缓冲输入流对象
缓冲字节流实现原理
  1. FileOutputStream和BufferedOutputStream原理分析:

BufferedOutputStream高效的原理:在该类型中准备了一个数组,存储字节信息,当外界调用write方法想写出一个字节的时候,该对象直接将这个字节存储到了自己的数组中,而不刷新到文件中。一直到该数组所有8192个位置全都占满,该对象才把这个数组中的所有数据一次性写出到目标文件中。如果最后一次循环过程中,没有将数组写满,最终在关闭流对象的时候,也会将该数组中的数据刷新到文件中。

  1. FileInputStream和BufferedInputStream原理分析:

BufferedInputStream高效的原理:在该类型中准备了一个数组,存储字节信息,当外界调用read()方法想获取一个字节的时候,该对象从文件中一次性读取了8192个字节到数组中,只返回了第一个字节给调用者。将来调用者再次调用read方法时,当前对象就不需要再次访问磁盘,只需要从数组中取出一个字节返回给调用者即可,由于读取的是数组,所以速度非常快。当8192个字节全都读取完成之后,再需要读取一个字节,就得让该对象到文件中读取下一个8192个字节了。

import java.io.IOException;

public class Demo01_BufferedStream {
    public static void main(String[] args) throws IOException{
        BufferedInputStream bis = new BufferedInputStream(
            new FileInputStream("D:\\0308系统班\\day19\\视频\\01.昨日内容回顾.mp4"));

        BufferedOutputStream bos = new BufferedOutputStream(
            new FileOutputStream("D:\\copy.mp4"));

        long begin = System.currentTimeMillis();
        int len;
        while((len = bis.read()) != -1){
                bos.write(len);
        }
        long end = System.currentTimeMillis();
        System.out.println(end - begin);

        bis.close();
        bos.close();
    }
}
flush()方法和close()方法区别
  1. close方法会先调用flush方法

  2. close方法用于流对象的关闭,一旦调用了close方法,那么这个流对象就不能继续使用了

  3. flush只是将缓冲区中的数据,刷新到相应文件中,而不会将流对象关闭,可以继续使用这个流对象。但是如果flush方法使用过于频繁,那么丧失了缓冲区的作用。

import java.io.*;

public class Demo02_BufferedStreamCopyTxt {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个高效字节输入流
        BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream("Info.txt")
        );

        // 2. 创建出一个高效字节输出流
        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("InfoCopy.txt")
        );

        // 3. 边读边写
        int len;
        while((len = bis.read()) != -1){
            bos.write(len);
        }
        /*
            问题:
               使用带有底层数组缓冲的流资源时, 如果没有刷新过资源, 也没有关闭资源, 那么流资源目标结果可能有问题, 例如 : 数据没有同步到文件中
            问题发生原因:
               BufferedOutputStream,将数据写入到底层的8192的字节数组中, 写入的数据因为不够8192,因此没有自动将数据同步到文件中,仍然还是存储在底层的数组中
            解决问题的办法
               1) flush() : 表示刷新流资源, 将当前流资源底层数组缓冲中的所有数据同步到文件中
               2) close() : 因为每次流资源关闭之前, 默认先调用一次flush方法功能,将所有存在于底层数组缓冲中的数据先同步到文件中, 然后再关闭流资源的使用权利

               flush和close方法的不同:
               flush刷新资源之后, 流还能使用
               close关闭资源之后, 流不能使用
         */

        // 5. 刷新流资源
        // bos.flush();

        // 4. 关闭资源,必须要做,在流资源使用结束之后
        bis.close();
        bos.close();

        bos.write("你好".getBytes());
        bos.flush();
    }
}
IO中保证流对象关闭的标准格式
  1. 捕获异常和声明异常的原则

    1. 如果知道如何处理异常,那么就使用try...catch来进行处理;如果不知道如何处理异常,那么就使用throws来对异常进行声明,或者将当前的异常包装成其他的异常类型,进行声明,因为越到代码的高层,拥有更多的资源、更高的位置和权力,知道如何处理,底层被调用的方法,虽然是出现异常的位置,但是并不知道如何处理。
    2. 如果你希望程序出现异常之后,继续运行下去,那么就使用try...catch;如果出现异常之后,希望当前方法的代码停止运行,那么就使用throws
  2. IO中保证流对象关闭的格式(jdk1.7之前)

   try{
	流对象的使用;
}catch(异常类名 变量名){
	异常的处理代码;
}finally{
	关闭流资源;
}
  1. IO中保证流对象关闭的格式(jdk1.7之后)
   try (
   	流对象的创建;
) {
	流对象的使用;
}catch(IOException e){
	异常处理方式;
}

特点:

​ 流对象使用之后,不需要手动关闭,因为这个格式中的小括号会在流资源使用完毕后,自动关闭了流对象.

JDK1.7之前标准异常处理方式

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo03_StreamExceptionDeal {
    public static void main(String[] args) {
        // 案例 : 普通字节输入输出流进行文件的复制案例
        FileInputStream fis = null;
        FileOutputStream fos = null;

        try{
            fis = new FileInputStream("myFirstFile.txt");
            fos = new FileOutputStream("myCopy.txt");
            int len;
            while((len = fis.read()) != -1){
                fos.write(len);
            }
        }catch(IOException e){// 利用多态捕获一个父类的异常即可
            e.printStackTrace();
        }finally{
            try {
                if(fis != null){
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    if(fos != null){
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

JDK1.7之后优化的IO流资源异常处理代码

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo04_StreamException2 {
    public static void main(String[] args) {
        /*
           新的IO流资源的异常处理语法结构 : 需要在JDK1.7版本以及以后才能使用
           try(
              需要使用流资源的对象创建;// 小括号作用: 当流资源使用完毕,自动关闭流资源
           ){
              对于流资源的使用过程;
           }catch(异常类型 e){
              异常处理方式;
           }

         */
        try(
                FileInputStream fis = new FileInputStream("myFirstFil.txt");
                FileOutputStream fos = new FileOutputStream("myCopy.txt");
         ){
                int len;
                while((len = fis.read()) != -1){
                    fos.write(len);
                }

        }catch(IOException e){
             // e.printStackTrace();
            System.out.println("我已经将异常抓住");
        }

        System.out.println("end code");
    }
}
字符流
使用字节流处理字符的问题
  1. 使用字节流写字符

可以使用,但是需要先把字符串转成字节数组,再存储到文件中,比较麻烦

  1. 使用字节流读取字符

如果是纯英文,可以一次读取一个字节

如果是纯中文,可以一次读取两个字节(GBK)

如果是中英文混杂,每次不知道读取多少个字节,因此无论字节数组准备多大,都会出现乱码

  1. 解决方案:

    1. 由于字节流操作中文不是特别的方便,所以Java就提供字符流。
    2. 字符流 = 字节流 + 编码表
    3. 中文的字节存储方式:

    用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?

    人类的所有语言文字,都可以分为一个一个的字符存在, 每一个国家对于这些字符的使用,都会有对应的编码表

    所有编码表,底层都兼容ASCII编码表(美国标准信息交换码表,包含: 0-127个字节,每一个字节对应的符号, 数字, 大写字母, 小写字母); 在此基础上,还会将自己国家语言文字对应成数字的形式,为了让计算机识别人类语言

    中国也有自己编码表:

    GBK(中国标准信息表换码表) : 一个中文在内存中占有2个字节

    UFT-8 (万国码表,包含了所有国家的语言文字字符组成): 一个中文在内存中占有3个字节

    不论哪种编码表, 中文文字, 第一个字节都是负数, 字符流在进行文件读取时, 先从文件中读取出一个字节, 判断, 这个字节是否为正数, 认为来自于ASCII, 直接通过编码表,将读取到的字节数据,转换成字符; 如果读取到的第一个字节为负数, 那么继续向下读取, 根据编码表的不同, 决定接下来, 是读取出2或者3个字节, 将这些字节的对应的数据结果参考编码表转换成对应的语言文字(字符)

字符流的使用
字符流写数据方式
  1. 字符流输出流介绍:
	Writer: //用于写入字符流的抽象父类

	FileWriter: //用于写入字符流的常用子类
  1. FileWriter构造方法
FileWriter(File file): //根据给定的 File 对象构造一个 FileWriter 对象

FileWriter(File file, boolean append): //根据给定的 File 对象构造一个 FileWriter 对象

FileWriter(String fileName): //根据给定的文件名构造一个 FileWriter 对象

FileWriter(String fileName, boolean append)//根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象
  1. 写的方法:
void write(int c): //写一个字符

void write(char[] cbuf): //写入一个字符数组

void write(char[] cbuf, int off, int len): //写入字符数组的一部分

void write(String str): //写一个字符串

void write(String str, int off, int len)//写一个字符串的一部分
import java.io.FileWriter;
import java.io.IOException;

public class Demo03_FileWriter {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个字符输出流,绑定一个数据目的
        FileWriter fw = new FileWriter("chinese.txt",true);
        fw.write("\r\n");
        // 1) 每次写入一个字符  write(int i)
        fw.write('A');
        fw.write(97);
        // 2) 向文件中写入字符数组
        char[] ch = {'1','?','y','国'};
        fw.write(ch);
        // 3) 向文件中写入字符数组的一部分
        fw.write(ch,0,2);// 1?
        // 4) 向文件中写入字符串
        String s = "今天下雨了";
        fw.write(s);
        // 5) 向文件中写入字符串的一部分
        fw.write(s,2,2);// 下雨
        // 必须关闭资源
        fw.close();
    }
}
字符流读数据方式
  1. 字符流输入流介绍:
	Reader: //用于读取字符流的抽象父类

	FileReader: //用于读取字符流的常用子类
  1. FileReader构造方法

    FileReader(File file): 在给定从中读取数据的 File 的情况下创建一个新 FileReader
    
    FileReader(String fileName): 在给定从中读取数据的文件名的情况下创建一个新 FileReader
    
  2. 读的方法:

    1. int read(): 一次读一个字符数据,返回值结果是int类型, 返回的就是这个字符在编码表中对应的整数结果,如果读到-1,证明文件读取完毕
    2. int read(char[] ch): 一次最多读一个字符数组数据, 将从文件中读取出的字符放置在参数ch数组中, 返回值结果int类型, 表示每次从文件中读取到的字符的个数, 如果读取到-1,证明文件读取完毕
import java.io.FileReader;
import java.io.IOException;

public class Demo02_FileReader {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个字符输入流, 绑定一个数据源
        FileReader fr = new FileReader("chinese.txt");
        int len;
        while((len = fr.read()) != -1){
            System.out.print((char)len);
        }
        fr.close();
    }
}
字符流的拷贝
  1. 纯文本文件概念 : 可以使用txt记事本方式打开的,并且可以读懂的文件

  2. 字符流复制结论:

    1. 字符流,可以复制纯文本文件, 但是效率不高, 因此对于文件的复制,通常采用字节流
    2. 字符流,不能复制非纯文本文件
    3. 实际开发中, 带有中文的文件, 类型一般都是记事本

    举例 : 实际开发中有产品文件, 可以将多个产品信息写在一个文件中, 代码可以进行批量的产品信息新增, 要求客户将产品信息写在文件中, 代码读取文件, 解析文件, 将每一个产品信息获取到, 同步到数据库中

产品编号 产品名称 产品单价 产品数量 产品成分 产品所属公司

100001||小金猴||3999||2||AU999||李老师黄金行

100002||纪念章||899.99|| 5|| AU999||深圳黄金

  1. 字符流复制纯文本效率低原理:
    image

4.字符流不能复制非纯文本文件原理:
image

复制纯文本代码

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Demo05_字符流复制文本文件 {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("nameAndPass.txt");
        FileWriter fw = new FileWriter("nameAndPassCopy.txt");
        int len;
        while((len = fr.read()) != -1){
            fw.write(len);
        }

        fr.close();
        fw.close();
    }
}

字符流复制非纯文本失败

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Demo06_字符流复制图片失败 {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("D:\\a\\stream.png");
        FileWriter fw = new FileWriter("D:\\copyFail.png");
        int len;
        while((len = fr.read()) != -1){
            fw.write(len);
        }

        fr.close();
        fw.close();
    }
}
字符(高效)缓冲流

1、

BufferedReader和BufferedWriter

2、使用:

创建了高效缓冲流对象之后,使用和加强之前一样的方式,BufferedWriter是Writer的子类,BufferedReader是Reader的子类,所以可以继续使用在抽象类中定义的各种方法。

3、高效的原因:

BufferedReader:每次调用read方法,只有第一次从磁盘中读取了8192个字符,存储到该类型对象的缓冲区数组中,将其中一个返回给调用者,再次调用read方法时,就不需要再访问磁盘,直接从缓冲区中拿一个出来即可,效率提升了很多。

BufferedWriter:每次调用write方法,不会直接将字符刷新到文件中,而是存储到字符数组中,等字符数组写满了,才一次性刷新到文件中,减少了和磁盘交互的次数,提升了效率

import java.io.*;

public class Demo07_字符高效缓冲流 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(
                new FileReader("Info.txt")
        );

        BufferedWriter bw = new BufferedWriter(
                new FileWriter("InfoCopy.txt")
        );

        int len;
        while((len = br.read()) != -1){
            bw.write(len);
        }
        br.close();
        bw.close();
    }
}
高效缓冲字符流的特有方法

1、BufferedReader:

 readLine()://可以从输入流中,一次读取一行数据,返回一个字符串,如果到达文件末尾,则返回null

2、BufferedWriter:

 newLine()://换行。在不同的操作系统中,换行符各不相同,newLine方法就是可以给我们根据操作系统的不同,提供不同的换行符
import java.io.*;

public class Demo08_字符高效流特有方法 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(
                new FileReader("code.txt")
        );

        BufferedWriter bw = new BufferedWriter(
                new FileWriter("codeCopy.txt")
        );

        String s;
        while((s = br.readLine()) != null){
            bw.write(s);
            bw.newLine();
        }

        br.close();
        bw.close();
    }
}

转换流

编码表

1、GBK:国标码,定义的是英文字符和中文字符。在GBK编码表中,英文字符占一个字节,中文字符占两个字节。

2、UTF-8:万国码,定义了全球所有语言的所有符号,定义了这些符号和数字的对应关系,英文字符使用一个字节进行存储,中文字符使用三个字节进行存储

总结 : 转换流的主要作用就是针对于不同编码集的文件,有正确的读写过程

1、OutputStreamWriter:字符流到字节流的桥梁,可以指定编码形式

 构造方法:OutputStreamWriter(OutputStream os, String charSetName)

 创建一个转换流对象,可以把将来方法中接收到的字符,通过指定的编码表charSetName,编码成字节信息,再通过指定的字节流os,将字节信息写出

 使用:直接使用Writer中的方法即可(该类是Writer的子类)
2、InputStreamReader:字节流到字符流的桥梁,可以指定编码形式

 构造方法:InputStreamReader(InputStream is, String charSetName)

 创建一个转换流对象,可以使用is这个指定的字节流,从磁盘中读取字节信息,通过指定的编码表charSetName,将字节信息解码成字符信息,返回给调用者

 使用:直接使用Reader类中的方法即可(该类是Reader的子类)
3、说明:
 无论是读取的时候,还是写出的时候,都需要参考读取文件和目标文件的编码形式
 读取源文件时,解码的形式必须和源文件的编码形式一致
 写出到目标文件时,编码形式必须和目标文件的编码形式一致

转换流的实现原理如下图:
image

import java.io.*;

public class Demo01_转换流 {
    public static void main(String[] args) throws IOException {
        // 1. 创建出一个转换输入流, 绑定一个源文件
        InputStreamReader isr = new InputStreamReader(
                new FileInputStream("D:\\0308系统班\\chinese.txt"),"GBK"
        );

        // 2. 创建出一个转换输出流, 绑定一个数据目的
        OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("chineseCopy.txt"),"UTF-8"
        );

        int len;
        while((len = isr.read()) != -1){
             osw.write(len);
        }

        osw.close();
        isr.close();
    }
}
标准输入输出流
  1. System.in : 标准输入流,返回值类型是InputStream (字节流)

分类:字节输入流

设备:默认关联键盘

对标准输入流关联的路径进行修改:setIn(InputStream is), 来自于System类中, 可以改变标准输入流的数据来源

该流对象不需要关闭

  1. System.out : 标准输出流,返回值类型PrintStream (打印流)

分类:打印字节流

设备:默认关联到控制台

对标准输出流关联的路径进行修改:setOut(PrintStream ps)

注意 : System.in主要使用的默认的键盘录入场景上, 例如: Scanner键盘默认录入数据System.out: 默认在控制台进行数据的输出, 主要关注的就是PrintStream中的print和println系列方法功能, 将任意的数据类型转换成字符串进行输出, 默认在控制台, 实际开发中, 通过System.out可以查看代码的运行结果, 自我测试最主要的使用场景

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Scanner;

public class Demo02_标准输入流 {
    public static void main(String[] args) throws FileNotFoundException {
        InputStream is = System.in;
        // 1. 修改流资源的数据来源
        // is.setIn();
        // 将标准的输入流数据来源从默认的键盘录入改成了student.txt来源
        System.setIn(new FileInputStream("student.txt"));
        Scanner sc = new Scanner(System.in);
        // int number = sc.nextInt();
        String s = sc.next();
        System.out.println(s);
        System.out.println(sc.next());

        // 2. System.out: 默认通过控制台进行输出
        PrintStream ps = System.out;
        ps.print('A');
        ps.println(12);

        // 将标准的输出流输出目的有默认的控制台改成chineseCopy.txt文件
        PrintStream ps1 = new PrintStream("chineseCopy.txt");
        System.setOut(ps1);
        ps1.println("我在家");
    }
}

打印流

PrintStream打印字节流,PrintWriter打印字符流,这两个流都属于输出流。

​ 这两个流中,除了继承各自父类中的write()方法外,还提供了很多重载形式的print()和println()方法,可以很方便的将各种数据类型以字符串的形式进行输出(会将所有数据类型,先转成字符串,然后进行输出),这两个方法也是打印流特有的方法,对于在使用这两个方法的方式上,这两个流没有区别,print()写出的数据末尾没有行终止符,println()写出的数据末尾有行终止符。

对象序列化

对象流: 将一个对象写入到文件中(序列化),或者将文件中的对象获取到(反序列化)的过程

序列化: 就是指将对象写入到文件中的过程

反序列化: 将对象和对象中的数据从文件中获取到

对象: 表示任意对象,Object的子类,都可以写

作用: 例如: 实际开发场景中,Person类,创建出(new)很多的个对象,正常对象的信息需要保存到数据库中,但是,也可以保存在文件中,节省数据库的空间

举例: 玩游戏,英雄联盟,角色(对象),角色有功能,技能,皮肤, 角色升级,第一次玩结束,于是角色需要被保存下来(序列化); 下次再玩,读档,角色还能重新使用,数据不变(反序列化)

对象输出流的介绍
ObjectOutputStream : //对象的输出流,是OutputStream的子类

功能: 将对象写入到文件中

构造方法:

ObjectOutputStream (OutputStream o): //需要绑定一个数据源,向文件中写入对象

常用方法:

writeObject(Object obj): //向文件中写入obj对象

注意事项:

  1. 写入文件的对象,必须要实现Serializable序列化接口,如果没有实现接口,会报错

    java.io.NotSerializableException

注意: 需要被序列化和反序列化的对象,必须要实现Serializable接口, 否则不能序列化和反序列化.Serializable接口是一个标志性的接口,里面没有任何方法

类比记忆: 比如买猪肉,猪肉上面的戳,就表示肉是经过检测,安全的,可以吃的,那这个戳,就相当于实现了Serializable接口,没有实质的用途,却可以表示该猪肉的安全特质

  1. 对象写到文件中,内容是无法读懂的,因为将对象转换成字节存储,因此无法识别,只要在进行反序列化的时候,能够将对象成功获取到即可.
import java.io.Serializable;
// 注意 : 实现类Serializable序列化接口的类型才能进行序列化和反序列化操作
public class Person implements Serializable {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
import java.io.*;

public class Demo03_对象序列化 {
    public static void main(String[] args) throws IOException {
        writePerson();
    }

    // 1. 定义出功能 : 将对象写入到文件中(序列化过程)
    public static void writePerson() throws IOException {
        // 1) 创建出一个对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
        // 2) 创建出一个Person类型对象
        Person p = new Person("张三",20);
        Person p1 = new Person("李四",18);
        // 3) 需要将对象p写入到指定的文件中
        oos.writeObject(p);
        oos.writeObject(p1);
        oos.close();
    }
  }
对象输入流的介绍
ObjectInputStream : //对象的输入流,是InputStream的子类
功能: 能将对象从文件中读取到
构造方法:
ObjectInputStream(InputStream in): //从某个指定的文件(序列化对象的文件)中读取到对象
常用方法:
readObject(): //将对象从文件中读取出来,每次读取出一个对象,返回值类型是Object
//如果文件中没有对象,还要继续进行对象读取,会抛出
EOFException : End Of File Excepttion //文件到达结束位置,不能再进行对象的获取了
import java.io.*;

public class Demo03_对象序列化 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // writePerson();
        readPerson();
    }

    // 1. 定义出功能 : 将对象写入到文件中(序列化过程)
    public static void writePerson() throws IOException {
        // 1) 创建出一个对象输出流
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
        // 2) 创建出一个Person类型对象
        Person p = new Person("张三",20);
        Person p1 = new Person("李四",18);
        // 3) 需要将对象p写入到指定的文件中
        oos.writeObject(p);
        oos.writeObject(p1);
        oos.close();
    }

    // 2. 定义出功能: 将对象从文件中读取出来(反序列化过程)
    public static void readPerson() throws IOException, ClassNotFoundException {
        // 1) 创建出一个对象输入流
        ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("person.txt")
        );
        // 2. 使用readObject()方法, 每次从文件中读取出一个对象
        Person p = (Person)ois.readObject();
        Person p1 = (Person)ois.readObject();
        // EOFException: End Of File Exception
        // 当文件中已经没有序列化对象之后,仍然使用readObject()继续获取对象
        // 那么报出: 文件到达结束异常
        Person p2 = (Person)ois.readObject();
        System.out.println(p.getName() + "---" + p.getAge());
        System.out.println(p1.getName() + "---" + p1.getAge());
        System.out.println(p2.getName() + "---" + p2.getAge());
        /*Object obj;
        while((obj = ois.readObject()) != null){
            Person p = (Person)obj;
            System.out.println(p.getName() + "---" + p.getAge());
       }*/

        ois.close();
    }
}
将对象存储在集合中进行序列化和反序列化

​ 反序列时 : 将对象从文件中读取,不知道文件中有几个对象, 于是获取对象次数多了, 报出EOFException, 文件到达末尾异常

​ 可以出一个优化方案: 将多个对象放置到一个集合中, 将这个集合对象写入到文件中,实现序列化过程; 读取文件的时候,就读一次, 将集合对象获取出来, 遍历集合相当于将集合中所有对象都获取到. 如此可以避免掉,不知道文件中有多少对象从而报出的异常问题

package com.ujiuye.stream;

import java.io.*;
import java.util.ArrayList;

public class Demo04_序列化优化 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // writePersonList();
        readPersonList();
    }

    // 1. 定义出方法: 进行多个对象放置在集合中的序列化过程
    public static void writePersonList() throws IOException {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("张三丰",21));
        list.add(new Person("李二狗",17));
        list.add(new Person("娃哈哈",15));
        list.add(new Person("大户好",19));

        // 1. 创建出一个对象输出流, 将集合list进行序列化
        ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream("personList.txt")
        );

        // 2. 序列化集合
        oos.writeObject(list);
        oos.close();
    }

    // 2. 定义出方法 : 反序列化list集合, 获取到集合中的所有对象
    public static void readPersonList() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("personList.txt")
        );
        ArrayList<Person> list = (ArrayList<Person>)ois.readObject();
        for(Person per : list){
            System.out.println(per.getName() + "---" + per.getAge());
        }
        ois.close();
    }
}
序列号serialVersionUID和transient关键字

类每次被编译成.class文件的时候,都有一个版本号 long数字

类有改动,重新编译成.class,生成了一个新的版本号 long 数字

两次数字不相等

进行对象的序列化,记录class文件的版本号 long数字

反序列化的时候,检测Person.class的版本号与序列化时的版本是否一致,如果不一致,报错. 无效的类异常

解决方案:

Person类中,添加一个成员,是序列号(版本号), 序列号是一个固定的值,不管该不该代码,序列号都是一样的
private static final long serialVersionUID = 1L;
transient:
	如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
	给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
import java.io.Serializable;
// 注意 : 实现类Serializable序列化接口的类型才能进行序列化和反序列化操作
public class Person implements Serializable {
    // 为每一个序列化的类添加一个不可改变的(固定的)serialVersionUID
    // 为了防止修改类的时候, 不能反序列化问题
    private static final long serialVersionUID = 42L;
    private String name;
    // 如果类型中有哪些成员变量不想要序列化,那么可以使用transient关键字进行成员变量的修饰
    private transient int age;
    // String sex;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
Properties
  1. Properties介绍:

​ 表示一个持久的属性集

​ 属性集:属性名称和属性值的对应关系,其实还是一个双列集合

​ 持久的:可以保存到流中,也可以从流中读取。可以很方便和文件进行交互

​ 是一个Map体系的集合类,是Hashtable的子类,所以可以当做普通的Map来使用

属性列表中的每个键及其对应的值都是一个字符串,因此不需要写泛型

  1. 主要作用: 一般是用于进行配置文件的读写,能将配置文件中的数据,读取到集合中

配置文件中,一般都是键值对的关系(都是String类型)进行存储, 因此Properties中默认存储的键值对类型也是<String,String>

  1. 配置文件中的注释, 使用#表示
#userName 表示用户姓名

userName = mysql

password = 88888
  1. 构造方法: 直接使用空参数的构造即可
Properties集合中的常用方法
put(key k,Value v): //将参数中的键值对存储在Properties集合中,继承自父接口中的方法
setProperty(String key, String value) : //将键值对映射关系添加到集合中,键和值都是String类型,key值不重复,如果重复,后面的value值替掉前面key对象的value值
getProperty(String key): //就是通过制定的key值获取到value值
stringPropertyNames() : //方法是将Properties集合中所有的key值获取到,放置到Set<String>集合中
import java.util.Properties;
import java.util.Set;

public class Demo01_PropertiesMethod {
    public static void main(String[] args) {
        // 1. 创建Properties键值对映射关系对象
        Properties p = new Properties();
        // 1) setProperty(String key,String value):将键值对映射关系添加到集合中,
        // 键和值都是String类型,key值不重复,如果重复,后面的value值替掉前面key对象的value值
        p.setProperty("张三","北京");
        p.setProperty("李四","新加坡");
        p.setProperty("张三","台湾");
        // 2) stringPropertyNames(): 获取到Properties中所有的String类型的key值
        Set<String> set  = p.stringPropertyNames();
        for(String key : set){
            // 3) getProperty(String key): 就是通过制定的key值获取到value值
            String value = p.getProperty(key);
            System.out.println(key + "--" + value);
        }
    }
}
Properties和IO流相结合的方法
1.load(InputStream inStream) : //通过load方法,将InputStream 字节流读取到的配置文件信息,放置到properties的键值对集合(String,String)中
2.load(Reader read): //通过load方法,将Reader 字符流读取到的配置文件信息,放置到properties的键值对集合(String,String)中
3.store(OutputStream out, String comments) : //使用字节的输入流,修改配置文件中的内容,comments 就是针对于本次修改的描述
4.store(Writer out, String comments) : //使用字符的输入流,修改配置文件中的内容,comments 就是针对于本次修改的描述

读取配置文件数据并且修改配置文件内容过程

  1. 先创建一个配置文件, config.properties
    1)配置文件的书写格式(正常的配置文件中,数据都是英文的)

    userName=zhangsan
    age=25
    ​ 2)通过load方法,读取配置文件中的内容到集合中
    ​ 3)修改配置文件的内容

setProperty(String key, String value) : //只是修改了集合中的变量,并没有修改文件内容
store(输出流,”修改原因”) : //将修改同步到配置文件中
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;

public class Demo02_PropertiesReadFile {
    public static void main(String[] args) throws IOException {
         // 1. 创建Properties键值对映射关系对象
         Properties p = new Properties();
         // 2. load方法功能,使用指定流资源.将配置文件中的字符串键值对,直接
         // 存储在Properties集合中
         p.load(new FileReader("config.properties"));
         // 3. 遍历Properties集合,查看读取到的文件内容
         Set<String> setKey = p.stringPropertyNames();
         for(String key : setKey){
             String value = p.getProperty(key);
             System.out.println(key + "--" + value);
         }
         // 4. 通过Properties集合,修改配置文件的内容
         // 1) 先将需要修改的值,设置在Properties键值对中
         p.setProperty("username","hello");
         // 2) store(writer,"修改的原因") : 将集合p中的键值对,同步到文件中
         p.store(new FileWriter("config.properties"),"change name");
    }
}
posted @ 2021-10-27 19:00  昊子豪  阅读(38)  评论(0编辑  收藏  举报