Java I/O 流

前言

1、IO 类型

  • Java 中的 IO 分 3 类:BIO、NIO、AIO。

  • BIO 同步阻塞 IO。

    • 对应 java.io 包提供的工具,是我们平常使用的传统 IO。
    • 基于流模型,虽然直观,代码实现也简单,但是扩展性差,消耗资源大,容易成为系统的瓶颈。
    • 它的特点是模式简单使用方便,并发处理能力低。
  • NIO 同步非阻塞 IO。

    • 对应 java.nio 包提供的工具,是传统 IO 的升级。
    • 核心类包含 Channel,Selector,Buffer,Charset。
    • 它实现了多路复用,一个线程高效管理多个客户端连接,通过事件监听处理感兴趣的事件。
  • AIO 异步非阻塞 IO,也叫 NIO2,

    • 操作基于事件和回调机制。
  • 服务器实现

    • BIO 服务器实现模式为 一个连接一个线程。

      • 即客户端有连接请求时服务器端就需要启动一个线程进行处理。
      • 如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
    • NIO 服务器实现模式为 一个请求一个线程。

      • 即客户端发送的连接请求都会注册到多路复用器上。
      • 多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。
    • AIO 服务器实现模式为 一个有效请求一个线程。

      • 客户端的 I/O 请求都是由 OS 先完成了再通知服务器应用去启动线程进行处理。
  • 适用场景

    • BIO 方式适用于连接数目比较小且固定的架构。

      • 这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4 以前的唯一选择,但程序直观简单易理解。
    • NIO 方式适用于连接数目多且连接比较短(轻操作)的架构。

      • 比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4 开始支持。
    • AIO 方式使用于连接数目多且连接比较长(重操作)的架构。

      • 比如相册服务器,充分调用 OS 参与并发操作,编程比较复杂,JDK7 开始支持。

2、流

  • 流 (Stream) 就是一系列的数据,当不同的介质之间有数据交互的时候,Java 就使用流来实现。

  • 数据源可以是文件,还可以是数据库,网络甚至是其他的程序。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package java.util.stream;

    /**
    * A sequence of elements supporting sequential and parallel aggregate
    * operations. The following example illustrates an aggregate operation using
    * {@link Stream} and {@link IntStream}:
    *
    * @see IntStream, LongStream, DoubleStream, java.util.stream
    * @since 1.8
    */

    public interface Stream<T> extends BaseStream<T, Stream<T>> {

    }
  • 比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流。

    1
    2
    3
    4
    File f = new File("d:/lol.txt");

    // 通过这个输入流,就可以把数据从硬盘,读取到 Java 的虚拟机中来,也就是读取到内存中
    FileInputStream fis = new FileInputStream(f);

2.1 Java 流的类型

  • Java 流关系图

    • 上图基于JDK 1.8 制作,其中需要注意的是 StringBufferInputStreamLineNumberInputStream 已被废弃。
    • StringBufferInputStream 建议用 StringReader 来取代使用。
    • LineNumberInputStream 建议用 LineNumberReader 来取代使用。
  • Java 中所有的流都是基于字节流,所以最基本的流是 输入 InputStream 输出 OutputStream 字节流。

  • 在字节流的基础上,封装了字符流,进一步,又封装了缓存流以及数据流、对象流,以及一些其他的奇奇怪怪的流。

  • 字节流字符流 等区别

    • Stream 结尾的是字节流,字节流只能处理字节流。
    • Writer/Reader 结尾的是字符流,字符流既可以处理字符流,也可以处理字节流。

    • 字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的。

    • 字符流在操作时使用了缓冲区,通过缓冲区再操作文件。

