JavaSE 学习笔记07丨IO流

时间:2024-04-30 18:19:22

Chapter 13. IO流

13.1 File类

java.io.File类是文件(file)和目录(文件夹)(directory)路径名(path)的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

一个File对象,代表硬盘中实际存在的一个文件或者目录(文件夹)。

  • 绝对路径:从盘符开始的路径,是完整的路径。eg: D:\\aaa\\233.txt
  • 相对路径:相对于项目目录的路径,是便携的路径(开发常用)。eg: 233.txt

13.1.1 构造方法

无论该路径下是否存在文件或者目录,都不影响File对象的创建。

  • public File(String pathname):通过将给定的路径名字符串pathname转换为抽象路径名,以创建新的File实例。

    String pathname = "D:\\aaa\\233.txt";
    File myfile = new File(pathname);
  • public File(String parent, String child):从父路径名字字符串和子路径名字符串,创建新的File实例。

    String parent = "D:\\aaa";
    String name = "233.txt";
    File myfile = new File(parent, child);
  • public File(File parent, String child):从父抽象路径名和子路径名字符串,创建新的File实例。

    File parentDir = new File("D:\\aaa");
    String child = "233.txt";
    File myfile = new File(parentDir, child);

13.1.2 关于获取信息的方法

  • public String getAbsolutePath():返回此File绝对路径名的字符串。

    File f = new File("E:\\素材\\灵感\\GVLOGO2333332.png");
    System.out.println("文件的绝对路径为:" + f.getAbsolutePath());
    //输出结果为:文件的绝对路径为:E:\素材\灵感\GVLOGO2333332.png File f_2 = new File("灵感\\GVLOGO2333332.png"); //传入相对路径
    System.out.println("文件的绝对路径为:" + f_2.getAbsolutePath()); //自动找到绝对路径,但不一定与实际相符。
    //输出结果为:E:\IDEA-Projects\basic-codes\灵感\GVLOGO2333332.png
  • public String getPath():将此File转换为路径名的字符串。

    File f = new File("E:\\素材\\灵感\\GVLOGO2333332.png");
    System.out.println("文件的构造路径为:" + f.getPath());\
    //输出结果为:文件的构造路径为:E:\素材\灵感\GVLOGO2333332.png File f_2 = new File("灵感\\GVLOGO2333332.png");
    System.out.println("文件的构造路径为:" + f_2.getPath());
    //输出结果为:文件的构造路径为:灵感\GVLOGO2333332.png
  • public String getName():返回由此File表示的文件或目录的名称字符串。

  • public long length():返回由此File表示的文件的大小(以字节为单位)。

13.1.3 关于判断的方法

  • public boolean exists():此File表示的文件或目录是否实际存在。
  • public boolean isDirectory():此File表示的是否为目录
  • public boolean isFile():此File表示的是否为文件

13.1.4 关于创建或删除的方法

  • public boolean createNewFile():当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。当该文件在实际当中并不存在时,返回true(表示创建文件);否则,返回false(不会再创建)。

    File f = new File("233.txt");
    System.out.println("是否存在:" + f.exists()); //false
    System.out.println("是否创建:" + f.createNewFile()); //注意,该方法需要对IO异常进行处理
    System.out.println("是否存在:" + f.exists()); //true

    注意,此方法只能创建文件,不能创建文件夹。且要保证创建文件的路径必须存在,否则抛出异常。

  • public boolean mkdir():创建由此File表示的单级空目录。

    File f = new File("newDir2333");
    System.out.println("是否存在:" + f.exists());
    System.out.println("是否创建:" + f.mkdir());
    System.out.println("是否存在:" + f.exists());

    public boolean mkdirs():创建由此File表示的目录,包括任何必需但不存在的父目录。也就说,既可创建单级空文件夹,也可创建多级文件夹。

    其中创建文件夹的路径和名称在构造方法中给出(即构造方法的参数)。当文件夹不存在时,方法会创建文件夹并返回true;当文件夹在实际中已经存在,则不会创建并返回false;当构造方法中给出的路径不存在,返回false

    File f = new File("newDir666\\newDir668");
    System.out.println("是否创建:" + f.mkdirs()); //true
    File f2 = new File("newDir666\\newDir668");
    System.out.println("是否存在:" + f2.mkdirs()); //false, 无需创建
  • public boolean delete():删除由此File表示的文件或目录。当文件或目录删除成功则返回true;当文件夹中有内容,则不会删除,且返回false;当构造方法中路径在实际中并不存在,返回false

    System.out.println(f.delete()); //true
    System.out.println(f2.delete()); //false

