JavaSE 第十二章 IO流

13.1 File类的使用

13.1.1 File类的介绍

  • java.io.File类:文件和文件目录的抽象表示形式
  • File类能新建、删除、重命名文件和目录,但File类不能访问文件内容,访问文件内容需要用到输入/输出流
  • 想要在Java程序中表示一个真实存在的文件或目录,需要有一个File对象,但是并不意味着File对象一定会对应着一个真实存在的文件或目录。
  • File对象可以作为实参传递给流的构造器

13.1.2 File类中的构造器

  • 构造方法
    • public File(String pathname) : 以pathname为路径创建File对象,可以是绝对路径,也可以是相对路径。
    • public File(File parent, String child) : 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
    • public File(String parent, String child) :根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
  • 在创建文件时,路径中的每级目录之间用一个路径分隔符隔开。
  • 路径分隔符在不同的系统中也不尽相同:
    • WIndows和DOS系统默认使用"\"来表示
    • UNIX和URL使用"/"来表示
  • Java是一门跨平台的语言,所以在使用分隔符时要格外小心。
    • File类中提供有一个常量 separator ,它会根据不同的操作系统动态的提供分隔符。
  • 创建File对象示例:
public class TestFile {
    public static void main(String[] args) {
        // 使用File类中的构造器创建File对象
        // public File(String pathname)
        // 绝对路径创建文件
        File file1 = new File("D:\\file1.txt");
        // 相对路径
        File file2 = new File("\\file1.txt");

        // public File(File parent,String child)
        File file3 = new File(new File("D:\\data"), "\\test.txt");

        // public File(String parent,String child)
        File file4 = new File("D:\\file", "\\test.txt");

        // 使用File类提供的常量来替换分隔符
        File file5 = new File("D:" + File.separator + "file" + File.separator + "file.txt");
    }
}

13.1.3 File类中的常用方法

获取功能
  • File类的获取功能

    • String getAbsolutePath():获取文件的绝对路径
    • String getPath() : 获取路径
    • String getName() : 获取文件名称
    • String getParent():获取上层文件目录路径,没有就返回null
    • long length():获取文件长度(字节数)
    • long lastModified():获取文件最后一次的修改时间
    • String[] list():获取指定目录下的所有文件或者文件目录的名称数组
    • File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
  • 示例:

import java.io.File;
import java.text.SimpleDateFormat;

public class GetOfFile {
    public static void main(String[] args) {
        // File类的获取功能
        File file = new File("D:\\file\\a\\test.txt");

        // 获取文件的绝对路径
        String path = file.getAbsolutePath();
        System.out.println("path = " + path);

        // 获取文件路径
        String path1 = file.getPath();
        System.out.println("path1 = " + path1);

        // 获取文件名
        String name = file.getName();
        System.out.println("name = " + name);

        // 获取文件的上层目录
        String parent = file.getParent();
        System.out.println("parent = " + parent);

        // 获取文件字节数
        long length = file.length();
        System.out.println("length = " + length);

        // 获取文件最后一次修改时间
        long lastModified = file.lastModified();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String s = format.format(lastModified);
        System.out.println("s = " + s);

        // 获取指定目录下的所有文件或者文件目录的名称数组
        File file1 = new File("D:\\file");
        String[] list = file1.list();
        for (String s1 : list) {
            System.out.println("s1 = " + s1);
        }

        // 获取指定目录下的所有文件或者文件目录的File数组
        File[] files = file1.listFiles();
        for (File file2 : files) {
            System.out.println("file2 = " + file2);
        }
    }
}

path = D:\file\a\test.txt
path1 = D:\file\a\test.txt
name = test.txt
parent = D:\file\a
length = 1151
s = 2022-08-15 08:23:50
s1 = a
s1 = a.txt
s1 = b
s1 = b.txt
s1 = c
s1 = c.txt
file2 = D:\file\a
file2 = D:\file\a.txt
file2 = D:\file\b
file2 = D:\file\b.txt
file2 = D:\file\c
file2 = D:\file\c.txt

重命名功能
  • boolean renameTo(File dest):把文件重命名为指定的文件路径
public class Demo1 {
    public static void main(String[] args) {
        File file = new File("D:\\file\\a.txt");

        File file1 = new File("D:\\file\\rename.txt");

        // 重命名
        boolean b = file.renameTo(file1);
        System.out.println("b = " + b); // true
    }
}
判断功能
  • boolean isDirectory():判断是否是目录
  • boolean isFile():判断是否是文件
  • boolean exists():判断文件是否存在
  • boolean canRead() :判断是否可读
  • boolean canWrite() :判断是否可写
  • boolean isHidden() :判断是否隐藏

示例:

public class Demo2 {
    public static void main(String[] args) {
        File file = new File("D:\\file\\a");
        File file1 = new File("D:\\file\\a.txt");

        // 判断是否是目录
        boolean directory = file.isDirectory();
        System.out.println("directory = " + directory);

        // 判断是否是文件
        boolean b = file.isFile();
        System.out.println("b = " + b);

        // 判断文件是否存在
        boolean b1 = file1.exists();
        System.out.println("b1 = " + b1);

        // 判断文件是否可读
        boolean b2 = file.canRead();
        System.out.println("b2 = " + b2);

        // 判断文件是否可写
        boolean b3 = file.canWrite();
        System.out.println("b3 = " + b3);

        // 判断文件是否隐藏
        boolean hidden = file.isHidden();
        System.out.println("hidden = " + hidden);
    }
}
创建功能
  • public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false;父目录不存在会爆IOException
  • public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。 如果此文件目录的上层目录不存在,也不创建。
  • public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建

示例:

public class Demo3 {
    public static void main(String[] args) throws IOException {
        // 创建文件对象
        File file = new File("D:\\file\\a.txt");
        // 判断文件是否存在
        System.out.println("file.exists() = " + file.exists());
        // 创建文件
        boolean b = file.createNewFile();
        System.out.println("b = " + b);

        File file1 = new File("D:\\file\\dir");
        System.out.println("file1.exists() = " + file1.exists());

        // 创建目录
        System.out.println("file1.mkdir() = " + file1.mkdir());

        File file2 = new File("D:\\file\\newfile\\dir\\a");
        System.out.println("file2.exists() = " + file2.exists());

        // 创建目录
        System.out.println("file2.mkdirs() = " + file2.mkdirs());
    }
}
删除功能
  • boolean delete() :删除文件或文件夹
  • 注意事项:
    • 这种删除方法删除的文件不会进入回收站
    • 要删除一个文件夹时要确保其中不含文件或文件目录

示例:

public class Demo4 {
    public static void main(String[] args) {
        File file = new File("D:\\file\\newfile\\dir\\a");

        boolean delete = file.delete();
        System.out.println("delete = " + delete);
    }
}

13.2 IO流概述及流的分类

13.2.1 IO流概述

  • I/O时Input/Output的缩写,I/O技术用于处理设备之间的数据传输。如文件的读写,网络通信等。
  • Java程序中,对数据的输入输出操作以“流(stream)”的方式进行。
  • Java.io包下提供了各种“流”类和接口,用以获取类的数据,并通过标准的方法输入输出数据。
  • 输入input:读取外部数据到程序内
  • 输出output:将程序中数据写到磁盘、光盘等存储设备中

13.2.2 流的分类

  • 按照数据单位不同分为:字节流(8bit,即一个字节)、字符流(16bit,即两个字节)
  • 按照数据流的流向不同:输入流、输出流
  • 按流的角色的不同分为:节点流、处理流
抽象基类字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

在这里插入图片描述

  • 区分字节流和字符流
    • 字节流的类都是以Stream结尾
    • 字符流的类都是以Reader/Writer结尾
  • 区分输入流和输出流
    • 带有Input/Reader关键字的流都为输入流
    • 带有Output/Writer关键字的流都是输出流
  • 节点流:直接从数据源或目标文件读取数据
  • 处理流:“连接”在已经存在的流之上,通过对数据的处理为程序提供更强大的读写功能

13.2.3 OutputStream 字节输出流

  • OutputStream是一个抽象类,无法直接创建对象,使用其子类实现类FileOutputStream
13.2.3.1 FileOutputStream
构造方法
  • FileOutputStream(File file):创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
  • FileOutputStream(File file, boolean append):创建一个向指定 File 对象表示的文件中写入数据的文件输出流。append为false时,文件不可追加;append为true时,文件可追加
  • FileOutputStream(String name):创建一个向具有指定名称的文件中写入数据的输出文件流。
  • FileOutputStream(String name, boolean append): 创建一个向具有指定 name 的文件中写入数据的输出文件流。
  • 对于此四种构造方法,如果传入的File对象或字符串指向的文件不存在,会自动创建,但必须确保文件的父目录是存在的,否则会报FileNotFoundException
常用方法
  • void close() :关闭流并释放资源
  • void write(int b):写入一个字节到文件输出流
  • void write(byte[] b):写入一个字节数组到文件输出流
  • void write(byte[] b , int off , int len):写入一个字节数组的一部分到文件输出流;off表示起始位置,len表示长度
  • 程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资 源,所以应该显式关闭文件 IO 资源。
代码演示
  • 不可追加输出流
public class Test1 {
    public static void main(String[] args) throws IOException {

        // 创建目标文件为C:\Users\26090\Desktop\newfile.txt的输出流对象
        FileOutputStream fos = new FileOutputStream("C:\\Users\\26090\\Desktop\\newfile.txt");

        // 一次写入一个字节
        fos.write(97);
        fos.write(98);
        fos.write(99);
        fos.write(100);

        // 一次写入一个字节数组
        fos.write("HelloWorld".getBytes());

        // 一次写入一个字节数组的一部分
        fos.write("javaSE".getBytes() , 0 , 4);

        // 文件中的内容为:abcdHelloWorldjava
        // 当再多次执行程序,文件中内容仍为:abcdHelloWorldjava,此为不可追加
        
        fos.close();
    }
}
  • 可追加输出流
