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

参考