13.1.5 目录的遍历

  • public String[] list():返回一个String数组,表示该File目录中所有子文件或目录的名称字符串。

  • public File[] listFiles():返回一个File数组,表示该File目录中所有子文件或目录的File对象(具有更多信息)。

    File dir = new File("E:\\素材\\灵感");
    //获取当前目录下的文件/文件夹名称
    String[] names = dir.list();
    for(String name : names) System.out.println(name);
    //获取当前目录下的文件/文件夹对象
    File[] files = dir.listFiles();
    for(File file : files) System.out.println(file);

    注意,调用上面两种方法的File对象,表示的必须是实际存在的目录,否则会抛出空指针异常。

文件搜索的案例:

搜索D:\aaa目录中的.java文件。

public class JustTest {
public static void main(String[] args) {
String pathName = new Scanner(System.in).next();
File fir = new File(pathName);
findDir(fir);
}
public static void findDir(File dir){
File[] files = dir.listFiles();
for(File file : files){
if(file.isFile()){ //是文件,到达递归基
if(file.getName().toLowerCase().endsWith(".java")){
System.out.println("文件:" + file.getAbsolutePath());
}
}
else{ //是目录,继续深搜
findDir(file);
}
}
}
}

13.1.6 文件过滤器的优化

java.io.FileFilter,是一个接口,是File的过滤器。该接口的对象,只有一个方法,可以传递给File类的listFiles(FileFilter)作为参数:

  • boolean accept(File pathname)

    测试pathname是否应该包含在当前File目录中,保留住则返回true,过滤掉则返回false。其保留规则:要么是.java文件;要么是目录,用于继续遍历。

    接口作为参数,则需传递子类对象,并覆写其中方法,我们选择匿名内部类方式较为方便。

    通过过滤器作业,listFiles(FileFilter)返回的数组元素中,子文件对象都是符合条件的,可直接打印。

public class JustTest {
public static void main(String[] args) {
String pathName = new Scanner(System.in).next();
File fir = new File(pathName);
findDir(fir);
}
public static void findDir(File dir){
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith(".java") || pathname.isDirectory();
}
});
for(File file : files){
if(file.isFile()){ //是文件,到达递归基
if(file.getName().endsWith(".java")){
System.out.println("文件:" + file.getAbsolutePath());
}
}
else{ //是目录,继续深搜
findDir(file);
}
}
}
}

13.2 I/O流概述

Java中I/O操作主要指java.io包下的内容,进行输入、输出操作。

13.2.1 IO的分类

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

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

根据数据的类型分为:字节流和字符流。

  • 字节流:以字节为单位,读写数据的流。
  • 字符流:以字符为单位,读写数据的流。
输入流 输出流
字节流 字节输入流(InputStream 字节输出流(OutputStream
字符流 字符输入流(Reader 字符输出流(Writer

JavaSE 学习笔记07丨IO流

由于简单演示,在介绍字节流、字符流时,我们暂且将IO异常抛出。实际开发中,针对IO异常,应使用try...catch代码块进行处理。

13.3 字节流

13.3.1 字节输出流(OutputStream)

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

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

13.3.2 FileOutputStream 类

java.io.FileOutputStream 类 是 文件输出流,它作为OutputStream中的一个子类,用于将数据写出到文件。

一、构造方法

  • public FileOutputStream(File file):创建文件输出流以写入由指定的File对象表示的文件。
  • public FileOutputStream(String name):创建文件输出流写入以指定名称name的文件。

当你创建一个流对象时,必须传入一个文件路径。若该路径下没有这个文件,则会自动创建;若该文件已存在,则会清空该文件中数据。举例如下:

public class JustTest {
public static void main(String[] args) throws FileNotFoundException {
File f = new File("233.txt"); //使用File对象
FileOutputStream fos = new FileOutputStream(f);
FileOutputStream fos_2 = new FileOutputStream("b.txt"); //使用文件名称创建流对象
}
}

JavaSE 学习笔记07丨IO流

二、写出字节数据

  1. 写出字节:调用write(int b)方法,每次可以写出一个字节数据。注意,尽管参数为int类型的四个字节,但只会保留一个字节的信息写出。

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileOutputStream fos = new FileOutputStream("233.txt");
    fos.write(97); //写入第一个字节,转换为字符即为'a'
    fos.write(98); //写入第二个字节,转换为字符即为'b'
    fos.write(99);
    fos.close(); //流操作完毕后必须调用close方法以释放系统资源
    }
    }
    // 233.txt文件中打印为:abc
  2. 写出字节数组:调用write(byte[] b)方法,每次可以写出数组中的数据。

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileOutputStream fos = new FileOutputStream("233.txt");
    byte[] b = "国奖我来了".getBytes(); //字符串转化为字节数组
    fos.write(b); //写出字节数组数据
    fos.close(); //关闭资源
    }
    }
    // 233.txt文件中打印为:国奖我来了
  3. 写出指定长度字节数组:调用write(byte[] b, int off, int len),每次写出时从off索引开始,以len个字节。

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileOutputStream fos = new FileOutputStream("233.txt");
    byte[] b = "abcdefgh".getBytes(); //字符串转化为字节数组
    fos.write(b, 3, 4); //写出字节数组数据
    fos.close(); //关闭资源
    }
    }
    // 233.txt文件中打印为:defg