public class Test2 {
    public static void main(String[] args) throws IOException {
        // 可追加
        FileOutputStream fos = new FileOutputStream("C:\\Users\\26090\\Desktop\\newfile.txt", true);

        // 换行
        byte[] bytes = "\r\n".getBytes();

        fos.write(bytes);

        // 一次写入一个字节
        fos.write(97);
        fos.write(98);
        fos.write(99);
        fos.write(100);

        fos.write(bytes);

        // 一次写入一个字节数组
        fos.write("HelloWorld".getBytes());

        fos.write(bytes);

        // 一次写入一个字节数组的一部分
        fos.write("javaSE".getBytes() , 0 , 4);

        fos.close();
    }
}

13.2.4 InputStream 字节输入流

  • InputStream是一个抽象类,无法直接创建对象,使用其子类实现类FileInputStream
13.2.4.1 FileInputStream
构造方法

- FileInputStream(File file) :通过一个实际存在的File文件的对象创建一个输入流对象并建立连接

  • FileInputStream(String name) :通过一个实际存在的文件的路径字符串创建一个输入流对象并建立连接
  • 对于上述两种构造方法,传入的File对象或String类型的文件路径必须有真实存在的文件与之对应,否则会报FileNotFountException
常用方法
  • void close():关闭流并释放资源
  • int read(): 从此输入流中读取一个字节的数据。
  • int read(byte[] b):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
  • int read(byte[] b, int off, int len):从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
  • long skip(long n):从输入流中跳过并丢弃 n 个字节的数据。
代码示例
  • 每次读取一个字节的数据
    • 注意:当文件读取完时,read()方法会返回-1
public class Test3 {
    public static void main(String[] args) throws IOException {
        // 创建文件字节输入流对象
        FileInputStream fis = new FileInputStream("C:\\Users\\26090\\Desktop\\newfile.txt");

        // 每次读取一个字节的数据
//        int b = fis.read();
        
        int b ;
        while ((b = fis.read()) != -1) {
            System.out.print((char)b);
        }

//        System.out.println("b = " + b);

        fis.close();
    }
}
  • 每次读取一个字节数组的数据

    • 当文件读取完时,read(byte[] b)也会返回-1,但是需要注意的是,在下面的代码中没有使用read = new String(bys, 0, len); 而是直接使用read = new String(bys); 的话,很有可能会出现上一次读取到的数据

在这里插入图片描述

public class Test4 {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream(new File("C:\\Users\\26090\\Desktop\\newfile.txt"));

        // 定义一个长度为5的byte数组
        byte[] bys = new byte[5] ;

        // 返回值类型为int,值为读取到的字节的个数
        /*
        int read = fis.read(bys);
        String string = new String(bys);
        System.out.println("string = " + string);
        */
        
        int len ;
        while ((len = fis.read(bys)) != -1) {
            String read = new String(bys, 0, len);
            System.out.print(read);
        }

        fis.close();
    }
}
  • 复制文件
public class Test5 {
    public static void main(String[] args) throws IOException {
        // 创建输入输出流对象
        // 源文件
        FileInputStream fis = new FileInputStream("C:\\Users\\26090\\Desktop\\newfile.txt");
        // 新文件
        FileOutputStream fos = new FileOutputStream("C:\\Users\\26090\\Desktop\\newfile1.txt");

        byte[] bytes = new byte[5];     // 长度为1024以及1024的倍数效率较高
        int len ;

        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes , 0 , len);
        }

        // 关闭流,释放资源
        fis.close();
        fos.close();
    }
}

13.3 节点流(文件流)

13.3.1 FileWriter

  • FIleWriter字符文件输出流,是OutputStreamWriter的子类
    在这里插入图片描述
构造方法
  • FileWriter(File file):根据给定的 File 对象构造一个 FileWriter 对象。
  • FileWriter(File file, boolean append):根据给定的 File 对象构造一个 FileWriter 对象,文件内容可拼接
  • FileWriter(String fileName):根据给定的文件名构造一个 FileWriter 对象。
  • FileWriter(String fileName, boolean append):根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。
常用方法
  • void close():关闭流,释放资源,需要先调用flush()方法刷新流
    - void write(char[] cbuf, int off, int len):写入字符数组的某一部分。
  • void write(int c):写入单个字符。
  • void write(String s, int off, int len): 写入字符串的某一部分。
  • void writer(char[] cbuf):写如一个字符数组
  • void writer(String s):写入一个字符串
代码示例
  • 向一个文件中写入数据
