IO简介

IO(inout),即输入和输出,指程序和外部设备之间的数据传递,常见的包括文件、管道、网络连接等。

在Java中,通过流处理IO。

流指的是一连串的数据(字符或字节)。
当程序需要读取数据时,会开启一个通向数据源的流;当程序需要写入数据时,会开启一个通向目的地的流。
流具有以下特征:

  • 先进先出:最先写入输出流的数据最先被输入流读到
  • 顺序存取:一个接着一个地往流中写入或读取一串字节,不能随机访问。(RandomAccessFile除外)
  • 只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能。

IO流的分类

按数据流的方向:输入流、输出流

输入流和输出流是相对程序而言的。
读取是流向程序,是输入流;写入是流出程序,是输出流。

1输入输出流

按处理数据单位:字节流、字符流

字节流和字符流的用法几乎完全一样,区别在于操作的数据单元不同。
字节流操作的是8bit(1byte),字符流操作的是16bit(2byte)的字符。

为什么要有字符流:
在Java中,字符(包括英文、中文以及其他语言)是通过Character实现,采用Unicode编码。在Unicode中,一个字符占用两个字节,即16bit。
而在UTF-8编码中,一个中文字符占用3byte。
在这种背景下,如果按字节读取字符将会出现乱码的情况。为了更方便处理中文这些字符,Java推出字符流。

字节流一般用来处理图像、视频、Word等类型的非纯文本文件;字符流用来处理纯文本类型的文件。
字节流本身没有缓冲区,缓冲字节流相对于字节流来说效率提升非常高;字符流本身带有缓冲区,但是缓冲字符流相对于字符流来说,效率提升没有字节流那么明显。

按功能:节点流、处理流

节点流:直接操作数据的读写的流,如FileInputStreamFileOutputStream
处理流:对一个已存在的流(节点流)的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流)。

处理流和节点流应用了Java的装饰者设计模式。

2节点流和处理流

在诸多处理流中,非常经典的是缓冲流。
程序与磁盘的交互相对于内存运算是很慢的,容易成为程序的性能瓶颈。因此减少程序与磁盘的交互是提升效率的一种有效手段。
在缓冲流中,在内存中设置一个缓存区,缓冲区先存储足够的带操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。

3缓冲流

理论介绍

File类

在和文件交互的流中,需要对文件进行加载和操作。
File类可以用来操作文件,但是不能操作文件中的数据。

public class File extends Object implements Serializable, Comparable<File>

File类实现了Serializable,说明其支持序列化;实现了Comparable,说明其支持排序。

File类的构造方法

构造方法说明
File(File parent, String child)根据parent路径File对象和child路径名字符串创建一个新的File实例
File(String pathname)根据给定路径名字符串转化为File实例
File(String parent, String chile)根据parent路径名祖父穿和child路径名字符串创建一个新的File实例
File(URI uri)通过给定的URI对象创建File实例

File类常用方法

| 方法 | 说明 |
| createNewFile() | 当且仅当不存在具有此路径名指定名称的文件时,不可分地创建一个新的空文件。 |
| delete | 删除文件或目录。 |
| exists | 测试文件或目录是否存在。 |
| getAbsoluteFile() | 返回的绝对路径名形式。 |
| getAbsolutePath() | 返回路径的绝对路径名字符串。 |
| length() | 返回文件的长度。 |
| mkdir() | 创建目录。 |

InputStream类和OutputStream类(字节流)

InputStreamOutputStream是两个抽象类,是字节流的基类。

public abstract class InputStream implements Closeable
public abstract class OutputStream implements Closeable, Flushable

InputStream