三、数据的追加续写

对于第二点中的方法,每次程序运行,创建输出流对象时,都会清空目标文件中的数据。为了保留目标文件中的数据,可以使用下面的构造方法(相比于第一点的构造方法,需要多传入一个append参数):

  • public FileOutputStream(File file, boolean append)
  • public FileOutputStream(File file, boolean append)

当传入的append参数为true时表示追加数据,false表示清空原有的数据。

public class JustTest {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("233.txt", true);
byte[] b = "abcde".getBytes(); //字符串转化为字节数组
fos.write(b);
fos.close(); //关闭资源
}
}
/* 对于 233.txt 文件
操作前:ddd
操作后:dddabcde
*/

【附】写出换行

  • 回车符\r:回到一行的开头(return)
  • 换行符\n:下一行(newline)

对于系统中的换行:

  • \(Windows\)系统,每行结尾为:\r\n(回车+换行)
  • \(Unix\)系统,每行结尾为:\n(换行)
  • \(Mac\)系统,每行结尾为:\r(回车),从\(Mac\ OS\ X\)开始与\(Linux\)统一。

13.3.3 字节输入流(InputStream)

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

  • public void close():关闭此输入流并释放与此流相关联的任何系统资源。
  • public abstract int read():从数据中读入一个字节,并返回该字节,该方法在碰到流的结尾时返回-1
  • public int read(byte[] b):读入一个字节数组,并返回实际读入的字节数,或者在碰到流的结尾时返回-1。注意,该方法最多读入b.length个字节。
  • public int read(byte[] b, int off, int len)

13.3.4 FileInputStream 类

java.io.FileInpurStream 类 是文件输入流,从文件中读入字节。

一、构造方法

  • FileInputStream(File file):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的File对象file命名。
  • FileInputStream(String name):通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name 命名。

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

public class JustTest {
public static void main(String[] args) throws IOException {
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file); //使用File对象创建流对象
FileInputStream fos_2 = new FileInputStream("233.txt"); //使用文件名称创建该流对象。
}
}

二、读取字节数据

  1. 读取字节:调用read()方法,每次可读取一个字节数据,并提升为int类型,读取到文件末尾时,返回-1。(注意,文件中含有中文时,读入后会变成乱码!)

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileInputStream fis = new FileInputStream("read.txt");
    int b; //临时变量,保存读入一个字节数据转化为的int数据
    while( (b = fis.read()) != -1)
    System.out.println((char)b);
    fis.close();
    }
    }
  2. 使用字节数组读取:调用read(byte[] b),每次读入b.length个字节数至数组中,并返回读取到的有效字节个数。同样地,当读取到末尾时,返回-1

    使用数组读入,使得每次读入多个字节,从而减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileInputStream fis = new FileInputStream("read.txt");
    int len;
    byte[] b = new byte[2]; //定义2个字节长度的字节数组
    while((len = fis.read(b)) != -1){ //每次读取后,将数组的有效字节部分,转换为字符串并打印
    System.out.println(new String(b, 0, len)); //len表示每次读取的有效字节个数!(想想边界条件)
    }
    fis.close();//关闭资源
    }
    }
    /*
    输出结果为
    ab
    cd
    e
    */