public class FileReaderDemo {
    public static void main(String[] args) throws IOException {

        // 创建字符输出流对象
        FileWriter fileWriter = new FileWriter("E:\\file\\Demo.txt");

        // 写入一个字符
        fileWriter.write(99);
        fileWriter.write("\r\n");
        fileWriter.write('a');
        fileWriter.write("\r\n");


        // 写入一个字符数组
        char[] chars = {'a' , 99 , '张' , 'w' , '李'} ;
        fileWriter.write(chars);
        fileWriter.write("\r\n");


        // 写入一个字符数组的一部分
        fileWriter.write(chars , 0 , 3);
        fileWriter.write("\r\n");


        // 写入一个字符串
        fileWriter.write("这是一个字符串");
        fileWriter.write("\r\n");


        // 写入一个字符串的一部分
        fileWriter.write("这是一个字符串" , 0 , 3);
        fileWriter.write("\r\n");

        // 关闭流
        fileWriter.close();
    }
}

13.3.2 FileReader

构造方法
  • FileReader(File file):在给定从中读取数据的 File 的情况下创建一个新 FileReader。
  • FileReader(String fileName):在给定从中读取数据的文件名的情况下创建一个新 FileReader。
常用方法
  • void close() :关闭流并释放资源
  • int read():读取单个字符,读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1
  • int read(char[] cbuf , int offset , int length):将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
  • int read(char[] cbuf):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
代码示例
public class FileReaderDemo {
    public static void main(String[] args) throws IOException {
        // 创建字符输入流对象
        FileReader fileReader = new FileReader("E:\\file\\test.txt");

        // 读取一个字符
        int c = fileReader.read();
        System.out.println(c);

        // 读取一个字符数组
        char[] chars = new char[5];
        fileReader.read(chars) ;
        String s = new String(chars);
        System.out.println("s = " + s);

        // 读取一个字符数组的一部分
        int len = fileReader.read(chars);
        String s1 = new String(chars , 0 , len);
        System.out.println("s1 = " + s1);

        // 关闭流
        fileReader.close();
    }
}
  • 文件复制

public class CopyFile1 {
    public static void main(String[] args) throws IOException {
        // 创建字符文件输入输出流对象
        // 字符文件输入流
        FileReader fr = new FileReader("E:\\file\\源文件.txt");
        // 字符文件输出流
        FileWriter fw = new FileWriter("E:\\file\\目标文件.txt");

        // 创建字符数组接收读取到的数据
        char[] chars = new char[1024];
        // 记录读取到的字符数
        int len ;

        while ((len = fr.read(chars)) != -1) {
            fw.write(chars , 0 , len);
        }

        // 刷新流
        fw.flush();
        // 关闭流
        fw.close();
        fr.close();
    }
}

13.3.3 注意点

  • 定义文件路径时,注意:可以用“/”或者“\\”。
  • 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文 件将被覆盖。
  • 如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖, 在文件内容末尾追加内容。
  • 在读取文件时,必须保证该文件已存在,否则报异常
  • 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
  • 字符流操作字符,只能操作普通文本文件。最常见的文本文 件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

13.4 缓冲流

13.4.1 缓冲流概述

  • 为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,默认使用8192个字节(8Kb)的缓冲区。
private static int DEFAULT_BUFFER_SIZE = 8192;
  • 缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:

    • BufferedInputStreamBufferedOutputStream 字节缓冲流
    • BufferedReaderBufferedWriter 字符缓冲流
  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区

  • 当使用BufferedInputStream读取字节文件时,BufferedInputStream会先从文件中读取到8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。

  • 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法 flush()可以强制将缓冲区的内容全部写入输出流

  • 关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也 会相应关闭内层节点流

  • flush()方法的使用:手动将buffer中内容写入文件

  • 如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷 新缓冲区,关闭后不能再写出

在这里插入图片描述

13.4.2 BufferedOutputStream

  • FileOutputStream类的子类
  • 带有缓冲区的字节输出流,在输出数据时,数据会被先存到缓冲区,缓冲区装满后,会自动刷新缓冲区中的文件到目标文件。
构造方法
  • BufferedOutputStream(OutputStream out) :创建一个缓冲字节输入流对象
  • BufferedOutputStream(OutputStream out, int size):创建一个指定缓冲区大小的缓冲字节输入流对象
常用方法
  • void flush():刷新缓冲输出流对象
  • void close() :关闭流并释放资源
  • void write(int b):写入一个字节到文件输出流
  • void write(byte[] b):写入一个字节数组到文件输出流
  • void write(byte[] b , int off , int len):写入一个字节数组的一部分到文件输出流;off表示起始位置,len表示长度
代码示例
public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 创建字节缓冲输出流对象
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\file\\demo.txt"));

        // 写入一个字节
        bos.write(97);
        bos.write('a');

        // 写入一个字节数组
        byte[] bytes = {'a' , 'b' , 'c' , 'd' , 'e'};
        bos.write(bytes);

        // 写入一个字节数组的一部分
        bos.write(bytes , 0 , 3);

        // 刷新流
        bos.flush();

        // 关闭流
        bos.close();
    }
}

