Java 输入输出

输出允许程序读取外部数据,输入允许程序记录运行状态、输出数据。

一、File

1. File

File 是文件/目录的抽象表示,通过文件/目录的路径字符串创建 File 实例。

File 不能访问文件内容,但可以新建、删除文件/目录。

2. 方法

(1) 创建 File 对象

方法 说明
new File(String pathname) 根据 pathname 字符串创建 File 对象
new File(String parent, String child) 根据 parent 和 child 字符串创建 File 对象
new File(File parent, String child); 根据父对象和 child 字符串创建 File 对象
1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
File file1 = new File("D:\\Test");
File file2 = new File("D:\\Test","hello");
File file3 = new File(file1,"hello");
System.out.println(file1);
System.out.println(file2);
System.out.println(file3);
}
}

(2) 创建

方法 说明
creatNewFile() 创建文件
mkdir() 创建目录
mkdirs() 创建目录,包括不存在的父目录

(3) 判断

方法 说明
exists() 判断文件/目录是否存在
isFile() 判断是否为文件
isDirectory() 判断是否为目录

(4) 获取

方法 说明
getName() 获取文件名/目录名
get[Absolute]Path() 获取[绝对]路径
list() 获取由目录中的文件名/目录名组成的数组
listFiles() 获取由目录中的文件/目录的 File 对象组成的数组

(5) 删除

方法 说明
delete() 删除文件/目录

需要注意的是:仅在目录被清空时,目录才可以被删除

二、IO 流

1. 什么是 IO 流?

IO 流,Input/Output 流,即输入/输出流。

在 Java 中把不同的输入/输出源抽象表述为“流“,通过”流“允许程序以相同的方式访问不同的输入/输出源。

2. IO 流的分类

(1) 输入流和输出流

根据数据的流向,可以将 IO 流分为:

  • 输入流
  • 输出流

其中,输入/输出是以内存的角度进行划分的,向内存中输入数据为输入流,从内存向外输出数据为输出流。

(2) 字节流和字符流

根据操作的数据单元不同,可以将 IO 流分为:

  • 字节流:操作的数据单元是字节
  • 字符流:操作的数据单元是字符

字节流可以处理一切文件

字符流只能处理文本

(3) 节点流和处理流

  • 节点流:向一个特定的 I/O 设备读/写数据
  • 处理流:对已存在的流进行封装,从而实现同一份代码读/写不同节点的数据

(4) 缓冲流

由于程序和内存之间的交互很快,内存和磁盘之间的交互很慢,访问外部数据时,最多的时间是在等外部设备响应,而不是数据处理。为提升程序效率,引入了缓冲流。

缓冲流的做法是在内存中设置一个缓冲区,待缓冲区存储够足够的数据后再进行读取和写入,通过提高每次交互的数据量,减少了交互次数。

3. flush()

在处理缓冲流时,应该确保执行了 flush(),将缓冲区中的数据输出到物流节点上,防止数据丢失。

4. close()

在流使用结束后,应该关闭流。关闭流可以保证与该流关联的所有系统资源被回收,并且还可以执行缓冲流的 flush()

通过流的 close() 方法可以对流进行关闭,为确保始终执行 close() 方法,可以:

(1) finally

在 finally 中调用 close() 方法,从而确保 close() 方法一定被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
try
{
FileInputStream stream = new FileInputStream(路径);
···
}
catch
{
···
}
finally
{
流.close()
}

(2) 自动收尾

Java7 之后,支持“自动收尾”写法,并且所有的 IO 类均实现了 AutoCloseable 接口。

可以在 try 之后紧跟 ( ) ,在其中放置 IO 类,IO 类将会在 try-catch 语句执行后被自动关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try
(
FileInputStream stream = new FileInputStream(路径);
)
{
···
}
catch
{
···
}
finally
{
流.close()
}

三、输入流

1. 抽象父类

InputStream 和 Reader 是所有输入流的抽象父类。

(1) InputStream

字节输入流

方法 说明
read() 从流中读取单个字节
read(byte[] b) 从流中读取最多 b.length 个字节,放入字节数组 b
read(byte[] b, int off, int len) 从流中读取最多 len 个字节,从字节数组的 off 索引处开始,依次放入
方法 说明
close() 关闭流并释放与此流有关的所有系统资源