13.3.5 字节流应用:图片复制

复制原理:从已有文件中读入字节,将该字节写出到另一个文件中。

public class JustTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("E:\\素材\\灵感\\GVLOGO23.png"); //字节输入流对象,构造方法绑定要读入的数据域
FileOutputStream fos = new FileOutputStream("test_copy.jpg"); //字节输出流对象,绑定要写入的目的地
byte[] b = new byte[1024];
int cur;
while ((cur = fis.read(b)) != -1){
fos.write(b, 0, cur);
}
fos.close(); //先关闭输出流
fis.close(); //后关闭输入流
} //先开后关,后开先关。
}

13.4 字符流

在存储文本字符串时,需要考虑字符编码(character encoding)方式,Java提供字符流类,以字符为单位读写数据,专门用于处理文本文件(如中文字符)。

字符流,只能操作文本文件,不能操作图片、视频等非文本文件。当我们只希望 读或写 文本文件时,使用字符流;其他情况使用字节流。

13.4.1 字符输入类(Reader)

java.io.Reader 抽象类是表示用于读取字符流的所有类的超类,可读入字符信息至内存中。它定义了字符输入流的几个基本方法:

与字节输入类InputStream的基本方法一致,只不过传入的是字符数组而不是字节数组

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

13.4.2 FileReader 类

java.io.FileReader 类是读取字符文件的便利类,构造时使用系统默认的字符编码和默认字节缓冲区。其构造方法的实现就是使用了 FileInputStream (文件字节输入流)来实现

字符编码:字节与字符的对应规则,\(Windows\)系统的中文编码默认为\(GBK\)编码表(一个汉字占用2个字节,简体中文),而IDEA中为\(UTF-8\)(一个汉字占用3个字节,8位UniCode转换格式)

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

一、构造方法:

  • FileReader(File file):给定要读取的File对象以创建一个新的FileReader

  • FileReader(String fileName):给定要读取的文件的名称fileName以创建一个新的FileReader

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

二、读取字符数据

  1. 读取字符:调用read方法,每次可读入一个字符的数据,并提升为int类型,读取到文件末尾时返回-1

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileReader fr = new FileReader("233.txt");
    int b;
    while((b = fr.read()) != -1){
    System.out.println((char)b);
    }
    fr.close();
    }
    }
  2. 使用字符数组读入:调用read(char[] cbuf)方法,每次读入cbuf.length字符数至数组中,并返回读取到的有效字符个数。同样地,当读取到末尾时,返回-1

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileReader fr = new FileReader("233.txt");
    int len;
    char[] cbuf = new char[2];
    while((len = fr.read(cbuf)) != -1){
    System.out.println(new String(cbuf, 0, len)); //要获取有效字符
    }
    fr.close();
    }
    }

13.4.3 字符输出流(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():关闭此输出流但需要先刷新它。

13.4.4 FileWriter 类

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

一、构造方法

  • FileWriter(File file):给定要读入的File对象,创建一个新的FileWriter

  • FileWriter(String fileName):给定要读入的文件名称,创建一个新的FileWriter

    public class JustTest {
    public static void main(String[] args) throws IOException {
    File file = new File("233.txt");
    FileWriter fw = new FileWriter(file); //使用File对象创建流对象
    FileWriter fw_2 = new FileWriter("666.txt"); //使用文件名称创建流对象
    }
    }

二、写出字符数据

  1. 写出字符:调用write(int b)方法,每次可写出一个字符数据(至内存缓冲区,是字符转换为字节的过程)。

    关于关闭和刷新:关闭一个输出流的同时,也会冲刷用于该输出流的缓冲区:所有被临时置于缓冲区中,以使用更大的包的形式传递的字符在关闭输出流时都将被送出。特别地,如果不关闭文件,那么写出字节的最后一个包可能将永远得不到传递。

    如果既想写出数据,又想继续使用流,则需要flush方法。

    • flush:刷新缓冲区,但流对象仍可继续使用。
    • close:先刷新缓冲区,然后通知系统释放资源,此时流对象不可再被使用。
    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileWriter fw = new FileWriter("233.txt");
    fw.write('刷');
    fw.flush();
    fw.write('新');
    fw.write("\r\n"); //写出换行!
    fw.flush();
    fw.write('关');
    fw.close();
    /*输出结果为:
    刷新

    */
    //fw.write('闭'); //关闭后再写入会抛出异常
    }
    }
  2. 写出字符数组:调用write(char[] cbuf)write(char[] cbuf, int off, int len),每次可以写出字符数组中的数据。

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileWriter fw = new FileWriter("233.txt");
    char[] arr = "你好打工人".toCharArray();
    fw.write(arr); //你好打工人
    fw.write(arr, 2, 3); //打工人
    fw.close();
    }
    }
  3. 写出字符串:调用write(String str)write(String str, int off, int len),每次可写出字符串中的数据。

    public class JustTest {
    public static void main(String[] args) throws IOException {
    FileWriter fw = new FileWriter("233.txt");
    String msg = "你好打工人";
    fw.write(msg); //你好打工人
    fw.write(msg, 2, 3); //打工人
    fw.close();
    }
    }