2.2 关闭流的方式

  • 所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭

  • 如果不关闭,会产生对资源占用的浪费。当量比较大的时候,会影响到业务的正常开展。

    1
    2
    3
    4
    5
    File f = new File("d:/lol.txt");
    FileInputStream fis = new FileInputStream(f);

    // 关闭流
    fis.close();
  • 关闭方式

    • try 中关闭:如果文件不存在,或者读取的时候出现问题而抛出异常,不推荐使用。
    • finally 中关闭:首先把流的引用声明在 try 的外面,在 finally 关闭之前,要先判断该引用是否为空,关闭的时候,需要再一次进行 try catch 处理,看上去很繁琐。
    • 使用 try() 的方式:把流定义在 try() 里,trycatch 或者 finally 结束的时候,会自动关闭。这种编写代码的方式叫做 try-with-resources。
  • 所有的流,都实现了一个接口叫做 AutoCloseable,任何类实现了这个接口,都可以在 try() 中进行实例化。 并且在 trycatchfinally 结束的时候自动关闭,回收相关资源。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    File f = new File("d:/lol.txt");

    // 把流定义在 try() 里,try、catch 或者 finally 结束的时候,会自动关闭
    try (FileInputStream fis = new FileInputStream(f)) {

    byte[] all = new byte[(int) f.length()];
    fis.read(all);
    for (byte b : all) {
    System.out.println(b);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

2.3 中文编码问题

  • Java 采用的是 Unicode(统一码,万国码)编码方式,每个数字都是很长的(4 个字节),不仅要表示字母,还要表示汉字等。
  • UTF-8,UTF-16 和 UTF-32 是针对不同类型的数据有不同的减肥效果的子编码,一般说来 UTF-8 是比较常用的方式。
  • UTF-8 对数字和字母就使用一个字节,而对汉字就使用 3 个字节。

  • FileInputStream 字节流正确读取中文

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    File f = new File("E:\\project\\j2se\\src\\test.txt");

    try (FileInputStream fis = new FileInputStream(f)) {
    byte[] all = new byte[(int) f.length()];
    fis.read(all);

    // 文件中读出来的数据是
    for (byte b : all) {
    int i = b&0x000000ff; // 只取 16 进制的后两位
    System.out.println(Integer.toHexString(i));
    }

    // 把这个数字,放在 GBK 的棋盘上去
    String str = new String(all, "GBK");
    System.out.println(str);
    } catch (IOException e) {
    e.printStackTrace();
    }
  • FileReader 字符流正确读取中文

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    File f = new File("E:\\project\\j2se\\src\\test.txt");

    // 默认编码方式
    // FileReader 得到的是字符,所以一定是已经把字节根据某种编码识别成了字符了
    // 而 FileReader 使用的编码方式是 Charset.defaultCharset() 的返回值,如果是中文的操作系统,就是 GBK
    try (FileReader fr = new FileReader(f)) {
    char[] cs = new char[(int) f.length()];
    fr.read(cs);

    // FileReader 使用默认的编码方式,识别出来的字符是
    System.out.println(new String(cs));
    } catch (IOException e) {
    e.printStackTrace();
    }

    // FileReader 是不能手动设置编码方式的,为了使用其他的编码方式,只能使用 InputStreamReader 来代替
    // 并且使用 new InputStreamReader(new FileInputStream(f),Charset.forName("UTF-8")); 这样的形式
    try (InputStreamReader isr = new InputStreamReader(new FileInputStream(f), Charset.forName("UTF-8"))) {
    char[] cs = new char[(int) f.length()];
    isr.read(cs);

    // InputStreamReader 指定编码方式 UTF-8,识别出来的字符是
    System.out.println(new String(cs));
    } catch (IOException e) {
    e.printStackTrace();
    }

3、字节流

  • Stream 结尾的就是字节流,以字节的形式读取和写入数据,只能处理字节流。

    • 字节输入流:InputStream
    • 字节输出流:OutputStream
  • 字节流传输的数据单位是字节,也意味着字节流能够读写任意一种文件。

  • 字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的。

  • 字节输入流 InputStream 是抽象类,只提供方法声明,不提供方法的具体实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package java.io;

    /**
    * This abstract class is the superclass of all classes representing
    * an input stream of bytes.
    *
    * @author Arthur van Hoff
    * @see java.io.BufferedInputStream, java.io.ByteArrayInputStream, java.io.DataInputStream, java.io.FilterInputStream
    * @see java.io.InputStream#read(), java.io.OutputStream, java.io.PushbackInputStream
    * @since JDK1.0
    */
    public abstract class InputStream implements Closeable {

    }
  • 字节输出流 OutputStream 是抽象类,只提供方法声明,不提供方法的具体实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package java.io;

    /**
    * This abstract class is the superclass of all classes representing
    * an output stream of bytes. An output stream accepts output bytes
    * and sends them to some sink.
    *
    * @author Arthur van Hoff
    * @see java.io.BufferedOutputStream, java.io.ByteArrayOutputStream, java.io.DataOutputStream
    * @see java.io.FilterOutputStream, java.io.InputStream, java.io.OutputStream#write(int)
    * @since JDK1.0
    */
    public abstract class OutputStream implements Closeable, Flushable {

    }

3.1 文件字节流

  • 文件 输入/输出 字节流

    • 文件字节输入流:FileInputStream
    • 文件字节输出流:FileOutputStream
  • 文件字节输入流 FileInputStreamInputStream 的子类。

    1
    2
    3
    4
    5
    package java.io;

    public class FileInputStream extends InputStream {

    }
  • 文件字节输出流 FileOutputStreamOutputStream 的子类。

    1
    2
    3
    4
    5
    package java.io;

    public class FileOutputStream extends OutputStream {

    }
  • 读取文件内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;

    File f = new File("d:/lol.txt"); // 准备文件 lol.txt 其中的内容是 AB,对应的 ASCII 分别是 65 66

    // 读取 数据
    try (FileInputStream fis = new FileInputStream(f)) { // 创建基于文件的输入流
    byte[] all = new byte[(int) f.length()]; // 创建字节数组,其长度就是文件的长度

    fis.read(all); // 以字节流的形式读取文件所有内容

    for (byte b : all) {
    System.out.println(b); // 打印出来是 65 66
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
  • 向文件写入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;

    File f = new File("d:/lol2.txt"); // 准备文件 lol2.txt 其中的内容是空的

    // 写入 数据
    try (FileOutputStream fos = new FileOutputStream(f)) { // 创建基于文件的输出流
    byte data[] = {88, 89}; // 准备长度是 2 的字节数组,用 88, 89 初始化,其对应的字符分别是 X, Y

    fos.write(data); // 把数据写入到输出流

    } catch (IOException e) {
    e.printStackTrace();
    }
  • 如果文件 d:/lol2.txt 不存在,写出操作会自动创建该文件。

  • 但是如果是文件 d:/xyz/lol2.txt,而目录 xyz 又不存在,会抛出异常。

3.2 数据流

  • 数据流

    • 数据输入流:DataInputStream
    • 数据输出流:DataOutputStream
  • 数据输入流 DataInputStreamInputStream 的子类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package java.io;

    public class FilterInputStream extends InputStream {

    }

    public class DataInputStream extends FilterInputStream
    implements DataInput {

    }
  • 数据输出流 DataOutputStreamOutputStream 的子类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package java.io;

    public class FilterOutputStream extends OutputStream {

    }

    public class DataOutputStream extends FilterOutputStream
    implements DataOutput {

    }
  • 数据流必须建立在一个存在的流的基础上。

  • 字符串的读写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;

    File f = new File("d:/lol.txt");

    // 读取 数据
    try (FileInputStream fis = new FileInputStream(f);
    DataInputStream dis = new DataInputStream(fis);) { // 创建 数据输入流

    boolean b= dis.readBoolean();
    int i = dis.readInt();
    String str = dis.readUTF();

    System.out.println("读取到布尔值: " + b);
    System.out.println("读取到整数: " + i);
    System.out.println("读取到字符串: " + str);
    } catch (IOException e) {
    e.printStackTrace();
    }

    // 写入 数据
    try (FileOutputStream fos = new FileOutputStream(f);
    DataOutputStream dos = new DataOutputStream(fos);) { // 创建 数据输出流

    dos.writeBoolean(true);
    dos.writeInt(300);
    dos.writeUTF("123 this is gareen");
    } catch (IOException e) {
    e.printStackTrace();
    }

3.3 对象流

  • 对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘。

    • 对象输入流:ObjectInputStream
    • 对象输出流:ObjectOutputStream
  • 对象输入流 ObjectInputStreamInputStream 的子类。

    1
    2
    3
    4
    5
    6
    package java.io;

    public class ObjectInputStream extends InputStream
    implements ObjectInput, ObjectStreamConstants {

    }
  • 对象输出流ObjectOutputStreamOutputStream 的子类。

    1
    2
    3
    4
    5
    6
    package java.io;

    public class ObjectOutputStream extends OutputStream
    implements ObjectOutput, ObjectStreamConstants {

    }
  • 一个对象以流的形式进行传输,叫做序列化该对象所对应的类,必须是实现 Serializable 接口。

  • 对象流必须建立在一个存在的流的基础上。

  • 序列化一个对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;

    // 要把 Hero 对象直接保存在文件上,务必让 Hero 类实现 Serializable 接口
    Hero h = new Hero("garen", 616);

    File f = new File("d:/garen.lol"); // 准备一个文件用于保存该对象

    try (FileOutputStream fos = new FileOutputStream(f);
    ObjectOutputStream oos =new ObjectOutputStream(fos); // 创建对象输出流
    FileInputStream fis = new FileInputStream(f);
    ObjectInputStream ois =new ObjectInputStream(fis);) { // 创建对象输入流

    oos.writeObject(h); // 写入 数据
    Hero h2 = (Hero) ois.readObject(); // 读取 数据

    System.out.println(h2.name);
    System.out.println(h2.hp);
    } catch (IOException e) {
    e.printStackTrace();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }

4、字符流

  • Writer/Reader 结尾的就是字符流,以字符的形式读取和写入数据,字符流既可以处理字符流,也可以处理字节流。

    • 字符输入流:Reader
    • 字符输出流:Writer
  • 字符流在操作时使用了缓冲区,通过缓冲区再操作文件。

  • 在关闭字符流时会强制性地将缓冲区中的内容进行输出,但是如果程序没有关闭,则缓冲区中的内容是无法输出的。

  • 字符输入流 Reader 是抽象类,只提供方法声明,不提供方法的具体实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package java.io;

    /**
    * Abstract class for reading character streams. The only methods that a
    * subclass must implement are read(char[], int, int) and close(). Most
    * subclasses, however, will override some of the methods defined here in order
    * to provide higher efficiency, additional functionality, or both.
    *
    * @author Mark Reinhold
    * @see BufferedReader, LineNumberReader, CharArrayReader, InputStreamReader, FileReader
    * @see FilterReader, PushbackReader, PipedReader, StringReader, Writer
    * @since JDK1.1
    */

    public abstract class Reader implements Readable, Closeable {

    }
  • 字符输出流 Writer 是抽象类,只提供方法声明,不提供方法的具体实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package java.io;

    /**
    * Abstract class for writing to character streams. The only methods that a
    * subclass must implement are write(char[], int, int), flush(), and close().
    * Most subclasses, however, will override some of the methods defined here in
    * order to provide higher efficiency, additional functionality, or both.
    *
    * @author Mark Reinhold
    * @see Writer, BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, FileWriter
    * @see PipedWriter, PrintWriter, StringWriter, Reader
    * @since JDK1.1
    */

    public abstract class Writer implements Appendable, Closeable, Flushable {

    }

4.1 文件字符流

  • 文件 输入/输出 字符流

    • 文件字符输入流:FileReader
    • 文件字符输出流:FileWriter
  • 文件字符输入流 FileReaderReader 的子类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package java.io;

    public class InputStreamReader extends Reader {

    }

    public class FileReader extends InputStreamReader {

    }
  • 文件字符输出流 FileWriterWriter 的子类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package java.io;

    public class OutputStreamWriter extends Writer {

    }

    public class FileWriter extends OutputStreamWriter {

    }
  • 读取文件内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import java.io.File;
    import java.io.FileReader;
    import java.io.IOException;

    File f = new File("d:/lol.txt"); // 准备文件 lol.txt 其中的内容是 AB

    // 读取 数据
    try (FileReader fr = new FileReader(f)) { // 创建基于文件的 Reader
    char[] all = new char[(int) f.length()]; // 创建字符数组,其长度就是文件的长度

    fr.read(all); // 以字符流的形式读取文件所有内容

    for (char b : all) {
    System.out.println(b); // 打印出来是 A B
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
  • 向文件写入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;

    File f = new File("d:/lol2.txt"); // 准备文件 lol2.txt

    // 写入 数据
    try (FileWriter fr = new FileWriter(f)) { // 创建基于文件的 Writer
    String data ="abcdefg1234567890";
    char[] cs = data.toCharArray();

    fr.write(cs); // 以字符流的形式把数据写入到文件中

    } catch (IOException e) {
    e.printStackTrace();
    }

4.2 缓存流

  • 缓存 输入/输出 字符流

    • 缓存字符输入流:BufferedReader
    • 缓存字符输出流:PrintWriter
  • 缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。

  • 在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了 IO 操作。

  • 缓存字符输入流 BufferedReaderReader 的子类。可以一次读取一行数据。

    1
    2
    3
    4
    5
    package java.io;

    public class BufferedReader extends Reader {

    }
  • 缓存字符输出流 PrintWriterWriter 的子类。可以一次写出一行数据。

    1
    2
    3
    4
    5
    package java.io;

    public class PrintWriter extends Writer {

    }
  • 缓存流必须建立在一个存在的流的基础上。

  • 读取数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileReader;
    import java.io.IOException;

    File f = new File("d:/lol.txt"); // 准备文件 lol.txt 其中的内容是

    // 读取 数据
    try (FileReader fr = new FileReader(f);
    BufferedReader br = new BufferedReader(fr);) { // 创建文件字符流,缓存流必须建立在一个存在的流的基础上

    while (true) {
    String line = br.readLine(); // 一次读一行
    if (null == line) {
    break;
    }
    System.out.println(line);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
  • 写入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.PrintWriter;

    File f = new File("d:/lol2.txt"); // 向文件 lol2.txt 中写入三行语句

    // 写入 数据
    try (FileWriter fw = new FileWriter(f);
    PrintWriter pw = new PrintWriter(fw);) { // 创建文件字符流,缓存流必须建立在一个存在的流的基础上

    pw.println("garen kill teemo");
    pw.println("teemo revive after 1 minutes");
    pw.println("teemo try to garen, but killed again");
    } catch (IOException e) {
    e.printStackTrace();
    }
  • 有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到 flush

  • 立即把数据写入到硬盘

    1
    2
    3
    pw.println("garen kill teemo");

    pw.flush(); // 强制把缓存中的数据写入硬盘,无论缓存是否已满

5、文件对象

  • File 类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package java.io;

    /**
    * An abstract representation of file and directory pathnames.
    *
    * @author unascribed
    * @since JDK1.0
    */

    public class File
    implements Serializable, Comparable<File> {

    }
  • 构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 路径名
    // 父路径名 和 子路径名
    // URI(统一资源标识符)

    File(String pathname)
    File(String pathname, int prefixLength)

    File(String parent, String child)

    File(File parent, String child)
    File(String child, File parent)

    File(URI uri)
  • File 类的对象是文件或目录的路径名的抽象表示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import java.io.File;

    // 绝对路径
    File f1 = new File("d:/LOLFolder");

    // 相对路径,相对于工作目录,如果在 eclipse 中,就是项目目录
    File f2 = new File("LOL.exe");

    // 把 f1 作为父目录创建文件对象
    File f3 = new File(f1, "LOL.exe");
  • 抽象路径名

    1
    2
    3
    4
    // 名为 test.txt 的文件不必存在,以使用此语句创建 File 对象
    // dummyFile 对象表示抽象路径名,它可能指向或可能不指向文件系统中的真实文件

    File dummyFile = new File("test.txt");

5.1 当前工作目录

  • JVM 的当前工作目录是根据我们如何运行 Java 命令来设置的。
  • 可以通过读取 user.dir 系统属性来获取 JVM 的当前工作目录。

    1
    String workingDir = System.getProperty("user.dir");
  • 使用 System.setProperty() 方法更改当前工作目录。

    1
    System.setProperty("user.dir", "C:\\myDir");
  • 要在 Windows 上指定 C:\ test 作为 user.dir 系统属性值,运行如下所示的程序。

    1
    java -Duser.dir = C:\test your-java-class

5.2 文件分隔符

  • 不同的操作系统使用不同的字符来分隔路径名中的两个部分。

    • 在 windows 中 文件文件分隔符 用反斜杠 ‘ \ ‘ 或者正斜杠 ‘ / ‘ 都可以。
    • 在 Linux 中,是不识别反斜杠 ‘ \ ‘ 的,必须使用正斜杠 ‘ / ‘ 都可以。
  • File 类定义了 File.separator,它是系统默认的文件分隔符号。

    • File.separator 在 windows 中的作用相当于 ‘ \ ‘,在 UNIX 系统上,此字段的值为 ‘ / ‘。
    • File.separator 将我们的分隔符作为 String。
    • File.separator 保证了在任何系统下不会出错。
  • 此外 File 类还有

    1
    2
    3
    4
    5
    6
    7
    8
    // 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符
    separatorChar

    // 与系统有关的路径分隔符,为了方便,它被表示为一个字符串
    pathSeparatorChar

    // 此字符用于分隔以路径列表形式给定的文件序列中的文件名,在 UNIX 系统上此字段为 ' : ',在 Windows 系统上,它为 ' ;'
    pathSeparator

5.3 文件创建

  • 使用 File 类的 createNewFile() 方法创建一个新文件

    1
    2
    3
    4
    5
    6
    7
    8
    try {
    File dummyFile = new File("test.txt");

    // 创建一个新文件
    boolean fileCreated = dummyFile.createNewFile();
    } catch (IOException e) {

    }
    • 如果指定名称的文件不存在,该 createNewFile() 方法创建一个新的空文件。
    • 如果文件已成功创建,则返回 true;否则,返回 false。
    • 如果发生 I/O 错误,该方法将抛出 IOException。
  • 可以在默认的临时文件目录或目录中创建一个临时文件。

  • 要在默认临时目录中创建临时文件,使用 File 类的 createTempFile() 静态方法,该方法接受前缀和后缀以生成临时文件名。

    1
    2
    // 创建临时文件
    File tempFile = File.createTempFile("abc", ".txt");

5.4 文件夹创建

  • 可以使用 mkdir()mkdirs() 方法创建一个新目录。

  • 仅当路径名中指定的父目录已存在时,mkdir() 方法才创建目录。

    1
    2
    3
    4
    5
    6
    7
    File newDir = new File("C:\\users\\home");

    // 只有当 C:\users 目录已经存在时,才会创建主目录 home
    boolean dirCreated = newDir.mkdir();

    // 如果 C:\users 目录不存在 将创建 users 目录
    boolean dirCreated = newDir.mkdirs()

5.5 常用方法

  • 常用方法
关键字 介绍
Path-component accessors
getName 获取文件名
getParent 以字符串形式返回获取所在文件夹
getParentFile 以文件形式返回获取所在文件夹
getPath Converts this abstract pathname into a pathname string.
isAbsolute Tests whether this abstract pathname is absolute.
getAbsolutePath Returns the absolute pathname string of this abstract pathname.
getAbsoluteFile Returns the absolute form of this abstract pathname. Equivalent to new File (this.getAbsolutePath).
getCanonicalPath Returns the canonical pathname string of this abstract pathname.
getCanonicalFile Returns the canonical form of this abstract pathname. Equivalent to new File (this.getCanonicalPath).
toURL Converts this abstract pathname into a file: URL.
toURI Constructs a file: URI that represents this abstract pathname.
Attribute accessors
lastModified 获取文件最后修改时间
length 获取文件的长度
exists 判断文件是否存在
isDirectory 判断是否是文件夹
isFile 判断是否是文件(非文件夹)
isHidden Tests whether the file named by this abstract pathname is a hidden file.
canRead Tests whether the application can read the file denoted by this abstract pathname.
canWrite Tests whether the application can modify the file denoted by this abstract pathname.
File operations
createNewFile 创建一个空文件,如果父文件夹不存在,就会抛出异常,所以创建一个空文件之前,通常都会创建父目录 .getParentFile().mkdirs()
delete 刪除文件
deleteOnExit JVM 结束的时候,刪除文件,常用于临时文件的删除
list 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
listFiles 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
mkdir 创建文件夹,如果父文件夹不存在,创建就无效
mkdirs 创建文件夹,如果父文件夹不存在,就会创建父文件夹
renameTo 文件重命名,用于对物理文件名称进行修改,但是并不会修改 File 对象的 name 属性
setLastModified 设置文件修改时间
setReadOnly Marks the file or directory named by this abstract pathname so that only read operations are allowed.
setReadable Sets the owner’s or everybody’s read permission for this abstract pathname.
setWritable Sets the owner’s or everybody’s write permission for this abstract pathname.
setExecutable Sets the owner’s or everybody’s execute permission for this abstract pathname.
canExecute Tests whether the application can execute the file denoted by this abstract pathname.
Filesystem interface
listRoots 列出所有的盘符 c: d: e: 等等
Disk usage
getTotalSpace Returns the size of the partition named by this abstract pathname.
getFreeSpace Returns the number of unallocated bytes in the partition named by this abstract path name.
getUsableSpace Returns the number of bytes available to this virtual machine on the partition named by this abstract pathname.
Basic infrastructure
compareTo Compares two abstract pathnames lexicographically.
equals Tests this abstract pathname for equality with the given object.
hashCode Computes a hash code for this abstract pathname.
toString Returns the pathname string of this abstract pathname.
writeObject WriteObject is called to save this filename.
readObject readObject is called to restore this filename.
toPath Returns a java.nio.file.Path object constructed from the this abstract path.
  • 文件删除

    • 使用 File 类的 delete() 方法来删除文件/目录。
    • 目录必须为空,才能删除它。
    • 如果文件/目录被删除,该方法返回 true; 否则,返回 false。
    • 还可以延迟删除文件,直到 JVM 通过使用 deleteOnExit() 方法终止。
    • 如果在程序中创建临时文件,当程序退出时要删除。

      1
      2
      3
      4
      5
      6
      7
      File dummyFile = new File("dummy.txt"); 

      // 立即删除文件
      dummyFile.delete();

      // 在 JVM 终止时删除文件
      dummyFile.deleteOnExit();
  • 文件重命名

    • File 对象是不可变的。创建后,它始终表示相同的路径名。当重命名文件时,旧的 File 对象仍然代表原始的路径名。
    • File 对象表示路径名,而不是文件系统中的实际文件。

    • 要重命名文件,使用 renameTo() 方法,它使用一个 File 对象来表示新文件。

    • 如果文件的重命名成功,返回 true;否则,返回 false。

      1
      2
      3
      4
      File oldFile = new File("old_dummy.txt");
      File newFile = new File("new_dummy.txt");

      boolean fileRenamed = oldFile.renameTo(newFile);
  • 文件属性

    • File 类包含获取/设置文件和目录的属性的方法。
    • 可以分别使用 setReadOnly()setReadable()setWritable()setExecutable() 方法将文件设置为只读,可读,可写和可执行。
    • 可以使用 lastModified()setLastModified() 方法来获取和设置文件的最后修改日期和时间。
    • 可以使用 isHidden() 方法检查文件是否被隐藏。
  • 文件大小

    • 可以使用 File 类的 length() 方法获取文件的大小(以字节为单位)。
    • 如果 File 对象表示不存在的文件,则 length() 方法返回零。
    • length() 方法的返回类型是 long,而不是 int。

      1
      2
      File myFile = new File("myfile.txt");
      long fileLength = myFile.length();
  • 列出文件和目录

    • 可以使用 File 类的 listRoots() 静态方法获取文件系统中可用根目录的列表。
    • 它返回一个 File 对象数组。

      1
      File[] roots = File.listRoots();
    • 可以使用 File 类的 list()listFiles() 方法列出目录中的所有文件和目录。

    • list() 方法返回一个 String 数组,而 listFiles() 方法返回一个 File 数组。
    • 还可以使用这些方法的文件过滤器从返回的结果中排除一些文件和目录。

      1
      2
      File dir = new File(dirPath);
      File[] list = dir.listFiles();
  • 文件过滤器

    • 要从列表中排除扩展名为 .SYS 的所有文件,我们可以使用由功能接口 FileFilter 的实例表示的文件过滤器来实现。
    • 它包含一个 accept() 方法,它将 File 作为参数列出,如果应该列出文件,则返回 true,返回 false 不会列出文件。

    • 以下代码创建一个文件过滤器,将过滤扩展名为 .SYS 的文件。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      FileFilter filter = file -> {
      if (file.isFile()) {
      String fileName = file.getName().toLowerCase();
      if (fileName.endsWith(".sys")) {
      return false;
      }
      }
      return true;
      };
    • 以下代码创建两个文件过滤器:一个仅过滤文件,另一个仅过滤目录。

      1
      2
      3
      4
      5
      // Filters only files
      FileFilter fileOnlyFilter = File::isFile;

      // Filters only directories
      FileFilter dirOnlyFilter = File::isDirectory;
    • 以下代码显示如何使用 FileFilter 过滤文件。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // Create a file filter to exclude any .SYS file
      FileFilter filter = file -> {
      if (file.isFile()) {
      String fileName = file.getName().toLowerCase();
      if (fileName.endsWith(".sys")) {
      return false;
      }
      }
      return true;
      };

      String dirPath = "C:\\";
      File dir = new File(dirPath);

      File[] list = dir.listFiles(filter);

      for (File f : list) {
      if (f.isFile()) {
      System.out.println(f.getPath() + " (File)");
      } else if (f.isDirectory()) {
      System.out.println(f.getPath() + " (Directory)");
      }
      }

5.6 文件上传/预览/下载

  • 文件 存储路径配置

    1
    2
    3
    4
    5
    // application.yaml

    file:
    path: /System/Volumes/Data/UploadFiles/demo
    server: https://file.qianchia.com
  • 文件 上传

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    // FileUploadController.java

    import org.springframework.web.multipart.MultipartFile;
    import java.io.*;
    import java.util.*;

    @Controller
    @RequestMapping("/api/v1")
    public class FileUploadController {

    @Value("${file.path}")
    String filePath;

    @Value("${file.server}")
    String fileServer;

    /**
    * 文件上传
    */
    @RequestMapping(value = "/file/upload", method = RequestMethod.POST)
    @ResponseBody
    public Response<Map<String, Object>> fileUpload(@RequestParam("file") MultipartFile file) {

    File destFolder = new File(filePath); // 文件存储路径
    if (!destFolder.isDirectory()) {
    destFolder.mkdirs();
    }

    String originalFilename = file.getOriginalFilename(); // 原始 文件名

    String[] strArray = originalFilename.split("\\.");
    String suffix = strArray[strArray.length - 1]; // 获取文件扩展名

    List<String> suffixs = Arrays.asList(new String[]{"pdf", "jpg", "jpeg", "bmp", "png", "gif"});
    if (!suffixs.contains(suffix)) { // 判断文件类型
    return new Response(415, "请上传 PNG,JPG,JPEG 格式图片!");
    }

    String fileName = UUID.randomUUID().toString() + "." + suffix; // 存储 文件名

    File destFile = new File(destFolder.getPath() + "/" + fileName); // 创建存储文件

    Map<String, Object> map = new HashMap<>();
    map.put("originalFilename", originalFilename);
    map.put("fileName", fileName);

    try {
    file.transferTo(destFile); // 存储文件

    return new Response<Map<String,Object>>().withData(map);
    } catch (Exception e) {
    e.printStackTrace();
    return new Response<>(500, "文件上传失败!");
    }
    }
    }
  • 文件 预览

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // FileUploadController.java

    import org.springframework.web.multipart.MultipartFile;
    import java.io.*;
    import java.util.*;

    @Controller
    @RequestMapping("/api/v1")
    public class FileUploadController {

    @Value("${file.path}")
    String filePath;

    @Value("${file.server}")
    String fileServer;

    /**
    * 文件预览
    */
    @RequestMapping(value = "/file/preview/{name}", method = RequestMethod.GET)
    @ResponseBody
    public void preview(@PathVariable(value = "name") String name, HttpServletResponse response) {
    try {
    File file = new File(filePath + "/" + name); // 创建文件路径

    String[] strArray = name.split("\\.");
    String suffix = strArray[strArray.length - 1]; // 获取文件扩展名

    // response.setHeader("Content-Type", suffix);
    response.setHeader("Content-Type", "text/html; charset=utf-8");
    response.setHeader("Content-Disposition", "inline; filename=" + name);

    FileInputStream fileInputStream = new FileInputStream(file);
    BufferedInputStream bis = new BufferedInputStream(fileInputStream); // 创建 缓存字符输入流

    OutputStream outputStream = response.getOutputStream(); // 创建 字节输出流

    byte[] buffer = new byte[1024];
    int i = bis.read(buffer);
    while (i != -1) {
    outputStream.write(buffer, 0, i); // 写入 数据
    i = bis.read(buffer); // 读取 数据
    }
    } catch (IOException ignored) {

    }
    }
    }
  • 文件 下载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    // FileUploadController.java

    import org.springframework.web.multipart.MultipartFile;
    import java.io.*;
    import java.util.*;

    @Controller
    @RequestMapping("/api/v1")
    public class FileUploadController {

    @Value("${file.path}")
    String filePath;

    @Value("${file.server}")
    String fileServer;

    /**
    * 文件下载
    */
    @RequestMapping(value = "/file/download/{name}", method = RequestMethod.GET)
    @ResponseBody
    public void download(@PathVariable(value = "name") String name, HttpServletResponse response) {
    try {
    File file = new File(filePath + "/" + name); // 创建文件路径

    String[] strArray = name.split("\\.");
    String suffix = strArray[strArray.length - 1]; // 获取文件扩展名

    response.setContentType("application/force-download");
    response.setHeader("Content-Disposition", "inline;filename=" + name);
    // response.setHeader("Content-Length", String.valueOf(fileByte.length));
    // response.setContentLength(fileByte.length);

    FileInputStream fileInputStream = new FileInputStream(file);
    BufferedInputStream bis = new BufferedInputStream(fileInputStream); // 创建 缓存字符输入流

    OutputStream outputStream = response.getOutputStream(); // 创建 字节输出流

    byte[] buffer = new byte[1024];
    int i = bis.read(buffer);
    while (i != -1) {
    outputStream.write(buffer, 0, i); // 写入 数据
    i = bis.read(buffer); // 读取 数据
    }
    } catch (IOException ignored) {

    }
    }
    }

6、控制台输入/输出流

6.1 System

  • System

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package java.lang;

    /**
    * The <code>System</code> class contains several useful class fields
    * and methods. It cannot be instantiated.
    *
    * @author unascribed
    * @since JDK1.0
    */

    public final class System {

    /**
    * The "standard" input stream. This stream is already
    * open and ready to supply input data. Typically this stream
    * corresponds to keyboard input or another input source specified by
    * the host environment or user.
    */
    public final static InputStream in = null;

    /**
    * The "standard" output stream. This stream is already
    * open and ready to accept output data. Typically this stream
    * corresponds to display output or another output destination
    * specified by the host environment or user.
    *
    */
    public final static PrintStream out = null;
    }
  • 输入/输出数据

    1
    2
    int i = System.in.read();                                  // 控制台输入
    System.out.println(i); // 控制台输出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import java.io.IOException;
    import java.io.InputStream;

    try (InputStream is = System.in) {
    while (true) {
    int i = is.read(); // 控制台输入

    System.out.println(i); // 控制台输出
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

6.2 Scanner

  • 使用 Scanner 可以逐行读取数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package java.util;

    /**
    * A simple text scanner which can parse primitive types and strings using
    * regular expressions.
    *
    * @since 1.5
    */

    public final class Scanner implements Iterator<String>, Closeable {

    }
  • 使用 Scanner 从控制台读取数据

    1
    2
    3
    4
    // 读取了整数后,接着读取字符串
    int i = scanner.nextInt(); // 读取整数
    String rn = scanner.nextLine(); // 取走回车换行
    String a = scanner.nextLine(); // 读取字符串
    1
    2
    3
    4
    5
    6
    7
    import java.util.Scanner;

    Scanner scanner = new Scanner(System.in);

    int a = scanner.nextInt(); // 读取整数
    float a = scanner.nextFloat(); // 读取浮点数
    String a = scanner.nextLine(); // 读取字符串

7、Java 序列化

  • 一个对象以流的形式进行传输,叫做序列化

    • 序列化:将对象写入到 IO 流中。
    • 反序列化:从 IO 流中恢复对象。
  • Java 序列化机制允许将实现序列化的 Java 对象转换为字节序列。

    • 这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。
    • 序列化机制使得对象可以脱离程序的运行而独立存在。
  • 所有可在网络上传输的对象都必须是可序列化的。

  • 所有需要保存到磁盘的对象都必须是可序列化的。

  • 一个对象要实现序列化,该对象所对应的类,必须是实现 Serializable 接口 或者 Externalizable 接口 之一。

实现 Serializable 接口 实现 Externalizable 接口
系统自动存储必要的信息 程序员决定存储哪些信息
Java 内建支持,易于实现,只需要实现该接口即可,无需任何代码支持 必须实现接口内的两个方法
性能略差 性能略好
  • 虽然 Externalizable 接口带来了一定的性能提升,但变成复杂度也提高了,所以一般通过实现 Serializable 接口进行序列化。

7.1 Serializable

  • Serializable 接口是一个标识性接口,没有任何方法,仅仅用于表示该类可以序列化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package java.io;

    /**
    * Serializability of a class is enabled by the class implementing the
    * java.io.Serializable interface. Classes that do not implement this
    * interface will not have any of their state serialized or
    * deserialized. All subtypes of a serializable class are themselves
    * serializable. The serialization interface has no methods or fields
    * and serves only to identify the semantics of being serializable. <p>
    *
    * @author unascribed
    * @see java.io.ObjectOutputStream, java.io.ObjectInputStream,
    * java.io.ObjectOutput, java.io.ObjectInput, java.io.Externalizable
    * @since JDK1.1
    */

    public interface Serializable {

    }
  • 通常建议,程序创建的每个 JavaBean 类都实现 Serializeable 接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Menu.java

    import java.util.List;
    import java.io.Serializable;

    public class Menu implements Serializable {

    private Integer id;
    private Integer parentId;
    private String menuName;
    private Integer menuOrder;
    private Integer status;
    private String parentName;

    private List<Resource> resourcesList; // Resource 类也需是可序列化的
    }
  • 如果一个可序列化的类的成员不是基本类型,也不是 String 类型,那这个引用类型也必须是可序列化的;否则,会导致此类不能序列化。

    1
    2
    3
    4
    5
    6
    7
    // Resource.java

    import java.io.Serializable;

    public class Resource implements Serializable { // Resource 类序列化

    }

7.2 Externalizable

  • Externalizable 接口。

    1
    2
    3
    4
    5
    public interface Externalizable extends java.io.Serializable {

    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
    }
  • 通过实现 Externalizable 接口,必须实现 writeExternalreadExternal 方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    public class ExPerson implements Externalizable {

    private String name;
    private int age;

    // 注意,必须加上 pulic 无参构造器
    public ExPerson() {
    }

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

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {

    // 将 name 反转后写入二进制流
    StringBuffer reverse = new StringBuffer(name).reverse();
    System.out.println(reverse.toString());
    out.writeObject(reverse);
    out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

    // 将读取的字符串反转后赋值给 name 实例变量
    this.name = ((StringBuffer) in.readObject()).reverse().toString();
    System.out.println(name);
    this.age = in.readInt();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {

    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ExPerson.txt"));
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ExPerson.txt"))) {
    oos.writeObject(new ExPerson("brady", 23));
    ExPerson ep = (ExPerson) ois.readObject();
    System.out.println(ep);
    }
    }
    }

    // 输出结果
    // ydarb
    // brady
    // ExPerson{name='brady', age=23}
文章目录
  1. 1. 前言
  2. 2. 1、IO 类型
  3. 3. 2、流
    1. 3.1. 2.1 Java 流的类型
    2. 3.2. 2.2 关闭流的方式
    3. 3.3. 2.3 中文编码问题
  4. 4. 3、字节流
    1. 4.1. 3.1 文件字节流
    2. 4.2. 3.2 数据流
    3. 4.3. 3.3 对象流
  5. 5. 4、字符流
    1. 5.1. 4.1 文件字符流
    2. 5.2. 4.2 缓存流
  6. 6. 5、文件对象
    1. 6.1. 5.1 当前工作目录
    2. 6.2. 5.2 文件分隔符
    3. 6.3. 5.3 文件创建
    4. 6.4. 5.4 文件夹创建
    5. 6.5. 5.5 常用方法
    6. 6.6. 5.6 文件上传/预览/下载
  7. 7. 6、控制台输入/输出流
    1. 7.1. 6.1 System
    2. 7.2. 6.2 Scanner
  8. 8. 7、Java 序列化
    1. 8.1. 7.1 Serializable
    2. 8.2. 7.2 Externalizable
隐藏目录