JavaSE 学习笔记07丨IO流
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"); //使用文件名称创建流对象
    }
}

二、写出字节数据
- 写出字节:调用 - 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
 
- 写出字节数组:调用 - 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文件中打印为:国奖我来了
 
- 写出指定长度的字节数组:调用 - 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"); //使用文件名称创建该流对象。
    }
}
二、读取字节数据
- 读取字节:调用 - 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();
 }
 }
 
- 使用字节数组读取:调用 - 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"); //使用文件名称创建流对象
 }
 }
 
二、读取字符数据
- 读取字符:调用 - 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();
 }
 }
 
- 使用字符数组读入:调用 - 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"); //使用文件名称创建流对象
 }
 }
 
二、写出字符数据
- 写出字符:调用 - 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('闭'); //关闭后再写入会抛出异常
 }
 }
 
- 写出字符数组:调用 - 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();
 }
 }
 
- 写出字符串:调用 - 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):与上同理,传入的是字符输入流。- 注意: - 文本中的数据,必须是键值对形式,可以使用空格、等号、冒号(英文)等符号作为分隔符。
- 可以使用#进行注释,被注释的键值对不会被读入。
 - /* 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次数,从而提高读写效率。
按数据类型分类有:
- 字节缓冲流:BufferedInputStream,BufferedOutputStream
- 字符缓冲流:BufferedReader,BufferedWriter
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):创建一个新的缓冲输出流。
字符缓冲流的基本方法与普通字符流调用方式一致,但也具备特有的方法:
- 对于 - BufferedReader,- public 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();
 }
 }
 
- 对于 - BufferedWriter,- public 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):创建一个指定- OutputStream的- ObjectOutputStream。
序列化操作:
- 一个对象若想要序列化,必须同时满足两个条件: - 该类必须实现 - 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);
 }
 } //为演示方便,部分方法暂忽略
 
 
- 写出对象的方法 - 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):创建一个指定- InputStream的- ObjectInputStream。
反序列化操作:
若能找到一个对象的.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()等共性方法。
特点:
- 只负责数据的输出,不负责数据的读取。
- 与其他输出流不同,它永远不会抛出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流的更多相关文章
- 【知了堂学习笔记】java IO流归纳总结
		皮皮潇最近学到了IO流但是感觉这一块要记的东西太多了,所以重API上查阅并总结了以下几点关于IO的知识. 1.File(文件类): File类是文件以及文件夹进行封装的对象,用对象的思想来操作文件和文 ... 
- JavaSE 学习笔记02丨对象与类
		Chapter 4. 对象与类 4.1 面向对象程序设计概述 面向对象程序设计(简称OOP),是当今主流程序设计范型.面向对象的程序是由对象组成的,每个对象(来自于标准库或自定义的)包含对用户公开的特 ... 
- Java基础学习笔记二十 IO流
		转换流 在学习字符流(FileReader.FileWriter)的时候,其中说如果需要指定编码和缓冲区大小时,可以在字节流的基础上,构造一个InputStreamReader或者OutputStre ... 
- JavaSE 学习笔记08丨网络编程
		Chapter 14. 网络编程 14.1 计算机网络入门 当前节的知识点只是一个概述,更具体.详细的内容放在 计算机网络 中. 14.1.1 软件结构 C/S结构(Client/Server结构): ... 
- JavaSE 学习笔记01丨开发前言与环境搭建、基础语法
		本蒟蒻学习过C/C++的语法,故在学习Java的过程中,会关注于C++与Java的区别.开发前言部分,看了苏星河教程中的操作步骤.而后,主要阅读了<Java核心技术 卷1 基础知识>(第8 ... 
- JavaSE 学习笔记06丨并发
		Chapter 12. 并发 12.1 并发与并行 并发:指两个或多个事件在同一个时间段内发生. 并行:指两个或多个事件在同一时刻发生(同时发生). 在操作系统中,并发指的是在一段时间内宏观上有多个程 ... 
- JavaSE 学习笔记05丨泛型、集合
		Chapter. 10 泛型 10.1 泛型程序设计 泛型,指可以在类或方法中预支地使用未知的类型.泛型程序设计(Generic programming),意味着编写的代码可被很多不同类型的对象所重用 ... 
- JavaSE 学习笔记04丨异常
		Chapter 9 异常 异常:指程序在执行过程中,出现的非正常的情况,最终导致JVM非正常停止. 在Java等面向对象的编程语言中,异常是一个类,所有异常都是发生在运行阶段的(因为也只有程序运行阶段 ... 
- JavaSE 学习笔记03丨继承、接口、多态、内部类
		Chapter. 5 继承 继承作为面向对象的三大特征之一,它是多态的前提.它主要解决的问题是共性抽取. Java中的继承,是单继承.多级继承的. 已存在的类,被称为超类.基类.父类(parent c ... 
随机推荐
- 2020年Android开发最新整理阿里巴巴、字节跳动、小米面经,你不看看吗?
			前言 2020年是转折的一年,上半年疫情原因,很多学android开发的小伙伴失业了,虽找到了一份工作,但高不成低不就,下半年金九银十有想法更换一份工作,很多需要大厂面试经验和大厂面试真题的小伙伴,想 ... 
- C# stopwatch的简单使用(计算程序执行时间)
			首先添加引用 using System.Diagnostics;//stopwatch的引用 //声明变量 Stopwatch a=new Stopwatch();//PS:这里一定要new(实例化) ... 
- 项目开发中的pro、pre、test、dev环境及SpringBoot多环境配置
			一.介绍: pro:生产环境,面向外部用户的环境,连接上互联网即可访问的正式环境. pre:灰度环境,外部用户可以访问,但是服务器配置相对低,其它和生产一样. test:测试环境,外部用户无法访问,专 ... 
- Flask中的RESTFul
			RESTFul 1.什么是RESTFul? 1.1 简介 REST即表述性状态传递(英文:Representational State Transfer, 简称REST)是Roy Fielding博士 ... 
- eyoucms破解授权/去版权插件
			插件描述:eyoucms内容管理系统的授权破解,可以去版权的插件,需要请自行关注. https://hbh.cool/find/136.html 
- vscode 插件配置指北
			Extension Manifest 就像 chrome 插件使用 manifest.json 来管理插件的配置一样,vscode 的插件也有一个 manifest,而且就叫 package.json ... 
- 5、Python语法之基本数据类型
			一 引入 我们学习变量是为了让计算机能够像人一样去记忆事物的某种状态,而变量的值就是用来存储事物状态的,很明显事物的状态分成不同种类的(比如人的年龄,身高,职位,工资等等),所以变量值也应该有不同的类 ... 
- leetcode148two-sum
			Given an array of integers, find two numbers such that they add up to a specific target number. The ... 
- 前端性能优化之 gzip+cache-control
			刚刚在Node.js环境下使用gzippo模块进行了测试. 使用gzip的压缩率惊人的好,达到了50%以上. 再加上express的staticCache,配合cache-control max-ag ... 
- 极客mysql06
			两阶段锁:在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放, 而是要等到事务结束时才释放. 建议:如果你的事务中需要锁多个行,要把最可能造成锁冲突.最可能影响并发度的锁 ... 