13.5 IO 异常的处理

13.5.1 JDK7前的处理

public class JustTest {
public static void main(String[] args) {
FileWriter fw = null; //注意,应在外面定义这个file对象(别忘了赋初值)
try {
fw = new FileWriter("233.txt");
fw.write("早安打工人!");
} catch (IOException e){
e.printStackTrace();
} finally { //关闭流资源
try{
if(fw != null) //fw有可能为空,为空则不能再调用其方法。
fw.close(); //然而,close也有可能抛出异常
} catch (IOException e){
e.printStackTrace();
}
}
}
}

13.5.2 JDK7的处理

可以使用JDK7优化后的try-with-resource语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource),指在程序完成后必须关闭的对象。

格式为:

try(创建流对象语句){ //可以使用多个,若有多个则使用';'隔开
//读写数据
} catch(IOException e){
e.printStackTrace();
}

使用举例:

public class JustTest {
public static void main(String[] args) {
try (FileWriter fw = new FileWriter("233.txt")){
fw.write("早安打工人!");
} catch (IOException e){
e.printStackTrace();
}
}
}

13.6 属性集

java.util.Properties继承于Hashtable,以表示一个持久的属性集,它使用键-值结果存储数据,每个键及其对应值默认是字符串.

Properties类 被许多Java类使用,如获取系统属性时,System.getProperties方法即为返回一个Properties对象。

一、构造方法

  • public Properties():创建一个空的属性列表。

二、基本的存储方法

  • public Object setProperty(String key, String value):保存一对属性。

  • public String getProperty(String key):使用此属性列表中指定的键key搜索属性值。

  • public Set<String> stringPropertyNames():获取由所有键的名称组成的集合

    public class JustTest {
    public static void main(String[] args) {
    Properties mypro = new Properties();
    //添加键值对元素
    mypro.setProperty("filename", "233.txt");
    mypro.setProperty("length", "998244353");
    mypro.setProperty("location", "D:\\233.txt");
    System.out.println(mypro); //打印属性集对象
    //通过键获取属性值
    System.out.println(mypro.getProperty("filename"));
    System.out.println(mypro.getProperty("length"));
    //遍历属性值,获取由所有键组成的集合
    Set<String> mystr = mypro.stringPropertyNames();
    for(String key : mystr){
    System.out.println(key + " : " + mypro.getProperty(key));
    }
    }
    }

三、与流相关的方法

  • public void load(InputStream inStream):从字节输入流inStream中读入键值对。通过流对象,可关联至硬盘中保存的某文件中,读取到集合中使用。

  • public void load(Reader reader) :与上同理,传入的是字符输入流。

    注意:

    1. 文本中的数据,必须是键值对形式,可以使用空格、等号、冒号(英文)等符号作为分隔符。
    2. 可以使用#进行注释,被注释的键值对不会被读入。
    /* 233.txt文件中 */
    #save data
    #Sun Nov 15 11:26:58 CST 2020
    姓名:打工人
    年龄=19岁
    居住地 广州
    #学校:TSH /* JustTest.java文件中 */
    public class JustTest {
    public static void main(String[] args) throws IOException {
    Properties mypro = new Properties();
    mypro.load(new FileReader("233.txt")); //字符输入流
    Set<String> myset = mypro.stringPropertyNames();
    for(String key : myset){
    System.out.println(key + "----" + mypro.getProperty(key));
    }
    }
    } /* 输出结果 */
    姓名----打工人
    居住地----广州
    年龄----20岁