13.4.3 BufferedInputStream

  • FileInputStream的子类
  • 带有缓冲区的字节输入流,读较大文件时效率较高
构造方法
  • BufferedInputStream(InputStream in) :创建一个带有缓冲区的字节输入流对象
  • BufferedInputStream(InputStream in, int size) :创建一个带有缓冲区的字节输入流对象,并设置其缓冲区大小
常用方法
  • void close():关闭流并释放资源
  • int read(): 从此输入流中读取一个字节的数据。
  • int read(byte[] b):从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
  • int read(byte[] b, int off, int len):从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
  • long skip(long n):从输入流中跳过并丢弃 n 个字节的数据。
代码示例
public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 创建一个字节缓冲输入流对象
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\file\\Demo.txt"));

        // 读取一个字节数据
        int read = bis.read();
        System.out.println("read = " + read);

        // 读取一个字节数组的数据
        byte[] bytes = new byte[3];
        System.out.println(new String(bytes));

        // 读取一个字节数组的一部分
        int len = bis.read(bytes);
        System.out.println(new String(bytes , 0 , len));

        // 关闭流
        bis.close();
    }
}
  • 文件复制
public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 创建字节缓冲输入输出流对象
        // 源文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\file\\a.txt"));
        // 目标文件
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\file\\b.txt"));

        // 定义字节数组接收读取到的数据
        byte[] bytes = new byte[1024];
        // 记录接收到的字节数组的个数
        int len ;

        // 当未读取到文件尾时,循环继续
        while ((len = bis.read(bytes)) != -1) {
            // 将读取到的数据写入目标文件
            bos.write(bytes , 0 , len);
        }

        // 刷新缓冲区
        // bos.flush();

        // 关闭流
        bos.close();
        bis.close();
    }
}

13.4.4 BufferedWriter

  • Writer类的直接子类,是一个带有缓冲区的字符输出流
构造方法
  • BufferedWriter(Writer out):创建一个使用默认大小输出缓冲区的缓冲字符输出流。
  • BufferedWriter(Writer out, int sz):创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
常用方法
  • void close():关闭流,释放资源,需要先调用flush()方法刷新流
  • void flush():刷新该流的缓冲
  • void newLine():写一入个行分隔符
    - void write(char[] cbuf, int off, int len):写入字符数组的某一部分。
  • void write(int c):写入单个字符。
  • void write(String s, int off, int len): 写入字符串的某一部分。
  • void writer(char[] cbuf):写如一个字符数组
  • void writer(String s):写入一个字符串
代码示例
public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 创建字符缓冲输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\file\\a.txt"));

        // 写入一个字符
        bw.write('好');
        // 换行
        bw.write("\r\n");

        // 写入一个字符数组
        char[] chars = {'a' , 'b' , '我' , '你' , '他'} ;
        bw.write(chars);
        bw.write("\r\n");

        // 写入一个字符数组的一部分
        bw.write(chars , 0 , 3);
        bw.write("\r\n");

        // 写入一个字符串
        bw.write("这是一个字符串");
        bw.write("\r\n");

        // 写入一个字符串的一部分
        bw.write("这是一个字符串" , 0 , 3);
        bw.write("\r\n");

        // 刷新流
        bw.flush();
        // 关闭流
        bw.close();
    }
}

13.4.5 BufferedReader

  • Reader的子类,带有缓冲区的字符输入流
构造方法
  • BufferedReader(Reader in): 创建一个使用默认大小输入缓冲区的缓冲字符输入流。
  • BufferedReader(Reader in, int sz):创建一个使用指定大小输入缓冲区的缓冲字符输入流。
常用方法
  • void close() :关闭流并释放资源
  • String getEncoding():返回此流的字符编码名称
  • int read():读取单个字符,读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1
  • int read(char[] cbuf , int offset , int length):将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
  • int read(char[] cbuf):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
  • String readLine():读取一行数据,但是不会读取到换行符,常与BufferedWriter类中的newLine()方法联用
代码示例
  • 复制文件
public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 创建字符缓冲输入输出流
        // 源文件
        BufferedReader br = new BufferedReader(new FileReader("day22\\file\\Demo1.txt"));
        // 目标文件
        BufferedWriter bw = new BufferedWriter(new FileWriter("day22\\file\\Demo3.txt"));

        // 定义String型变量line接收读取到的数据
        String line ;

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

        // 刷新流
        bw.flush();
        // 关闭流
        bw.close();
        br.close();
    }
}

13.5 转换流

13.5.1 转换流概述

  • 转换流提供了在字节流和字符流之间的转换
  • Java API提供了两个转换流:
    • InputStreamReader将InputStream转换为Reader
    • OutputStreamWriter将Writer转换为OutputStream
  • 字节流中的数据都是字符时,转成字符流操作更高效。
  • 很多时候我们使用转换流来处理文件乱码问题。实现编码和 解码的功能。