(2) Reader

字符输入流

方法 说明
read() 从流中读取单个字节
read(char[] c) 从流中读取最多 c.length 个字符,放入字符数组 c
read(char[] c, int off, int len) 从流中读取最多 len 个字符,从字符数组的 off 索引处开始,依次放入
方法 说明
close() 关闭流并释放与此流有关的所有系统资源

2. 节点流实现类

常用的输入流实现类有 FileInputStream 和 FileReader 。

1
2
3
4
5
6
7
8
9
// 根据字符串创建File对象,根据File对象创建流
FileInputStream(String name)
// 根据File对象创建流
FileInputStream(File file)

// 根据字符串创建File对象,根据File对象创建流
FileReader(String name)
// 根据File对象创建流
FileReader(File file)

四、输出流

1. 抽象父类

OutputStream 和 Writer 是所有输出流的抽象父类。

(1) OutputStream

字节输出流

方法 说明
write(int/char c) 将字节/字符输出到流中,其中字节/字符可以以数字/字符表示
write(byte[] b) 将字节数组 b 中的数据输出到流中
write(byte[] b, int off, int len) 将字节数组 b 中从 off 索引处开始,长度为 len 的数据输出到流中

方法 说明
close() 关闭流并释放与此流有关的所有系统资源

(2) Writer

字符输出流

方法 说明
write(int/char c) 将字节/字符输出到流中,其中字节/字符可以以数字/字符表示
write(char[] c) 将字符数组 c 中的数据输出到流中
write(char[] c, int off, int len) 将字符数组 c 中从 off 索引处开始,长度为 len 的数据输出到流中
write(String str) 将字符串 str 中的数据输出到流中
write(String str, int off, int len) 将字符串 str 中从 off 位置开始,长度为 len 的数据输出到流中

方法 说明
flush() 刷新流并强制输出所有缓冲的输出字符
close() 刷新流,关闭流并释放与此流有关的所有系统资源

2. 节点流实现类

常用的输入流实现类有 FileOutputStream 和 FileWriter 。

1
2
3
4
5
6
7
8
9
// 根据字符串创建File对象,根据File对象创建流
FileOutputStream(String name)
// 根据File对象创建流
FileOutputStream(File file)

// 根据字符串创建File对象,根据File对象创建流
FileWriter(String name)
// 根据File对象创建流
FileWriter(File file)

五、对象序列化

1. 什么是对象序列化?

对象序列化的目标是将对象转换为可保存、可传输的二进制流,允许将对象传输至其它节点,或持久地保存在磁盘上。

对象序列化使得对象可以脱离程序的运行而独立存在。

2. 对象序列化的前提

如果需要让某个对象支持序列化,则必须让它的类实现以下接口之一:

  • Serializable

    仅仅是一个标记接口,实现该接口无需实现任何方法

  • Externalizable

3. 序列化

  • 创建节点输出流

  • 通过节点输出流创建 ObjectOutputStream 流

    1
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(节点流)
  • 调用 ObjectOutputStream 流中的 writeObject() 输出对象

    1
    objectOutputStream.writeObject(obj)

4. 反序列化

  • 创建节点输入流

  • 通过节点输入流创建 ObjectInputStream 流

    1
    ObjectInputStream objectInputStream = new ObjectInputStream(节点流)
  • 调用 ObjectInputStream 流中的 readObject() 输出对象

    1
    Object obj = objectInputStream.readObject()

5. SerializableID

在实际开发中,代码不断迭代,类可能新增、删除属性,如果某个对象在早些时候序列化,在类已经修改后进行反序列化,可能会因不兼容出现异常。

为避免这一问题,可以通过 SerializableID 对类的版本做唯一标识。

具体来说,有两种做法:

  • 显式声明:在类中声明 SerializableID,并在类更新时手动修改

    1
    2
    3
    public class MyClass implements Serializable {
    private static final long serialVersionUID = 123456789L;
    }
  • 隐式生成:Java 会根据类的包、字段、方法等信息生成一个隐式的 SerializableID

参考