13.7 缓冲流

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

按数据类型分类有:

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

13.7.1 字节缓冲流

构造方法:

  • public BufferedInputStream(InputStream in):创建一个新的缓冲输入流。
  • public BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流。

使用举例:

public class JustTest {
public static void main(String[] args) throws IOException {
long st = System.currentTimeMillis();
try( //创建流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\素材\\灵感\\GVLOGO233.png"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\素材\\copy.png"));
){ //读写数据
int b;
while((b = bis.read()) != -1){
bos.write(b);
}
} catch (IOException e){
e.printStackTrace();
}
long ed = System.currentTimeMillis();
System.out.println("缓冲流复制时间:" + (ed - st) + "ms!");
}
}
/*输出结果:
缓冲流复制时间:82ms!
*/

13.7.2 字符缓冲流

构造方法:

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

字符缓冲流的基本方法与普通字符流调用方式一致,但也具备特有的方法:

  • 对于 BufferedReaderpublic String readLine():读一行文字。

    public class JustTest {
    public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader("E:\\IDEA-Projects\\basic-codes\\233.txt"));
    String myline = null; //保存读入的一行字符
    while((myline = br.readLine()) != null){ //读取【每】一行的字符
    System.out.println(myline);
    System.out.println("------------");
    }
    br.close();
    }
    }
  • 对于 BufferedWriterpublic void newLine():写出一行 行分隔符,由系统属性定义符号。

    public class JustTest {
    public static void main(String[] args) throws IOException {
    BufferedWriter bw = new BufferedWriter(new FileWriter("666.txt"));
    bw.write("早安");
    bw.newLine(); //换行
    bw.write("打工人");
    bw.write("好起来了!");
    bw.close();
    }
    }
    /*输出结果:
    早安
    打工人好起来了!
    */

13.7.3 缓冲流应用:文本排序

将下列文本信息按段编号恢复顺序:

JavaSE 学习笔记07丨IO流

public class JustTest {
public static void main(String[] args) throws IOException {
HashMap<String, String> lineMap = new HashMap<>(); //Map集合,序号作为key,段落文字内容作为value
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
String myline = null;
while((myline = br.readLine()) != null){
String[] twoStrs = myline.split("\\."); //将一行文字以.作为分隔符,分隔为两个字符串。
lineMap.put(twoStrs[0], twoStrs[1]);
}
br.close();
for(int i = 1; i <= lineMap.size(); i++){
String keyStr = String.valueOf(i); //将数字key转换为字符串类型
String valStr = lineMap.get(keyStr); //查找对应的value字符串
bw.write(keyStr + "." + valStr); //写出段落
bw.newLine(); //换行!
}
bw.close();
}
}

13.8 转换流

13.8.1 字符编码和字符集

字符编码(Character Encoding):一套自然语言的字符与二进制数之间的对应规则。按其相应规则,将字符存储到计算机中,称为编码;将存储在计算机中的二进制数按照相应规则解析显示出来,称为解码。

字符集(Charset):也称编码表,是一个系统支持的所有字符的集合,包括各国文字、标点符号、图形符号、数字等。当指定其常见的字符集有:ASCII字符集、GBK字符集、Unicode字符集等。

JavaSE 学习笔记07丨IO流

当指定了编码,它所对应的字符集也即指定下来。

  • ASCII 字符集 :用于显示现代英文,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字、西文符号)。

    • 基本ASCII字符集使用7位表示一个字符(共128个)
    • 拓展字符集使用8位表示一个字符(共256个)。
  • ISO-8859-1 字符集:用于显示欧洲使用的语言。

  • GBxxx 字符集:用于显示中文而设计的一套字符集

    • GBK:最常用的中文码表。
    • GB18030:最新的中文码表。每个字可由1个、2个或4个字节组成。
  • Unicode 字符集:也称统一码、标准万国码,为表达任意语言的任意字符而设计。最多使用4个字节的数字来表达每个字母、符号或者文字。

    • UTF-8编码:最常用,可以用来表示Unicode标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。其编码规则有:128个US-ASCII字符,只需一个字节编码;拉丁文等字符,需要二个字节编码;大部分常用字(含中文),使用三个字节编码;其他极少使用的Unicode辅助字符,使用四字节编码。
    • UTF-16
    • UTF-32