在这里插入图片描述

13.5.2 OutputStreamWriter

OutputStreamWriter构造方法
  • OutputStreamWriter(OutputStream out):创建一个字符输出流对象,使用默认字符编码的
  • OutputStreamWriter。OutputStreamWriter(OutputStream out, Charset cs):创建使用给定字符集的 OutputStreamWriter对象。
  • OutputStreamWriter(OutputStream out, CharsetEncoder enc): 创建使用给定字符集编码器的 OutputStreamWriter对象。
  • OutputStreamWriter(OutputStream out, String charsetName):创建使用指定字符集的 OutputStreamWriter对象。
OutputStreamWriter常用方法
  • void close():关闭此流,但要先刷新它。
  • void flush(): 刷新该流的缓冲。
  • void writer(int c):写入单个字符
  • void writer(char[] cbuf):写入一个字符数组
  • void writer(String str):写入一个字符串
  • void writer(char[] cbuf , int off , int len):写入一个字符数组的一部分
  • void writer(String str , int off , int len):写入一个字符串的一部分
  • String getEncoding():获取当前流所使用的字符编码方式
代码示例
public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 创建一个字符输出流
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("day22\\file\\newfile.txt"));

        // 创建一个字符数组
        char[] c = new char[] {'成' , '王' , '败' , '寇' , '1'} ;
        // 写入单个字符
        osw.write('赢');
        // 写入单个字符
        osw.write(97);
        // 向文件中写入字符数组
        osw.write(c);
        // 写入整个字符串
        osw.write("怎么回事?");
        // 写入字符数组的一部分
        osw.write(c , 0 , 3);
        // 写入字符串的一部分
        osw.write("这是一个字符串" , 0 , 3);

        // 获取当前流使用的字符编码格式
        String encoding = osw.getEncoding();
        System.out.println("encoding = " + encoding);

        // 刷新流
        osw.flush();
        // 关闭流,释放资源
        osw.close();
    }
}

13.5.3 InputStreamReader

InputStreamReader构造方法
  • InputStreamReader(InputStream in):创建一个使用默认字符集的字符输入流对象
  • InputStreamReader。InputStreamReader(InputStream in, Charset cs):创建使用给定字符集的InputStreamReader对象。
  • InputStreamReader(InputStream in, CharsetDecoder dec):创建使用给定字符集解码器的 InputStreamReader对象。
  • InputStreamReader(InputStream in, String charsetName):创建使用指定字符集的 InputStreamReader对象。
InputStreamReader常用方法
  • void close() :关闭流并释放资源
  • String getEncoding():返回此流的字符编码名称
  • int read():读取单个字符,读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个 字节的Unicode码),如果已到达流的末尾,则返回 -1
  • int read(char[] cbuf , int offset , int length):将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字 符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
  • int read(char[] cbuf):将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
代码示例
public class Demo1 {
    public static void main(String[] args) throws IOException {
//        InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\26090\\Desktop\\newfile.txt"));
        // 创建一个字符输入流,并设置其字符编码格式为UTF-8
        InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\26090\\Desktop\\newfile.txt") , "UTF-8");

        // 一次读取一个字符
        int read = isr.read();
        System.out.println("read = " + read);

        char[] c = new char[5] ;
        // 一次读取一个字符数组的一部分
        int read1 = isr.read(c);
        String string = new String(c, 0, read1);
        System.out.println("string = " + string);

        // 一次读取一个字符数组
        string = new String(c);
        System.out.println("string = " + string);

        // 获取字符编码
        System.out.println("isr.getEncoding() = " + isr.getEncoding());

        isr.close();
    }
}
  • 文件复制
public class Demo2 {
    public static void main(String[] args) throws IOException {
        // 创建字符输入输出流对象
        InputStreamReader isr = new InputStreamReader(new FileInputStream("io\\file\\Demo1.java"));
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("io\\file\\Demo1.txt"));

        // 创建字符数组
        char[] chars = new char[5] ;
        int len ;

        // isr.read(chars)) != -1表明文件还未读取完
        while ((len = isr.read(chars)) != -1) {
            // 写入另外一个文件
            osw.write(chars , 0 , len);
        }

        // 刷新流
        osw.flush();
        // 关闭流
        osw.close();
        isr.close();
    }
}