4inputstream家族

  • InputStream:是所有字节输入流的抽象基类。
  • FileInputStream:文件输入流,用于对文件进行读取操作。
  • PipeInputStream:管道字节输入流,能实现多线程间的管道通信。
  • ByteArrayInputStream:字节数组输入流,从字节数组(byte[])中进行以字节为单位的读取,即将资源文件以字节的形式存入到该类的字节数组中。
  • FilterInputStream:装饰者类,具体的装饰者继承该类,这些类都是处理类。
  • DataInputStream:数据输入流,用来装饰其他输入流,允许程序以与机器无关的方式从底层输入流中读取基本Java数据类型。
  • BufferedInputStream:缓冲流,利用内部缓存区提高输入流的效率。
  • ObjectInputStream:对象输入流,用来提供对基本数据或对象的持久存储。通常应用在反序列化中。

其主要方法有:

  • read():从输入流中读取一个字节。
  • read(byte[] b):从输入流中将最多b.length个字节读入数组中。
  • read(byte[] b, int off, int len):从输入流中将最多len字节的数据读入数组中。
  • close():关闭输入流,释放资源。

OutputStream

5outputstream家族

InputStream类似,其中PrintStream是打印输出流。

其主要方法有:

  • write(byte[] b):将b.length个字节写入输出流中。
  • write(byte[] b, int off, int len):将指定的byte数组中从偏移量off开始的len个字节写入输出流中。
  • write(int b):将指定字节写入文件输出流。
  • close():关闭输出流,释放资源。

Reader和Writer(字符流)

与字节流类似,字符流也有两个抽象基类,分别是Reader和Writer。其他的字符流实现类都是继承了这两个类。

6reader家族

  • InputStreamReader:从字节流到字符流的桥梁,读取字节并使用指定的字符集将其解码为字符。
  • BufferedReader:从字符输入流中读取文本,设置一个缓冲区来提高效率,是对InputStreamReader的封装。
  • FileReader:用于读取字符文件,new FileReader(File file)等同于new InputStreamReader(new FileInputStream(file, true), "UTF-8"),但FileReader不能指定字符编码和默认缓冲区大小。
  • PipeReader:管道字符输入流,实现多线程间的管道通信。
  • CharArrayReader:从char数组中读取数据的介质流。
  • StringReader:从String中读取数据的介质流。

Writer与Reader结构类似,方向相反。唯一有区别的是,Writer的子类PrintWriter,是文本输出流打印。

Reader的主要方法:

  • read():读取单个字符
  • read(char[] cbuf):将字符读入数组
  • read(char[] cbuf, int off, int len):将字符读入数组的某一部分
  • read(CharBuffer target):试图将字符读入指定的字符缓冲区
  • flush():刷新该流的缓冲
  • close():关闭流,释放资源。(需要先刷新)

Writer的主要方法:

  • write(char[] cbuf):写入字符数组
  • write(char[] cbuf, int off, int len):写入字符数组的某一部分
  • write(int c):写入单个字符
  • write(String str):写入字符串
  • write(String str, int off, int len):写入字符串的某一部分
  • flush():刷新该流的缓冲。
  • close():关闭流,释放资源。(需要先刷新)

另外,字符缓冲流还有两个独特的方法:

  • BufferedWriternewLine():写入一个行分隔符(该方法会自动适配所在操作系统的行分隔符)。
  • BufferedReaderreadLine():读取一个文本行。

ObjectInputStream和ObjectOutputStream(序列化流)

序列化:如果需要将对象保存到磁盘中,或者在网络中传输,就必须对对象进行序列化。在Java中,实现Serializable接口可以实现序列化。
序列化是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据、对象中存储的属性等信息。
字节序列写到文件之后,相当于文件中持久保存了一个对象的信息;反之,该字节序列还可以从文件中读取回来,重构对象,尽心反序列化。

使用ObjectInputStream可以实现对对象的序列化,使用ObjectInputStream可以实现对象的反序列化。

代码操作

File类

