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

由于简单演示,在介绍字节流、字符流时,我们暂且将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"); //使用文件名称创建流对象
}
}

二、写出字节数据

  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 缓冲流应用:文本排序

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

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字符集等。

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

  • 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

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

13.8.2 InputStreamReader 类

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

构造方法:

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

使用举例:

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):创建一个指定字符集的字节流。

使用举例:

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

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

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();
}
}

执行结果:

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();
    }
    }

JavaSE 学习笔记07丨IO流的更多相关文章

  1. 【知了堂学习笔记】java IO流归纳总结

    皮皮潇最近学到了IO流但是感觉这一块要记的东西太多了,所以重API上查阅并总结了以下几点关于IO的知识. 1.File(文件类): File类是文件以及文件夹进行封装的对象,用对象的思想来操作文件和文 ...

  2. JavaSE 学习笔记02丨对象与类

    Chapter 4. 对象与类 4.1 面向对象程序设计概述 面向对象程序设计(简称OOP),是当今主流程序设计范型.面向对象的程序是由对象组成的,每个对象(来自于标准库或自定义的)包含对用户公开的特 ...

  3. Java基础学习笔记二十 IO流

    转换流 在学习字符流(FileReader.FileWriter)的时候,其中说如果需要指定编码和缓冲区大小时,可以在字节流的基础上,构造一个InputStreamReader或者OutputStre ...

  4. JavaSE 学习笔记08丨网络编程

    Chapter 14. 网络编程 14.1 计算机网络入门 当前节的知识点只是一个概述,更具体.详细的内容放在 计算机网络 中. 14.1.1 软件结构 C/S结构(Client/Server结构): ...

  5. JavaSE 学习笔记01丨开发前言与环境搭建、基础语法

    本蒟蒻学习过C/C++的语法,故在学习Java的过程中,会关注于C++与Java的区别.开发前言部分,看了苏星河教程中的操作步骤.而后,主要阅读了<Java核心技术 卷1 基础知识>(第8 ...

  6. JavaSE 学习笔记06丨并发

    Chapter 12. 并发 12.1 并发与并行 并发:指两个或多个事件在同一个时间段内发生. 并行:指两个或多个事件在同一时刻发生(同时发生). 在操作系统中,并发指的是在一段时间内宏观上有多个程 ...

  7. JavaSE 学习笔记05丨泛型、集合

    Chapter. 10 泛型 10.1 泛型程序设计 泛型,指可以在类或方法中预支地使用未知的类型.泛型程序设计(Generic programming),意味着编写的代码可被很多不同类型的对象所重用 ...

  8. JavaSE 学习笔记04丨异常

    Chapter 9 异常 异常:指程序在执行过程中,出现的非正常的情况,最终导致JVM非正常停止. 在Java等面向对象的编程语言中,异常是一个类,所有异常都是发生在运行阶段的(因为也只有程序运行阶段 ...

  9. JavaSE 学习笔记03丨继承、接口、多态、内部类

    Chapter. 5 继承 继承作为面向对象的三大特征之一,它是多态的前提.它主要解决的问题是共性抽取. Java中的继承,是单继承.多级继承的. 已存在的类,被称为超类.基类.父类(parent c ...

随机推荐

  1. 【总结】IP

    一. IP基本认识 1.IP 在 TCP/IP 参考模型中处于第三层,也就是网络层. 网络层的主要作用是:实现主机与主机之间的通信,也叫点对点(end to end)通信 2.网络层与数据链路层有什么 ...

  2. Centos7安装Gitlab11

    一.基础介绍 1.简介 一个基于GIT的源码托管解决方案 基于rubyonrails开发 集成了nginx postgreSQL redis sidekiq等组件 2.安装要求 2g内存以上,有点占内 ...

  3. Git的介绍及使用

    一.配置用户信息: 配置用户名和邮箱: $ git config --global user.name "chunyu" $ git config --global user.em ...

  4. 修改redo log 的大小

    alert日志中含有大量警告信息:"Thread 1 cannot allocate new log, sequence 320xx Checkpoint not complete" ...

  5. swjtuoj2433 Magic Mirror

    描述 Magic Mirror is an artificial intelligence system developed by TAL AI LAB,It can determine human ...

  6. Dcoker 安装 rabbitMq

    使用Docker安装部署RabbitMQ   1.docker search rabbitmq:management     2.docker pull rabbitmq:management     ...

  7. CF1108E2 Array and Segments (Hard version)

    线段树 对于$Easy$ $version$可以枚举极大值和极小值的位置,然后判断即可 但对于$Hard$ $version$明显暴力同时枚举极大值和极小值会超时 那么,考虑只枚举极小值 对于数轴上每 ...

  8. 7_Reverse Integer

    7.Reverse Integer Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 ...

  9. MySQL中load data infile将文件中的数据批量导入数据库

    有时候我们需要将文件中的数据直接导入到数据库中,那么我们就可以使用load data infile,下面具体介绍使用方法. dao中的方法 @Autowired private JdbcTemplat ...

  10. Thinkphp3.2 cms之权限管理

    五.权限管理 <?php namespace Admin\Controller; use Think\Controller; class CommonController extends Con ...