13.5.4 字符编码

  • 编码表的由来 计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字。就将各个国家的文字用数字来表示,并一一对应,形成一张表。 这就是编码表。
  • 常见的编码表
    • ASCII:美国标准信息交换码。 用一个字节的7位可以表示。
    • ISO8859-1:拉丁码表。欧洲码表 。用一个字节的8位表示。
    • GB2312:中国的中文编码表。最多两个字节编码所有字符
    • GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
    • Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的 字符码。所有的文字都用两个字节来表示。
    • UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
  • 在Unicode出现之前,所有的字符集都是和具体编码方案绑定在一起的(即字 符集≈编码方式),都是直接将字符和最终字节流绑定死了。

  • GBK等双字节编码方式,用最高位是1或0表示两个字节和一个字节。

  • Unicode不完美,这里就有三个问题,一个是,我们已经知道,英文字母只用 一个字节表示就够了,第二个问题是如何才能区别Unicode和ASCII?计算机 怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?第三个,如果 和GBK等双字节编码方式一样,用最高位是1或0表示两个字节和一个字节, 就少了很多值无法用于表示字符,不够表示所有字符。Unicode在很长一段时 间内无法推广,直到互联网的出现。

  • 面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的 编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

  • Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯 一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的 Unicode编码是UTF-8和UTF-16。

  • 编码:字符串->字节数组
  • 解码:字节数组->字符串
  • 转换流的编码应用
    • 可以将字符按指定编码格式存储
    • 可以对文本数据按指定编码格式来解读
    • 指定编码表的动作由构造器完成

13.6 标准输入、输出流

13.6.1 概述

  • System.inSystem.out分别代表了系统标准输入系统标准输出
  • 默认输入设备是:键盘,输出设备是:显示器
  • System.in的类型是InputStream
  • System.out的类型是PrintStream,其是OutputStream的子类 FilterOutputStream的子类
  • 重定向:通过System类的setIn()setOut()方法对默认设备进行改变。
    • public static void setIn(InputStream in)
    • public static void setOut(PrintStream out)
  • 标准输入流和标准输出流是两个字节流

13.6.2 示例

之前我们使用键盘输入的时候都是创建Scanner对象并将System.in作为参数传入,使用标准输入流可以替换这一操作

示例:

  • 键盘录入一个字符并输出到控制台。把标准输入流包装为字符输入流
public class PrintStreamDemo {
    public static void main(String[] args) throws IOException {
        // 把标准输入流这个字节流包装为字符流
        // 创建流
        InputStreamReader isr = new InputStreamReader(System.in);

        // 输入一个字符
        System.out.print("请输入一个字符:");
        int c = isr.read();

        // 输出
        System.out.print("你输入的字符为:");
        System.out.println((char)c);

        // 关闭流
        isr.close();
    }
}

请输入一个字符:o
你输入的字符为:o

  • 键盘输入一个字符串,由控制台打印输出
    • 将标准输入流包装为字符输入流,再包装为缓冲流
public class Demo1 {
    public static void main(String[] args) throws IOException {
        // 将标准输入流包装为字符输入流,再包装为缓冲流
//        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        // 将标准输入流包装为字符输入流
        InputStreamReader isr = new InputStreamReader(System.in);

        // 将字符输入流包装为缓冲流
        BufferedReader br = new BufferedReader(isr);
        
        // 键盘录入一个字符串
        System.out.print("请输入一个人字符串:");
        String line = br.readLine();

        // 关闭流
        // 关闭流时,只需要关闭最外层的流即可
        br.close();

        System.out.print("你输入的字符串为:");
        System.out.println(line);
    }
}

请输入一个人字符串:别再停电了!!!
你输入的字符串为:别再停电了!!!

13.7 打印流

13.7.1 概述

  • 实现将基本数据类型的数据格式转化为字符串输出
  • 打印流:PrintStream和PrintWriter
    • 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
    • PrintStream和PrintWriter的输出不会抛出IOException异常
    • PrintStream和PrintWriter有自动flush功能
    • PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。 在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
    • System.out返回的是PrintStream的实例

13.7.2 示例

public class Demo2 {
    public static void main(String[] args) throws FileNotFoundException {
        PrintStream ps = new PrintStream(new FileOutputStream("D:\\file\\print.txt"));
        int i = 1 ;
        while (i <= 255) {
            ps.print((char) i);
            if (i % 20 == 0) {
                ps.println();
            }
            i ++ ;
        }
        ps.close();
    }
}

13.8 数据流

  • 为了方便地操作Java语言的基本数据类型和String的数据,可以使用数据流。
  • 数据流有两个类:(用于读取和写出基本数据类型、String类的数据)
    • DataInputStream 和 DataOutputStream
    • 分别“套接”在 InputStream 和 OutputStream 子类的流上
  • DataInputStream中的方法
    • boolean readBoolean()
    • char readChar()
    • double readDouble()
    • long readLong()
    • byte readByte()
    • float readFloat()
    • short readShort()
    • int readInt()
    • String readUTF()
  • DataOutputStream中的方法
    • boolean writeBoolean()
    • char writeChar()
    • double writeDouble()
    • long writeLong()
    • byte writeByte()
    • float writeFloat()
    • short writeShort()
    • int writeInt()
    • String writeUTF()