转换流即是字节与字符间的桥梁。

JavaSE 学习笔记07丨IO流

13.8.2 InputStreamReader 类

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

构造方法:

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

JavaSE 学习笔记07丨IO流

使用举例:

public class JustTest {
public static void main(String[] args) throws IOException {
String myfile = "E:\\IDEA-Projects\\666.txt";
InputStreamReader isr = new InputStreamReader(new FileInputStream(myfile)); //创建流对象,默认UTF-8编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(myfile)); //创建流对象,指定GBK编码
int b;
while((b = isr.read()) != -1){ //使用默认编码字符流读入,乱码
System.out.println((char)b); //有可能出现乱码
}
isr.close();
while((b = isr2.read()) != -1){ //使用指定编码字符流读入,正常解析
System.out.println((char)b); //正常显示
}
isr.close();
}
}

13.8.3 OutputStreamWriter 类

转换流java.io.OutputStreamWriter,是Writer子类,是从字符流至字节流的桥梁,使用指定的字符集,将字符编码为字节

构造方法:

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

JavaSE 学习笔记07丨IO流

使用举例:

public class JustTest {
public static void main(String[] args) throws IOException {
String myfile = "E:\\IDEA-Projects\\666.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(myfile)); //默认UTF-8编码
osw.write("早安"); //保存6个字节
osw.close();
String myfile_2 = "E:\\IDEA-Projects\\123.txt";
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(myfile_2), "GBK"); //指定GBK编码
osw2.write("打工人"); //保存为4个字节
osw2.close();
}
}

13.8.4 转换流应用:转换文件编码

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

public class JustTest {
public static void main(String[] args) throws IOException {
String srcFile = "E:\\IDEA-Projects\\666.txt";
InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile), "GBK"); //转换输入流,指定GBK编码
String destFile = "E:\\IDEA-Projects\\233.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));//转换输出流,默认UTF-8编码 char[] cbuf = new char[1024];
int len;
while((len = isr.read(cbuf)) != -1){ //读入GBK编码的文本文件
osw.write(cbuf, 0, len); //写出UTF-8编码的文本文件
}
osw.close();
isr.close();
}
}

13.9 序列化

Java 提供了一种对象序列化的机制,用一个字节序列表示一个对象,该字节序列包含该「对象的数据」、「对象的类型」和「对象中存储的数据」信息。当字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

JavaSE 学习笔记07丨IO流

该字节序列还可从文件中读入,重构对象,对其进行反序列化

13.9.1 ObjectOutputStream 类

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

构造方法:

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

序列化操作:

  1. 一个对象若想要序列化,必须同时满足两个条件:

    • 该类必须实现java.io.Serializable 接口(该接口是一个标记接口)。若不实现此接口的类,则不会使任何状态序列化或反序列化,抛出NotSerializableException

    • 该类所有属性必须是可序列化的,若有一个属性不需要可序列化,则该属性必须注明是瞬态的,使用transient关键字修饰。

      public class Student implements  java.io.Serializable{ //实现Serializable的接口
      public String name;
      public String No;
      public transient String address; //transient瞬态修饰成员,不会被序列化
      public void nameCheck(){
      System.out.println("学号为" + No + "的学生姓名为:" + name);
      }
      } //为演示方便,部分方法暂忽略
  2. 写出对象的方法

    • public final void writeObject(Object obj):将指定对象写出。
    public class JustTest {
    public static void main(String[] args) {
    Student a = new Student();
    a.name = "Luffy"; a.No = "23333"; a.address = "Brazil";
    try{
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\IDEA-Projects\\666.txt"));
    oos.writeObject(a); //写出对象,但注意该方法会抛出IO异常
    oos.close();
    System.out.println("Serialized data is saved successfully!");
    } catch (IOException i){ //捕获IO异常
    i.printStackTrace();
    }
    }
    }

13.9.2 ObjectInputStream 类

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

构造方法:

  • public ObjectInputStream(InputStream in):创建一个指定InputStreamObjectInputStream

反序列化操作:

若能找到一个对象的.class文件,即可进行反序列化操作,调用ObjectInputStream 读入对象的方法:

  • public final Object readObject():读入一个对象。注意,JVM反序列化对象时,必须能够找到传入的.class文件的类,否则抛出ClassNotFoundException 异常。

    public class JustTest {
    public static void main(String[] args) {
    Student a = null;
    try{
    FileInputStream fileIn = new FileInputStream("E:\\IDEA-Projects\\666.txt");
    ObjectInputStream ois = new ObjectInputStream(fileIn); //传入字节输入流参数
    a = (Student) ois.readObject(); //读入对象,返回Object类需强转一下
    ois.close();
    fileIn.close();
    } catch (IOException i){
    i.printStackTrace();
    return;
    } catch (ClassNotFoundException c){ //readObject会抛出这类异常
    System.out.println("Student class can't be found!");
    c.printStackTrace();
    return;
    }
    System.out.println("Name:" + a.name + " Address:" + a.address); //若无异常则直接打印输出。
    }
    }

    尽管JVM反序列化对象时能够找到.class,但注意.class文件在序列化对象之后发生了修改,则反序列化操作有可能失败,抛出个 InvalidClassException 异常。发生该异常的原因如下:

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

    Serializable 接口给需要序列化的类提供一个默认的序列版本号serialVersionUID,该版本号目的在于验证序列化的对象和对应类是否版本匹配。当然,也可以自定义版本号,从而避免序列化后再修改.class文件所发生的冲突异常。

    public class Student implements java.io.Serializable{ //实现Serializable的接口
    public String name;
    public String No;
    public transient String address; //transient瞬态修饰成员,不会被序列化
    private static final long serialVersionUID = 1L; //加入序列版本号
    public int eid; //假定我序列化之后再添加该属性再编译,是能够反序列化的(前提是你不能使用默认的序列版本号),该属性赋为默认值。
    public void nameCheck(){
    System.out.println("学号为" + No + "的学生姓名为:" + name);
    }
    }

13.9.3 序列化集合

练习:将存有多个自定义对象的集合进行序列化,并保存至文件中。然后再反序列化该文件,遍历集合打印对象信息。

public class JustTest {
public static void main(String[] args) throws Exception {
Student a = new Student("Luffy", "001");
Student b = new Student("Ben", "002");
Student c = new Student("Nami", "003");
ArrayList<Student> arr = new ArrayList<>();
arr.add(a); arr.add(b); arr.add(c);
serialise(arr); //序列化 //反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\IDEA-Projects\\666.txt"));
ArrayList<Student> mylist = (ArrayList<Student>) ois.readObject();
for(int i = 0; i < mylist.size(); i++){
Student cur = mylist.get(i);
System.out.println(cur.getName() + "---" + cur.getNo());
}
}
private static void serialise(ArrayList<Student> arr) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\IDEA-Projects\\666.txt"));
oos.writeObject(arr);
oos.close();
}
}

执行结果:

JavaSE 学习笔记07丨IO流

13.10 打印流

控制台打印输出即调用了print()/println()方法等,这些方法均来自于java.io.PrintStream 类,该类能够方便地打印各种数据类型的值。

13.10.1 PrintStream 类

java.io.PrintStream继承自父类OutputStream,故也有close()write()flush()等共性方法。

特点:

  1. 只负责数据的输出,不负责数据的读取。
  2. 与其他输出流不同,它永远不会抛出IOException。(但还是能够抛出FileNotFoundException 异常)

构造方法:

创建打印流PrintStream对象,其构造方法需传入 要输出的目的地(必须存在) 作为参数。

  • public PrintStream(File file):输出目的地为一个文件。
  • public PrintStream(OutputStream out):输出目的地为一个字节输出流。

特有方法:

  • void print(任意类型)

  • void println(任意类型):输出包括换行

    若使用继承自父类的write方法写出数据,它会查询对应的编码表(如97->'a')并将其解码输出;若使用自己特有的方法print()println(),写出的数据原样输出(如97->97)

另外,通过调用System.setOut()方法,可以将输出语句的目的地,改变为参数中传递的打印流的目的地(默认情况下,是控制台输出的)

  • static void setOut(PrintStream out):重新分配“标准”输出流。

    public class Hello {
    public static void main(String[] args) throws FileNotFoundException {
    System.out.println("我在控制台输出!");
    PrintStream ps = new PrintStream("C:\\Users\\admin\\IdeaProjects\\MyIdeaProjects\\666.txt");
    System.out.println(ps);
    System.out.println("我在打印流的目的地中输出!");
    ps.close();
    }
    }