常量

  • File.pathSeparatorChar:用于分割多个路径的系统分隔符,在Unix系统上为:,在Windows系统上为;
  • File.separatorChar:表示该操作系统的路径分隔符,在Unix系统上为/,在Windows系统上为\
  • File.pathSeparator:用于分割多个路径的系统分隔符,由pathSeparatorChar扩展而来的字符串常量
  • File.separator:表示该操作系统的路径分隔符,由separatorChar扩展而来的字符串常量

实例

需求:

  1. 检测某文件是否存在,若存在则删除,不存在则创建。输出该文件的绝对路径和长度。
public static void main(String[] args) {

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

    if (file.exists()) {
        file.delete();
    } else {
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    System.out.println(file.getAbsolutePath());
    System.out.println(file.length());

}
  1. 检测某目录是否存在,若存在则删除,不存在则创建。输出该文件的绝对路径和长度。
public static void main(String[] args) throws IOException {

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

    if (file.exists()) {
        file.delete();
    } else {
        file.mkdir();
    }

    System.out.println(file.getAbsolutePath());
    System.out.println(file.length());

}

字节流的操作

字节输出流

步骤:

  1. 创建字节输出流对象
  2. 调用字节输出流对象的写数据方法
  3. 释放资源

需求1:创建文件并写入一个字符串"Hello world!\n你好,世界!",其中\n是换行。

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

    FileOutputStream fos = new FileOutputStream(new File("test.txt"));
    byte[] bytes = "Hello World!\n你好,世界!".getBytes();
    fos.write(bytes);
    fos.close();

}

FileOutputStream还有另外一个构造方法,FileOutputStream(File file, boolean append)

需求2:在上述操作之后,在其文件后追加字符串"你好,Java!"

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

    FileOutputStream fos = new FileOutputStream(new File("test.txt"), true);
    byte[] bytes = "你好,Java!".getBytes();
    fos.write(bytes);
    fos.close();

}

字节输入流

步骤:

  1. 创建字节输入流对象
  2. 调用字节输入流对象的读数据方法
  3. 释放资源

需求3:读取上述文件

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

    FileInputStream fis = new FileInputStream(new File("test.txt"));

    int read;

    while ((read = fis.read()) != -1) {
        System.out.print((char) read);
    }

    fis.close();

}

读取后发现输出的结果中中文是乱码状态,这需要使用字符流来解决:

Hello World!
你好,世界!你好,Java!

字节流的综合应用(实现拷贝文件)

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

    FileInputStream fis = new FileInputStream(new File("test.txt"));
    FileOutputStream fos = new FileOutputStream(new File("test-cp.txt"));

    byte[] bytes = new byte[1024];
    int len;
    while ((len = fis.read(bytes)) != -1) {
        fos.write(bytes, 0, len);
    }

    fos.close();
    fis.close();

}

使用字符缓冲流实现拷贝文件

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

    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.txt"));
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test-cp.txt"));

    byte[] bytes = new byte[1024];
    int len;
    while ((len = bis.read(bytes)) != -1) {
        bos.write(bytes, 0, len);
    }

    bos.close();
    bis.close();

}

字符流

字符流 = 字节流 + 编码表

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

    InputStreamReader isw = new InputStreamReader(new FileInputStream("test.txt"));

    int ch;
    while ((ch = isw.read()) != -1) {
        System.out.print((char) ch);
    }

    isw.close();

}
Hello World!
你好,世界!你好,Java!

对象序列化流

public class Test01 {

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

        Student stu = new Student("xiaoming", 19);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("stu.txt"));

        oos.writeObject(stu);

        oos.close();


        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("stu.txt"));

        Object readObject = null;
        try {
            readObject = ois.readObject();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println(readObject.getClass());
        System.out.println(readObject instanceof Student);
        System.out.println(readObject);

    }

}


class Student implements Serializable {

    String name;
    Integer age;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "[name=" + this.name + ", age=" + this.age + "]";
    }

}

参考文章:

  1. 【Java基础-3】吃透Java IO:字节流、字符流、缓冲流
  2. IO流知识点整理总结
  3. JAVA基础知识之File类