public class DataStreamDemo {
    public static void main(String[] args) throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("day23\\file\\data.txt"));

        dos.writeByte(5);
        dos.writeUTF("字符串");
        dos.writeBoolean(true);

        DataInputStream dis = new DataInputStream(new FileInputStream("day23\\file\\data.txt"));

        byte b = dis.readByte();
        System.out.println(b);

        String s = dis.readUTF();
        System.out.println(s);

        boolean b1 = dis.readBoolean();
        System.out.println(b1);

        dis.close();
        dos.close();
    }
}

读取超出文件末尾时会报EOFException

13.9 对象流

13.9.1 对象流概述

  • ObjectInputStreamObjectOutputSteam

  • 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

  • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制,将对象写入文件,实现永久保存

  • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制 ,将文件中的序列化对象读取到内存中

  • ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

  • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从 而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传 输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原 来的Java对象

  • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据, 使其在保存和传输时可被还原

  • 序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返 回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础

  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可 序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。 否则,会抛出NotSerializableException异常

    • Serializable
    • Externalizable

Exception in thread “main” java.io.NotSerializableException: cn.pdsu.edu._objstream.Student

  • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
    • private static final long serialVersionUID;
    • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
    • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自 动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议, 显式声明。
  • 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验 证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同 就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异 常。(InvalidCastException)

Exception in thread “main” java.io.InvalidClassException: cn.pdsu.edu._objstream.Student; local class incompatible: stream classdesc serialVersionUID = 6313230627945453726, local class serialVersionUID = -9098633194752760340

13.9.2 ObjectOutputStream 序列化流

序列化就是将对象存入硬盘,实现永久存储

构造方法

ObjectOutputStream(OutputStream out) :创建序列化流对象

常用方法

任意类型的数据都能存储

  • void writeObject(Object obj):将指定的对象写入 ObjectOutputStream。

在这里插入图片描述

示例代码
public class Demo3 {
    public static void main(String[] args) throws IOException {
        // 创建序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\file\\obj.txt"));

        // 向文件中存入对象,即序列化对象
        oos.writeObject(100);
        oos.writeObject("字符串");
        oos.writeObject(new Student("111" , "张三" , 20 , "北京"));
        oos.writeObject(new Random());
        oos.writeObject(false);

        // 关闭流
        oos.close();
    }
}

直接使用记事本打开是类似于乱码的
在这里插入图片描述

13.9.3 ObjectInputStream 反序列化流

反序列化就是将对象从硬盘上读取到内存中,不具有永久存储

构造方法
  • ObjectInputStream(InputStream in) :创建反序列化流对象
常用方法
  • Object readObject(): 读取一个对象。
    在这里插入图片描述
示例代码
public class Demo4 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 创建反序列化流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\file\\obj.txt"));

        // 读取对象
        Object o = ois.readObject();
        System.out.println("o = " + o);

        o = ois.readObject();
        System.out.println("o = " + o);

        o = ois.readObject();
        System.out.println("o = " + o);

        o = ois.readObject();
        System.out.println("o = " + o);

        o = ois.readObject();
        System.out.println("o = " + o);

        // 第六次读取
        o = ois.readObject();
        System.out.println("o = " + o);
        
        // 关闭流
        ois.close();
    }
}

o = 100
o = 字符串
o = Student{id=‘111’, name=‘张三’, age=20, address=‘北京’}
o = java.util.Random@378bf509
o = false
Exception in thread “main” java.io.EOFException
at java.io.ObjectInputStream$BlockDataInputStream.peekByte(ObjectInputStream.java:2917)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1502)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at cn.pdsu.edu._objstream.Demo4.main(Demo4.java:29)

EOFExceptionEnd Of File Exception,读取到了文件的末尾了,可以使用try-catch语句块解决,但是可以使用一种更好用的方法,在序列化对象时,可以将对象存到一个容器中,将这个容器进行序列化,读取时从容器中取,即ArrayList

public class Demo5 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 创建序列化和反序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\file\\obj.txt"));
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\file\\obj.txt"));


        // 将对象先存入ArrayList集合
        ArrayList<Object> list = new ArrayList<>();
        Collections.addAll(list , "字符串" , 100 , new Random() , false , new Student("111" , "张三" , 20 , "北京")) ;

        // 将ArrayList集合对象序列化
        oos.writeObject(list);

        // 将对象进行反序列化操作
        ArrayList<Object> list1 = (ArrayList<Object>) ois.readObject() ;

        for (Object o : list1) {
            System.out.println("o = " + o);
        }

        // 关闭流
        ois.close();
        oos.close();
    }
}

13.9.4 对java.io.Serializable接口的理解

  • 实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后 完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机 制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创 建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里 准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必 关心字节的顺序或者其他任何细节。
  • 由于大部分作为参数的类如String、Integer等都实现了 java.io.Serializable的接口,也可以利用多态的性质,作为参数使接口更 灵活。
  • 接口中没有任何方法,它是作为类似于一个标记的东西存在的,告诉虚拟机这个类的对象是可序列化的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值