IO流简介

(1)IO流是一个抽象的概念,可以理解为一段单向的数据序列,是Java实现输入输出的基础

概念简介
流(Stream)数据在程序(内存)和数据源(文件)之间经历的路径
输入流(Input)数据从数据源(文件)到程序(内存)的路径
输出流(Output)数据从程序(内存)到数据源(文件)的路径

(2)Java把所有的传统的流类型都放到在java.io包下,用于实现输入和输出功能,输入也叫做读取数据,输出也叫做作写出数据

(3)Java的IO模型使用装饰者模式(Decorator),按功能划分流(Stream),您可以动态装配这些流(Stream),以便获得您需要的功能

IO流分类

抽象基类

Java的IO流共涉及40多个类,实际上非常规则,都是从4个抽象基类派生,派生出来的子类名称都是以其父类名作为子类名后缀,由于抽象基类都是抽象类,本身不能创建实例,但是继承它们的子类有对应的功能

分类抽象基类
字节输入流InputStream
字节输出流OutputStream
字符输入流Reader
字符输出流Writer

输入流和输出流

按照流的流向分,可以分为输入流和输出流

IO流简介
Input输入流负责读,外部 -> 内存
Output输出流负责写,内存 -> 外部

字节流和字符流

按照操作单元划分,可以划分为字节流和字符流,使用字节流读取文件为中文的时候,GBK占用2个字节(byte),UTF-8占用3个字节

IO流简介
字节流操作的单元是数据单元是8位的字节,例如图片、音乐、视频等文件,可以对二进制文件进行处理
字符流操作的是数据单元为16位的字符,例如.txt、.java、.c、.cpp等文本文件,.doc、excel、ppt这些不是文本文件

节点流和处理流

按照流的角色划分为节点流和处理流

IO流简介
节点流直接从数据源或目的地读写数据
处理流处理流也叫包装流,对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连,而是“连接”在已存在的流(节点流或处理流)之上,处理流也被称为高级流,处理流以增加缓冲的方式来提高输入输出的效率

节点流

分类字节输入流字节输出流字符输入流字符输出流
抽象基类InputStreamOutputStreamReaderWriter
访问文件FileInputStreamFileOutputStreamFileReaderFileWriter
访问数组ByteArrayInputStreamByteArrayOutputStreamcharArrayReadercharArrayWriter
访问管道PipedInputStreamPipedOutputStreamPipedReaderPipedWriter
访问字符串StringReaderStringWriter

处理流

分类字节输入流字节输出流字符输入流字符输出流
缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
对象流ObjectInputStreamObjectOutputStream
转换流InputStreamReaderOutputStreamWriter
过滤流FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
打印流PrintStreamPrintWriter
数据流DataInputStreamDataOutputStream
回退流PushbackInputStreamPushbackReader

File类

路径分隔符

路径中的每级目录之间需要用一个路径分隔符隔开,但路径分隔符和系统有关,比如windows和DOS系统默认使用\\来表示、UNIX和URL使用/来表示,为了解决Java程序跨平台运行,File类提供了一个常量,可以根据操作系统,动态的提供分隔符(盘符+路径+文件名)

1
public static final String separator;

File类简介

(1)java.io.File类的一个对象,代表一个文件或一个文件目录(文件夹)

(2)File 能新建、删除、重命名文件和目录,但File不能访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。

(3)想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。

File类构造器

构造器简介
File(String pathname)以pathname为路径创建File对象,可以是绝对路径或者相对路径
File(String parent,String child)以parent为父路径,child为子路径创建File对象
File(File parent,String child)根据一个父File对象和子文件路径创建File对象
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
public class Demo {
@Test
public void test() {
// 相对路径
File file1 = new File("hello.txt");
System.out.println(file1);// hello.txt

// 绝对路径
File file2 = new File("E:\\test\\hello.txt");
System.out.println(file2);// E:\test\hello.txt

// 路径+目录
File file3 = new File("E:\\test", "java");
System.out.println(file3);// E:\test\java

// File类型+文件名
File file4 = new File(file3, "hello.txt");
System.out.println(file4);// E:\test\java\hello.txt

/**
*路径中的每级目录之间需要用一个路径分隔符隔开,但路径分隔符和系统有关,
*比如windows和DOS系统默认使用`\\`来表示、UNIX和URL使用`/`来表示,
*为了解决Java程序跨平台运行,File类提供了一个常量,可以根据操作系统,动态的提供分隔符(盘符+路径+文件名)
*public static final String separator;
*/
File file5 = new File("E:" + File.separator + "test" + File.separator + "hello.txt");
System.out.println(file5);// E:\test\hello.txt
}
}

File类常用方法

获取操作

(1)方法示例

方法简介
getAbsolutePath()获取绝对路径
getPath()获取路径
getName()获取名称
getParent()获取上层文件目录路径,若无则返回null
length()获取文件长度(字节数)不能获取目录的长度
lastModified()获取最后一次的修改时间,毫秒值
list()获取指定目录下的所有文件或者文件目录的名称数组
listFiles()获取指定目录下的所有文件或者文件目录的File数组

(2)代码示例

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
public class Demo {
@Test
public void test() {
File file1 = new File("hello.txt");
// getAbsolutePath():获取绝对路径
System.out.println(file1.getAbsolutePath());// D:\Java\IDEA-2018.2.7\Java\JavaSE\JavaSE_7_IO流\hello.txt
// getPath():获取路径
System.out.println(file1.getPath());// hello.txt
// getName():获取名称
System.out.println(file1.getName());// hello.txt
// getParent():获取上层文件目录路径,若无则返回null
System.out.println(file1.getParent());// null
// length():获取文件长度(字节数),不能获取目录的长度。
System.out.println(file1.length());//5
// lastModified():获取最后一次的修改时间,毫秒值
System.out.println(file1.lastModified());//1667315658044
System.out.println(new Date(file1.lastModified()));//Tue Nov 01 23:14:18 CST 2022

File file2 = new File("E:\\java");
// list():获取指定目录下的所有文件或者文件目录的名称数组
String[] list = file2.list();
// 遍历
for (String f : list) {
System.out.println(f);
}
// listFiles() :获取指定目录下的所有文件或者文件目录的File数组
File[] files = file2.listFiles();
// 遍历
for (File f : files) {
System.out.println(f);
}
}
}

重命名操作

(1)方法示例

方法简介
renameTo(File dest)把文件重命名为指定的文件路径

(2)代码示例

1
2
3
4
5
6
7
8
9
10
public class Demo {
@Test
public void test(){
// public boolean renameTo(File dest):把文件重命名为指定的文件路径
File file1 = new File("E:\\test\\hello.txt");
File file2 = new File("hello.txt");
boolean b = file1.renameTo(file2);
System.out.println(b);
}
}

判断操作

(1)方法示例

方法简介
isDirectory()判断是否是文件目录
isFile()判断是否是文件
exists()判断是否存在
canRead()判断是否可读
canWrite()判断是否可写
isHidden()判断是否隐藏

(2)代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Demo {
@Test
public void test() {
File file = new File("hello.txt");
// isDirectory():判断是否是文件目录
System.out.println(file.isDirectory());//false
// isFile():判断是否是文件
System.out.println(file.isFile());//true
// exists():判断是否存在
System.out.println(file.exists());//true
// canRead():判断是否可读
System.out.println(file.canRead());//true
// canWrite():判断是否可写
System.out.println(file.canWrite());//true
// isHidden():判断是否隐藏
System.out.println(file.isHidden());//false
}
}

创建操作

(1)方法示例

注意事项:如果创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下

方法简介
createNewFile()创建文件,若文件存在,则不创建,返回false
mkdir()创建文件目录, 如果此文件目录存在,就不创建了 如果此文件目录的上层目录不存在,也不创建
mkdirs()创建文件目录,如果上层文件目录不存在,一并创建

(2)代码示例

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
public class 创建操作 {
@Test
public void test5() throws IOException {
File file1 = new File("test1.txt");
// createNewFile():创建文件。若文件存在,则不创建,返回false
boolean exists = file1.createNewFile();
if (exists) {
System.out.println("创建成功");
} else {
System.out.println("创建失败");
}

File file2 = new File("E:\\test1");
// mkdir():创建文件目录。如果此文件目录存在,就不创建了,如果此文件目录的上层目录不存在,也不创建(不能创建多级目录)
// 如果file2不存在,就创建为目录,存在就打印失败
boolean mkdir = file2.mkdir();
if (mkdir) {
System.out.println("创建成功");
} else {
System.out.println("创建失败");
}

File file3 = new File("E:\\test1\\test2\\test3");
// mkdirs():创建文件目录。如果上层文件目录不存在,一并创建(可以创建多级目录)
boolean mkdirs = file3.mkdirs();
if (mkdirs) {
System.out.println("创建成功");
} else {
System.out.println("创建失败");
}
}
}

删除操作

(1)方法示例

注意事项: Java中的删除不走回收站,要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录

方法简介
delete()删除文件或者文件夹

(2)代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {
@Test
public void test() throws IOException {
File file = new File("test.txt");
file.mkdir();
boolean delete = file.delete();
if (delete){
System.out.println("删除成功");
}else{
System.out.println("没有这个文件");
}
}
}

字符流(Character Stream)

字符流是Java IO库中用于处理字符数据的流。它以字符为单位进行读取和写入操作,适用于处理文本文件、配置文件等字符数据。

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
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
public class FileReaderTest {

/**
* 读取文件流程:
* (1)实例化File类的对象,指明要操作的文件
* (2)提供具体流读取文件,FileReader
* (3)数据的读入:创建一个临时存放数据的数组、调用流对象的读取方法,将流中的数据读入到数组中,并遍历数组
* (4)流的关闭操作
*/
@Test
public void test1() {
FileReader fileReader = null;
try {
//(1)实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");

//(2)提供具体流读取文件,FileReader
fileReader = new FileReader(file);

//(3)数据的读入
// read():返回读入的一个字符,如果达到文件末尾,返回-1
int data = fileReader.read();//记录每次读入到数组的个数
while (data != -1) {
System.out.print((char) data);
data = fileReader.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@Test
public void test2() {
FileReader fileReader = null;
try {
//(1)实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");

//(2)提供具体流读取文件,FileReader
fileReader = new FileReader(file);

//(3)数据的读入
int data;//记录每次读入到数组的个数
// read():返回读入的一个字符,如果达到文件末尾,返回-1
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* 读取文件优化:对read()操作升级,使用read的重载方法
*/
@Test
public void test3() {
FileReader fileReader = null;
try {
//(1)实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");
//(2)提供具体流
fileReader = new FileReader(file);
//(3)数据的读入
char[] c = new char[5];//创建一个临时存放数据的数组
int num;//记录每次读入到数组的个数
while ((num = fileReader.read(c)) != -1) {
for (int i = 0; i < num; i++) {
System.out.print(c[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@Test
public void test4() {
FileReader fileReader = null;
try {
//(1)实例化File类的对象,指明要操作的文件
File file = new File("hello.txt");
//(2)提供具体流
fileReader = new FileReader(file);
//(3)数据的读入
char[] c = new char[5];//创建一个临时存放数据的数组
int num;//记录每次读入到数组的个数
while ((num = fileReader.read(c)) != -1) {
String str = new String(c, 0, num);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

}

FileWriter写出文件

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
public class FileWriterTest {
/**
* 写出文件流程:
* (1)实例化File类的对象,指明写出的文件及地址
* (2)提供具体流写出文件,FileWriter
* (3)从内存中写出数据到硬盘的文件里
* (4)流的关闭操作
* 注意事项:
* (1)File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件
* (2)File对应的硬盘中的文件如果存在,
* 如果流使用的构造器是FileWrite(file,false)、FileWrite(file),则会对原有文件覆盖
* 如果流使用的构造器是FileWrite(file,true),则不会对原有文件覆盖,而是进行追加数据
*/
@Test
public void test() {
FileWriter fileWriter = null;
try {
//(1)实例化File类的对象,指明写出的文件及地址
File file = new File("E:\\test.txt");
//(2)提供具体流写出文件,FileWriter
fileWriter = new FileWriter(file);
//(3)从内存中写出数据到硬盘的文件里
fileWriter.write("FileWriter流测试");
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

FileReader、FileWriter复制文件

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
public class FileReader_FileWriter_Copy {
/**
* 读取并写出(文本文件复制)
* (1)实例化File类的对象,指明读入和写出的文件及地址
* (2)提供具体流读写文件,输入流和输出流fileReader、FileWriter
* (3)读入数据并写出数据到硬盘的文件里(复制的过程)
* (4)流的关闭操作
*/
@Test
public void test() {
FileReader fileReader = null;
FileWriter fileWriter = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file1 = new File("hello.txt");
File file2 = new File("E:\\test.txt");

//(2)提供具体流读写文件,输入流和输出流fileReader、FileWriter
fileReader = new FileReader(file1);// 读取
fileWriter = new FileWriter(file2);// 写出

//(3)读入数据并写出数据到硬盘的文件里(复制的过程)
char[] c = new char[5];//创建一个临时存放数据的数组
int num;// 记录每次读入到数组的个数
while ((num = fileReader.read(c)) != -1) {
// 每次写入num个字符
fileWriter.write(c, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

字节流(Byte Stream)

字节流是Java IO库中处理二进制数据的流。它以字节为单位进行读取和写入操作,适用于处理图像、音频、视频等二进制数据。

FileInputStream读取文件

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
public class FileInputStreamTest {
/**
* 文件读取
* (1)实例化File类的对象,指明要操作的文件
* (2)造字节输入流,FileInputStream
* (3)读数据
* (4)关闭流
*/
@Test
public void test() {
FileInputStream fileInputStream = null;
try {
//(1)实例化File类的对象,指明要操作的文件
File file = new File("1.png");
//(2)造字节流,FileInputStream
fileInputStream = new FileInputStream(file);
//(3)读数据
byte[] b = new byte[3];//创建一个临时存放数据的数组
int num;//记录每次读入到数组的个数
while ((num = fileInputStream.read(b)) != -1) {
String str = new String(b, 0, num);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)关闭流
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

FileOutputStream写出文件

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
public class FileOutputStreamTest {
/**
* 写出文件流程:
* (1)实例化File类的对象,指明写出的文件及地址
* (2)造字节输出流,FileOutputStream
* (3)从内存中写出数据到硬盘的文件里
* (4)关闭流
*/
@Test
public void test() {
FileOutputStream fileOutputStream = null;
try {
//(1)实例化File类的对象,指明写出的文件及地址
File file = new File("E:\\test.txt");
//(2)造流
fileOutputStream = new FileOutputStream(file);
//(3)从内存中写出数据到硬盘的文件里
fileOutputStream.write(12345);
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)关闭流
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

FileInputStream、FileOutputStream复制文件

(1)FileInputStream、FileOutputStream复制非文本文件复制

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
public class FileInputStream_FileOutputStream_Copy {
/**
* 非文本文件复制:.mp3、mp4、.avi、.rmvb、.jpg、.doc、.ppt
* (1)实例化File类的对象,指明读入和写出的文件及地址
* (2)提供具体流读写文件,输入流和输出流FileInputStream、FileOutputStream
* (3)读入数据并写出数据到硬盘的文件里(复制的过程)
* (4)流的关闭操作
*/
@Test
public void test() {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file1 = new File("1.png");
File file2 = new File("E:\\1.png");
//(2)提供具体流读写文件,输入流和输出流FileInputStream、FileOutputStream
fileInputStream = new FileInputStream(file1);
fileOutputStream = new FileOutputStream(file2);
//(3)读入数据并写出数据到硬盘的文件里(复制的过程)
byte[] b = new byte[3];//创建一个临时存放数据的数组
int num;//记录每次读入到数组的个数
while ((num = fileInputStream.read(b)) != -1) {
//每次写入num个字符
fileOutputStream.write(b, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

(2)复制代码逻辑封装

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
57
58
59
60
61
62
63
64
65
public class copyUtils {

public static void main(String[] args) {
// 获取开始的时间
long startTime = System.currentTimeMillis();

// 调用封装的复制文件逻辑实现复制
String startPath = "E:\\1.png";
String endPath = "E:\\1.png";
copyFile(startPath, endPath);

// 获取结束的时间
long endTime = System.currentTimeMillis();

System.out.println("复制花费的时间为:" + (endTime - startTime));//54
}

/**
* 指定路径下的文件复制:将代码封装,使用时只需要填入开始与结束路径
* (1)实例化File类的对象,指明读入和写出的文件及地址
* (2)提供具体流读写文件,输入流和输出流FileInputStream、FileOutputStream
* (3)读入数据并写出数据到硬盘的文件里(复制的过程)
* (4)流的关闭操作
*
* @param startPath 开始路径
* @param endPath 结束路径
*/
public static void copyFile(String startPath, String endPath) {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file1 = new File(startPath);
File file2 = new File(endPath);
//(2)提供具体流读写文件,输入流和输出流FileInputStream、FileOutputStream
fileInputStream = new FileInputStream(file1);
fileOutputStream = new FileOutputStream(file2);
//(3)读入数据并写出数据到硬盘的文件里(复制的过程)
byte[] b = new byte[3];
int num;//记录每次读入到数组的个数
while ((num = fileInputStream.read(b)) != -1) {
//每次写入num个字符
fileOutputStream.write(b, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

转换流(Transcoder Streams)

转换流是Java IO库中的字符流类,在字节流和字符流之间提供了一个桥梁,用于将字节流转换为字符流或将字符流转换为字节流

字节流转字符流(InputStreamReader)

将字节流转换为字符流,读取字节并将其解码为字符。可以指定不同的字符编码方式,例如UTF-8、GBK等,以正确地将字节流转换为对应的字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class InputStreamReaderExample {
public static void main(String[] args) {
try {
// 创建字节输入流
InputStream inputStream = new FileInputStream("input.txt");

// 创建InputStreamReader,将字节流转换为字符流(指定字符编码为UTF-8)
Reader reader = new InputStreamReader(inputStream, "UTF-8");

int data = reader.read(); // 读取字符流

while (data != -1) { // 当读取到文件末尾时,read()方法返回-1
char character = (char) data;
System.out.print(character); // 输出字符
data = reader.read(); // 继续读取下一个字符
}

reader.close(); // 关闭字符流
} catch (IOException e) {
e.printStackTrace();
}
}
}

字符流转字节流(OutputStreamWriter)

将字符流转换为字节流,将字符编码为字节,并将其写入输出流。同样可以指定字符编码方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class OutputStreamWriterExample {
public static void main(String[] args) {
try {
// 创建字节输出流
OutputStream outputStream = new FileOutputStream("output.txt");

// 创建OutputStreamWriter,将字符流转换为字节流(指定字符编码为UTF-8)
Writer writer = new OutputStreamWriter(outputStream, "UTF-8");

String text = "Hello, World!";
writer.write(text); // 将字符串写入字符流

writer.close(); // 关闭字符流
} catch (IOException e) {
e.printStackTrace();
}
}
}

文件编解码

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
public class Demo {
/**
* 将已有的utf-8格式的文件装换为gbk格式
* 解码:字节、字节数组 —> 字符串、字符数组
* 编码:字符串、字符数组 —> 字节、字节数组
*/
@Test
public void test() throws IOException {
InputStreamReader inputStreamReader = null;
java.io.OutputStreamWriter outputStreamWriter = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file1 = new File("hello.txt");
File file2 = new File("test.txt");

//(2)提供具体流读写字节文件,输入流和输出流FileInputStream、FileOutputStream
FileInputStream fileInputStream = new FileInputStream(file1);
FileOutputStream fileOutputStream = new FileOutputStream(file2);

inputStreamReader = new InputStreamReader(fileInputStream,"utf-8");
outputStreamWriter = new OutputStreamWriter(fileOutputStream, "gbk");

//(3)读入数据并写出数据到硬盘的文件里(复制的过程)
char[] c = new char[5];
int num;//记录每次读入到数组的个数
while ((num = inputStreamReader.read(c)) != -1) {
//每次写入num个字符
outputStreamWriter.write(c, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (outputStreamWriter != null) {
outputStreamWriter.close();
}
if (inputStreamReader != null) {
inputStreamReader.close();
}
}
}
}

缓冲流(Buffered Stream)

缓冲流是Java IO库提供的一种流装饰器,用于提高IO操作的性能。内部维护了一个缓冲区(Buffer),在读取和写入数据时,先将数据存储到缓冲区中。当缓冲区满了或者达到一定条件时,才将数据真正读取或写入到底层数据源(如文件、网络等)中。

字符缓冲流

BufferedReader

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
public class BufferedReaderTest {
/**
* (1)实例化File类的对象,指明读入和写出的文件及地址
* (2)提供具体流读文件,输入流FileReader、输入缓冲流bufferedReader
* (3)读取数据
* (4)流的关闭操作
* 先关闭外侧的流,再关闭内层的流,因为关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略
*/
@Test
public void readerFile() {
BufferedReader bufferedReader = null;
FileReader fileReader = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file = new File("hello.txt");
//(2)提供具体流读写文件
//造节点流
fileReader = new FileReader(file);
//造缓冲流
bufferedReader = new BufferedReader(fileReader);
//(3)读取数据
char[] c = new char[3];//创建一个临时存放数据的数组
int num;//记录每次读入到数组的个数
while ((num = bufferedReader.read(c)) != -1) {
String str = new String(c, 0, num);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作(关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略)
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

BufferedWriter

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
public class BufferedWriterTest {
@Test
public void writerFile() {
BufferedWriter bufferedWriter = null;
try {
//(1)实例化File类的对象,指明写出的文件及地址
File file = new File("E:\\test.txt");
//(2)提供具体流写出文件,FileWriter
// 造节点流
FileWriter fileWriter = new FileWriter(file);
// 造缓冲流
bufferedWriter = new BufferedWriter(fileWriter);
//(3)从内存中写出数据到硬盘的文件里
bufferedWriter.write("hello");
bufferedWriter.newLine();//插入一个和系统相关的换行符
bufferedWriter.write("hello");

} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) throws IOException {
String filePath = "E:\\note.txt";
//(1)创建BufferedWriter,在节点流上多一个 true 表示追加写入内容,不加就是覆盖
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath, true));
//(2)写入内容与换行
bufferedWriter.write("hello");
bufferedWriter.newLine();//插入一个和系统相关的换行符
bufferedWriter.write("hello");

//(3)关闭外层流即可,传入的 new FileWriter(filePath)会在底层自动关闭
bufferedWriter.close();
}
}

BufferedReader、BufferedWriter复制文件

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
public class BufferedReader_BufferedWriter_Copy {
/**
* 指定路径下的文件复制:将代码封装,使用时只需要填入开始与结束路径
* (1)实例化File类的对象,指明读入和写出的文件及地址
* (2)提供具体流读写文件,
* 输入流和输出流FileReader、FileWriter
* 输入缓冲流和输出缓冲流bufferedReader、bufferedWriter
* (3)读入数据并写出数据到硬盘的文件里(复制的过程)
* (4)流的关闭操作
* 先关闭外侧的流,再关闭内层的流,因为关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略
*/
@Test
//文本文件复制
public void copyFile() {
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file1 = new File("E:\\1.png");
File file2 = new File("E:\\1.png");
//(2)提供具体流读写文件
// 造节点流
FileReader fileReader = new FileReader(file1);
FileWriter fileWriter = new FileWriter(file2);
// 造缓冲流
bufferedReader = new BufferedReader(fileReader);
bufferedWriter = new BufferedWriter(fileWriter);
//(3)复制的细节:读取、写入
char[] c = new char[3];
int num;//记录每次读入到数组的个数
while ((num = bufferedReader.read(c)) != -1) {
//每次写入num个字符
bufferedWriter.write(c, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作(关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略)
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

字节缓冲流

BufferedInputStream

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
public class BufferedInputStreamTest {
@Test
public void inputFile() {
BufferedInputStream bufferedInputStream = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file1 = new File("1.png");

//(2)提供具体流读写文件
//造节点流
FileInputStream fileInputStream = new FileInputStream(file1);
//造缓冲流
bufferedInputStream = new BufferedInputStream(fileInputStream);

//(3)复制的细节:读取、写入
byte[] b = new byte[3];
int num;//记录每次读入到数组的个数
while ((num = bufferedInputStream.read(b)) != -1) {
String str = new String(b, 0, num);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
// 先关闭外侧的流,再关闭内层的流,因为关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

BufferedOutputStream

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
public class BufferedOutputStreamTest {
/**
* 写出文件流程:
* (1)实例化File类的对象,指明写出的文件及地址
* (2)造字节输出流和缓冲流,FileOutputStream、BufferedOutputStream
* (3)从内存中写出数据到硬盘的文件里
* (4)关闭流
*/
@Test
public void outputFile() {
BufferedOutputStream bufferedOutputStream = null;
try {
//(1)实例化File类的对象,指明写出的文件及地址
File file = new File("E:\\test.txt");
//(2)造流
//造节点流
FileOutputStream fileOutputStream = new FileOutputStream(file);
//造缓冲流
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
//(3)从内存中写出数据到硬盘的文件里
bufferedOutputStream.write(12345);
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)关闭流
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

BufferedInputStream、BufferedOutputStream复制文件

(1)FileInputStream、FileOutputStream复制非文本文件复制

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
57
58
public class BufferedInputStream_BufferedOutputStream_Copy {
/**
* 指定路径下的文件复制:将代码封装,使用时只需要填入开始与结束路径
* (1)实例化File类的对象,指明读入和写出的文件及地址
* (2)提供具体流读写文件,
* 输入流和输出流FileInputStream、FileOutputStream
* 输入缓冲流和输出缓冲流bufferedInputStream、bufferedOutputStream
* (3)读入数据并写出数据到硬盘的文件里(复制的过程)
* (4)流的关闭操作
* 先关闭外侧的流,再关闭内层的流,因为关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略
*/
@Test
public void copyFile() {
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file1 = new File("E:\\1.png");
File file2 = new File("E:\\1.png");

//(2)提供具体流读写文件
//造节点流
FileInputStream fileInputStream = new FileInputStream(file1);
FileOutputStream fileOutputStream = new FileOutputStream(file2);
//造缓冲流
bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

//(3)复制的细节:读取、写入
byte[] b = new byte[3];
int num;//记录每次读入到数组的个数
while ((num = bufferedInputStream.read(b)) != -1) {
//每次写入num个字符
bufferedOutputStream.write(b, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
// 先关闭外侧的流,再关闭内层的流
//先关闭外侧的流,再关闭内层的流,因为关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

(2)复制代码逻辑封装

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class Demo {

public static void main(String[] args) {
// 获取开始的时间
long startTime = System.currentTimeMillis();

// 调用封装的复制文件逻辑实现复制
String startPath = "E:\\1.png";
String endPath = "E:\\1.png";
copyFile(startPath, endPath);

// 获取结束的时间
long endTime = System.currentTimeMillis();

System.out.println("复制花费的时间为:" + (endTime - startTime));//1
}

/**
* 指定路径下的文件复制:将代码封装,使用时只需要填入开始与结束路径
* (1)实例化File类的对象,指明读入和写出的文件及地址
* (2)提供具体流读写文件,
* 输入流和输出流FileInputStream、FileOutputStream
* 输入缓冲流和输出缓冲流bufferedInputStream、bufferedOutputStream
* (3)读入数据并写出数据到硬盘的文件里(复制的过程)
* (4)流的关闭操作
* 先关闭外侧的流,再关闭内层的流,因为关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略
*
* @param startPath 开始路径
* @param endPath 结束路径
*/
//指定路径下的文件复制
//将代码封装,使用时只需要填入开始与结束路径
public static void copyFile(String startPath, String endPath) {
BufferedInputStream bufferedInputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
//(1)实例化File类的对象,指明读入和写出的文件及地址
File file1 = new File(startPath);
File file2 = new File(endPath);

//(2)提供具体流读写文件
//造节点流
FileInputStream fileInputStream = new FileInputStream(file1);
FileOutputStream fileOutputStream = new FileOutputStream(file2);
//造缓冲流
bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);

//(3)复制的细节:读取、写入
byte[] b = new byte[3];//创建一个临时存放数据的数组
int num;//记录每次读入到数组的个数
while ((num = bufferedInputStream.read(b)) != -1) {
//每次写入num个字符
bufferedOutputStream.write(b, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
// 先关闭外侧的流,再关闭内层的流,因为关闭外层流的同时,内层流也会自动的进行关闭,所以内层流的关闭可以省略
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

对象流(Object Stream)

对象流是Java IO库中用于序列化和反序列化对象的流。它提供了将对象以字节流的形式进行读写的功能,可以实现对象的持久化、传输和共享。

ObjectOutputStream序列化

对象的序列化(Serialization)是指将一个对象转换为字节流的过程,以便于在网络上传输或者存储到文件中。

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
public class Demo {
/**
* 序列化:对象—>字节序列
* 用ObjectOutputStream类将内存中的java对象保存到磁盘中或通过网络传输出去
*/
@Test
public void test() {
ObjectOutputStream objectOutputStream = null;
try {
//(1)创建连接到指定文件的数据输出流对象
objectOutputStream = new ObjectOutputStream(new FileOutputStream("hello.txt"));
//(2)写入对象数据
objectOutputStream.writeObject(new String("你好java"));
objectOutputStream.writeObject(new Person("小明", 18));
objectOutputStream.flush();//刷新操作
} catch (IOException e) {
e.printStackTrace();
} finally {
//(3)关闭流
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

ObjectInputStream反序列化

反序列化(Deserialization)是将字节流重新转换为对象的过程,以便于恢复对象的状态和数据

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
public class Demo {
/**
* 反序列化:字节序列—>对象
* 用ObjectInputStream类将磁盘文件读取(还原)为内存中的一个java对象
*/
@Test
public void test() {
ObjectInputStream objectInputStream = null;
try {
//(1)创建连接到指定文件的数据输入流对象
objectInputStream = new ObjectInputStream(new FileInputStream("hello.txt"));
//(2)读取对象数据
Object o = objectInputStream.readObject();
String str = (String) o;
Person person = (Person) objectInputStream.readObject();

System.out.println(str);//你好java
System.out.println(person);//Person{name='小明', age=18}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
//3.关闭流
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

打印流(PrintStream)

打印流(PrintStream)是Java中用于将数据打印到控制台或文件的输出流。是Java标准库中的一个类,属于java.io包下的一部分。使用PrintStream可以方便地输出各种类型的数据,包括基本类型和对象。

常用方法

打印流提供了一系列的print和println方法来输出数据,并且具有自动转换数据类型和格式化输出的功能。

方法签名描述
println(String str)将指定的字符串以行的形式输出到打印流。每次调用该方法会输出一行内容,并在行末自动添加换行符 \n
print(String str)将指定的字符串输出到打印流,不换行。
flush()刷新打印流,将缓冲区的内容立即输出。调用该方法可以确保内容被及时输出到目标设备。
PrintStream(OutputStream out)创建一个新的打印流对象,并将输出重定向到指定的输出流。

使用案例

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
public class PrintStreamTest {
/**
* 将控制台输出的数据改为输出为文件
*/
@Test
public void test() {
PrintStream printStream = null;
try {
//(1)指明输出文件地址并提供流
FileOutputStream fileOutputStream = new FileOutputStream(new File("E:\\text.txt"));
//(2)创建打印输出流,把标准输出流改成输出为文件
// true表示设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
printStream = new PrintStream(fileOutputStream, true);
// 如果不为空就把控制台输出的数据改为输出为文件
if (printStream != null) {
System.setOut(printStream);
}
//(3)打印输出的数据:打印ASCII字符
for (int i = 0; i <= 255; i++) {
System.out.print((char) i);
//设置每50个数据换一行
if (i % 50 == 0) {
System.out.println(); //换行
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
//(4)流的关闭操作
if (printStream != null) {
printStream.close();
}
}
}
}

标准输入输出流

标准输入输出流简介
System.in标准的输入流,默认从键盘输入
System.out标准的输出流,默认从控制台输出
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
57
58
59
60
public class Demo {
/**
* 例题:
* 从键盘输入字符串,要求将读取到的整行字符串转成大写输出。
* 然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序。
* 实现方式
* 方式一:使用Scanner实现
* 方式二:使用标准输入输出流
*/
@Test
public void test1() {
Scanner scan = new Scanner(System.in);
String num = scan.next();

System.out.println("请输入字符:");
while (true) {
//判断是否为e或exit,如果是就退出,否则变为大写
//equalsIgnoreCase():忽略大小写判断是否为某个值
if ("e".equalsIgnoreCase(num) || "exit".equalsIgnoreCase(num)) {
System.out.println("安全退出!!");
break;//退出并结束程序
} else {
//toUpperCase():将读取到的整行字符串转成大写输出
System.out.println(num.toUpperCase());
}
}
}

@Test
public void test2() {
System.out.println("请输入信息(退出输入e或exit):");
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = null;
try {
while ((s = br.readLine()) != null) {
//判断是否为e或exit,如果是就退出,否则变为大写
//equalsIgnoreCase():忽略大小写判断是否为某个值
if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
System.out.println("安全退出!!");
break;//退出并结束程序
}
//toUpperCase():将读取到的整行字符串转成大写输出
System.out.println("-->:" + s.toUpperCase());
System.out.println("继续输入信息");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
//关闭过滤流时,会自动关闭它包装的底层节点流
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

RandomAccessFile类

简介

(1)RandomAccessFile类是 Java 输入/输出流体系中功能最丰富的文件内容访问类,不属于流,但提供了众多的方法来访问文件内容,可以读取文件内容,也可以向文件输出数据,既可以作为一个输入流也可以作为一个输出流

(2)RandomAccessFile类直接继承于java.lang.Object类,实现了DataInput、DataOutput这两个接口

(3)与普通的输入/输出流不同的是,RandomAccessFile 支持随机访问的方式,程序可以直接跳转到文件的任意地方来读写数据。

(4)RandomAccessFile类允许自由定位文件记录指针,可以不从开始的地方开始输出,因此如果只需要访问文件部分内容,而不是把文件从头读到尾或向已存在的文件后追加内容,使用 RandomAccessFile类是更好的选择

构造器

构造器简介
RandomAccessFile(File file, String mode)使用File参数来指定文件本身,mode 参数用来访问模式
RandomAccessFile(String name, String mode)使用String参数来指定文件名,mode 参数用来访问模式

创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式

访问模式简介
r以只读方式打开
rw打开以便读取和写入
rwd打开以便读取和写入;同步文件内容的更新
rws打开以便读取和写入;同步文件内容和元数据的更新

常用方法

方法简介
skipBytes(int n)使读写指针从当前位置开始,跳过n个字节
write(byte[] b)将指定的字节数组写入到这个文件,并从当前文件指针开始
getFilePointer()返回当前读写指针所处的位置
seek(long pos)设定读写指针的位置,与文件的开头相隔pos个字节数
setLength(long newLength)设置此文件的长度
readLine()从指定文件当前指针读取下一行内容

文件的读写操作

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
public class Demo {
/**
* RandomAccessFile类实现文件的读写操作
*/
@Test
public void test() {
RandomAccessFile randomAccessFile1 = null;
RandomAccessFile randomAccessFile2 = null;
try {
//(1)实例化File类的对象,指明要操作的文件
randomAccessFile1 = new RandomAccessFile(new File("带土.jpg"), "r");
randomAccessFile2 = new RandomAccessFile(new File("带土111.jpg"), "rw");

//(2)数据的读入
int num;
byte[] b = new byte[3];
while ((num = randomAccessFile1.read(b)) != -1) {
randomAccessFile2.write(b, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//(3)流的关闭操作
if (randomAccessFile2 != null) {
try {
randomAccessFile2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (randomAccessFile1 != null) {
try {
randomAccessFile1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

数据的插入操作

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
public class Demo {
/**
* RandomAccessFile类实现数据的插入
*/
@Test
public void test() {
RandomAccessFile randomAccessFile = null;
try {
//(1)实例化File类的对象,指明要操作的文件
randomAccessFile = new RandomAccessFile(new File("hello.txt"), "rw");

//(2)数据的操作
randomAccessFile.seek(3);//将文件记录指针定位到 3 的位置
//保存指针3后面的所有数据到stringBuffer中
StringBuffer stringBuffer = new StringBuffer((int) (new File("hello.txt").length()));
byte[] b = new byte[5];
int num;
while ((num = randomAccessFile.read(b)) != -1) {
String str = new String(b, 0, num);
stringBuffer.append(str);
}
//调回指针写入数据
randomAccessFile.seek(3);//将文件记录指针定位到 3 的位置
randomAccessFile.write("123".getBytes());//覆盖
//将stringBuffer中的数据再写入到文件
randomAccessFile.write(stringBuffer.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//(3)流的关闭操作
if (randomAccessFile != null) {
try {
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

I/O 模型

IO与I/O 模型

什么是IO?

  • 从计算机角度理解IO:从计算机角度来看,输入输出(IO)是指计算机与外部设备之间进行数据传递的过程,包括将外部设备的数据输入到计算机中进行处理,以及将计算机处理结果输出到外部设备进行显示或者存储。
  • 从操作系统角度理解IO:从操作系统角度来看,IO是指应用程序通过调用操作系统提供的API,将数据从用户空间传输到内核空间进行实际的输入输出操作,包括磁盘读写、网络通信等。用户空间是应用程序访问的内存区域,而内核空间是操作系统内核访问的受保护内存区域。因此应用程序的IO操作可以看作是IO调用触发操作系统内核执行实际的IO操作。

什么是IO模型?

IO模型(Input/Output Model)是描述在计算机系统中,如何处理输入和输出操作的一种模型,常见的有如下几种

  1. 阻塞IO模型(Blocking IO Model):当应用程序执行IO操作时,整个进程会被阻塞,直到操作完成。在等待IO完成期间无法执行其他任务,效率较低。
  2. 非阻塞IO模型(Non-blocking IO Model):当应用程序执行IO操作时,可以立即返回,而不必等待操作完成。通过轮询或异步通知方式,应用程序可以继续执行其他任务,并周期性地检查IO操作是否完成,但仍然需要主动轮询,效率仍然有限。
  3. 多路复用IO模型(Multiplexing IO Model):通过使用系统调用(如select、poll、epoll等),可以同时监听多个IO操作的完成情况。应用程序将IO操作注册给内核,内核会通知应用程序哪些IO操作已经完成,从而避免了轮询的开销,提高了效率。
  4. 信号驱动IO模型(Signal-driven IO Model):应用程序将IO操作注册给内核,并指定一个信号处理函数。当IO操作完成时,内核会向应用程序发送一个信号,应用程序在信号处理函数中进行相应的处理。这种模型可以避免轮询,但需要处理信号的开销。
  5. 异步IO模型(Asynchronous IO Model):应用程序发起IO操作后,不需要等待操作完成,可以继续执行其他任务。当IO操作完成时,系统会通知应用程序,应用程序可以回调相应的处理函数进行后续处理。异步IO模型相对于其他模型较为复杂,但具有较高的性能和灵活性。

为了方便理解IO模型,可以拿现实中的例子来比喻这个过程,假设一个老师正在收取学生的作业。

  1. 同步阻塞方式(Blocking):老师逐个学生地等待每个学生完成作业再继续收下一个学生的作业。当一个学生没有完成作业时,老师会一直等待,直到该学生完成作业后才能继续收取下一个学生的作业。这种方式下,老师需要阻塞等待每个学生完成作业,效率较低。
  2. 同步非阻塞方式(Non-blocking):老师逐个学生地收取作业,但如果某个学生还没完成作业,老师会暂时跳过该学生继续收取下一个学生的作业,直到之前的学生完成作业后再回来收取。这种方式下,老师不会阻塞在每个学生上,但仍然需要轮询每个学生是否完成作业。
  3. IO多路复用方式(select和poll):老师相当于在询问所有的学生是否完成作业,但并不知道具体哪个学生完成了。老师持续地询问每个学生是否完成作业,直到所有学生中有学生举手表示完成作业为止。这种方式下,老师需要不断地轮询每个学生来确定是否完成作业,存在一定的性能开销。
  4. IO多路复用方式(epoll):学生举手相当于触发了一个事件,并告诉老师是哪个学生举手了。老师只需要关注那些举手的学生,而不需要轮询询问每个学生,从而提高了效率。这种方式下,老师能够直接知道具体是哪个学生完成了作业,避免了轮询和性能开销。
  5. 异步IO方式(Async):老师委托一位助教负责收取学生的作业,然后可以继续进行其他工作。助教负责等待学生完成作业,当有学生完成作业时,助教会通知老师。这种方式下,老师完全不需要关注学生的作业进度,只需等待助教的通知即可。与其他模型相比,异步IO方式能够充分利用时间,提高效率。

常见的五种 IO 模型

阻塞IO模型(Blocking IO Model)

阻塞IO是最传统的IO模型,它的特点是当应用程序执行IO操作时,整个进程会被阻塞,直到操作完成

这种模型在等待IO完成期间无法执行其他任务,直到IO操作完成,效率较低。

非阻塞IO模型(Non-blocking IO Model)

非阻塞IO模型中,当应用程序执行IO操作时,可以立即返回并执行其他任务,而不必等待操作完成。

但应用程序需要轮询或者使用多线程,周期性检查IO操作是否已经完成,如果IO操作还未完成,则返回一个错误码或者空数据,应用程序需要再次发起IO操作。

虽然非阻塞IO模型可以在应用程序执行IO操作时立即返回并执行其他任务,避免了等待操作完成的阻塞状态,但应用进程需要主动轮询,效率仍然有限。

多路复用IO模型(Multiplexing IO Model)

多路复用IO模型中,通过使用操作系统提供的select、poll或者epoll机制,可以同时监听多个IO操作的完成情况。

  • selectselect是最早出现的多路复用函数之一,适用于Unix/Linux等操作系统。
  • pollpoll是对select的改进,也用于Unix/Linux等操作系统。
  • epollepoll是Linux特有的多路复用机制,具有更高的性能和可扩展性。

应用程序将IO操作注册给内核,内核会调用select、poll或者epoll函数,进行监听多个IO操作的完成情况。这些函数会阻塞等待,直到任何一个IO事件发生,一旦有IO事件发生,内核会通知应用程序,并返回哪些文件描述符已经就绪,以便应用程序进行相应的IO操作处理,从而避免了应用进程轮询的开销,提高了效率。

信号驱动IO模型(Signal-driven IO Model)

信号驱动IO模型中,应用程序将IO操作注册给内核,并指定一个信号处理函数,可以继续执行其他任务而不被阻塞。

当操作系统完成IO操作后,内核会向应用程序发送一个信号,表示IO已经完成。应用程序收到信号后,可以处理已完成的IO操作。

这种模型可以避免轮询,但需要操作系统支持,并且对信号处理也是开销。

异步IO模型(Asynchronous IO Model)

异步IO模型中,应用程序发起IO操作后,不需要等待操作完成,可以继续执行其他任务。当操作系统完成IO操作后,系统会通知应用程序,并将结果返回给应用程序,应用程序可以回调相应的处理函数进行后续处理。异步IO模型相对于其他模型较为复杂,但具有较高的性能和灵活性。

BIO、NIO、AIO

BIO(Blocking I/O)、NIO(Non-blocking I/O)和AIO(Asynchronous I/O)是Java中用于处理I/O操作的三种不同的编程模型

BIO、NIO、AIO对比

对比BIONIOAIO
IO模型同步阻塞同步非阻塞 (多路复用)异步非阻塞
编程难度简单复杂复杂
处理效率
并发力

BIO、NIO、AIO使用场景

虽然Java-NIOJava-AIO,在性能上比Java-BIO要强很多,但是基本没人直接用Java-NIOJava-AIO,如果要进行网络通信,一般都会采用Netty,它对原生的Java-NIO进行了封装优化

I/O 模型使用场景
BIO连接数较少且并发要求不高的场景,如传统的Socket编程
NIO大规模并发连接的场景,如弹幕系统、聊天服务器、游戏服务器等
AIO大规模并发连接且每个连接的数据处理时间较长的场景,如高性能服务器、文件传输等

BIO(同步阻塞I/O)

BIO简介

(1)BIO也称为阻塞I/O模型,在JDK1.4之前建立网络连接时采用的都是 BIO 模式,是最传统的Java I/O编程模型

(2)BIO相关的类和接口位于java.io包下,如InputStream、OutputStream、Reader和Writer等。

(3)BIO模型在处理客户端请求时,通常会为每个请求分配一个线程。当连接建立之后,如果该线程没有操作,它将会一直阻塞等待,这样会导致服务器资源浪费,在高并发的场景下,机器资源很快就会被耗尽。

BIO 通信模型图

NIO(同步非阻塞I/O)

NIO简介

(1)NIO是从Java 1.4版本引入的一套新的IO API,可以替代标准的Java IO API,提供了一系列改进的输入/输出的新特性

(2)NIO相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。

(3)NIO的特点是同步非阻塞,同步指的是必须等待 IO 缓冲区内的数据就绪,而非阻塞指的是,用户线程不原地等待 IO 缓冲区,可以先做一些其他操作,但是要定时轮询检查 IO 缓冲区数据是否就绪

NIO三大核心部分

  • 通道(Channel):与数据源(如文件或网络套接字)进行交互的对象,可以用于读取和写入数据,并且可以支持非阻塞式的 I/O 操作

  • 缓冲区(Buffer):用于存储数据的对象

  • 选择器(Selector):用于监视多个通道的对象,通过选择器,可以使用单个线程同时监视多个通道上的事件,实现多路复用

NIO的执行流程

  1. 创建通道(Channel):首先,需要创建一个通道对象,可以是文件通道(FileChannel)或网络通道(SocketChannel、ServerSocketChannel)。通道负责与数据源进行交互。
  2. 创建缓冲区(Buffer):为了进行数据的读取和写入,需要创建一个缓冲区对象。不同类型的数据可以使用不同的缓冲区,如字节缓冲区(ByteBuffer)、字符缓冲区(CharBuffer)等。缓冲区是存储数据的容器。
  3. 将通道注册到选择器(Selector):将创建的通道注册到选择器上。选择器是多路复用器,可以同时监控多个通道的状态。通过选择器,可以实现单线程处理多个通道的就绪事件。
  4. 轮询选择器:使用一个线程轮询选择器上注册的通道,以获取就绪状态的通道。当通道准备好进行读取或写入时,选择器会返回相应的通道集合。
  5. 处理就绪事件:根据选择器返回的就绪通道集合,进行相应的读取或写入操作。可以使用缓冲区从通道中读取数据,或者将数据写入到通道中。
  6. 循环执行轮询:在一次循环执行完毕后,再次进行轮询操作,处理新的就绪事件。这样,可以不断地处理多个通道的就绪事件,实现高效的非阻塞式 I/O 操作。

NIO 通信模型图

AIO(异步非阻塞I/O)

AIO简介

(1)AIO是NIO的升级版本,也被称为NIO 2.0。它引入了异步通道的概念,并采用了Proactor模式。

(2)AIO模型采用了异步的方式处理I/O操作。当应用程序发起一个I/O请求后,不需要等待操作完成,而是继续执行其他任务。当I/O操作完成时,系统会通知应用程序,并可以通过回调机制来处理已完成的操作

(3)JDK 7引入了Path、Paths和Files三个类,声明在java.nio.file包下。这些类增强了对文件处理和文件系统特性的支持,让处理文件和I/O操作变得更加便捷。这些类的引入是为了进一步完善NIO,使其更适合处理现代应用中的文件操作需求

简介
PathPath表示文件或目录的路径。它提供了一系列方法来操作路径,例如获取父路径、解析路径、合并路径等,可以看做是java.io.File类的升级版本
PathsPaths类是Path类的工具类,提供了静态方法用于创建Path对象
FilesFiles类提供了一系列静态方法来进行文件和目录的操作。它可以完成文件的复制、移动、删除、创建目录等操作,还可以读取和写入文件的内容

NIO的执行流程

  1. 创建异步通道:首先,需要创建一个异步通道对象,用于与数据源进行交互,可以是文件通道或网络通道。

  2. 创建缓冲区:与 NIO 类似,在 AIO 中也需要创建一个缓冲区对象来存储数据。

  3. 准备回调函数:回调函数是异步操作完成后的回调方法。在发起异步操作之前,需要准备一个回调函数来处理操作完成后的结果。

  4. 发起异步操作:通过异步通道发起读取或写入操作。与传统的同步 I/O 不同,AIO 的异步操作不会阻塞线程,而是在后台进行。

  5. 继续执行其他任务:在异步操作进行的同时,可以继续执行其他任务。

  6. 异步操作完成后触发回调:当异步操作完成时,会触发事先准备好的回调函数。回调函数会将异步操作的结果返回,并进行相应的处理。

AIO模型的优点

  • 异步处理:应用程序在发起I/O请求后可以继续执行其他任务,不需要等待I/O操作完成。
  • 非阻塞:AIO模型允许应用程序在执行I/O操作时不被阻塞,而是立即返回并继续执行其他任务。
  • 资源效率:由于不需要为每个I/O操作创建一个线程,AIO模型能够更有效地利用系统资源,提高并发性能。

Path

Path类常用方法

方法描述
getFileName()返回路径中表示文件或目录的最后一个元素
getParent()返回路径中表示父目录的Path对象
resolve(String other)将给定的字符串(或另一个Path对象)添加到当前路径后面
toAbsolutePath()返回绝对路径的Path对象
normalize()返回表示去除冗余标识符的规范化路径的Path对象

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PathTest {
/**
* Path中的常用方法
*/
public static void main(String[] args) {
Path path = Paths.get("/path/to/file.txt");

// 获取文件名
System.out.println("File name: " + path.getFileName());

// 获取父目录
System.out.println("Parent directory: " + path.getParent());

// 解析路径
System.out.println("Resolved path: " + path.resolveSibling("newFile.txt"));

// 获取绝对路径
System.out.println("Absolute path: " + path.toAbsolutePath());

// 规范化路径
System.out.println("Normalized path: " + path.normalize());
}
}

Paths

Paths类常用方法:

方法描述
get(String first, String... more)根据给定的路径元素获取Path对象
get(URI uri)根据给定的URI获取Path对象

示例代码:

1
2
3
4
5
6
7
8
9
10
public class PathsTest {
public static void main(String[] args) {
Path path1 = Paths.get("/path/to");
Path path2 = Paths.get("file.txt");

// 合并路径
Path fullPath = Paths.get(path1.toString(), path2.toString());
System.out.println("Full path: " + fullPath);
}
}

Files

Files类常用方法:

方法描述
copy(Path source, Path target, CopyOption... options)复制文件或目录
move(Path source, Path target, CopyOption... options)移动文件或目录
delete(Path path)删除文件或目录
exists(Path path)判断文件或目录是否存在
createDirectory(Path dir, FileAttribute<?>... attrs)创建目录
readAllLines(Path path, Charset cs)读取文件的所有行
write(Path path, byte[] bytes, OpenOption... options)将字节数组写入文件
newInputStream(Path path, OpenOption... options)创建文件输入流
newOutputStream(Path path, OpenOption... options)创建文件输出流

示例代码:

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
public class FilesTest {
public static void main(String[] args) throws Exception {
Path sourcePath = Paths.get("/path/to/source.txt");
Path targetPath = Paths.get("/path/to/target.txt");

// 复制文件
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);

// 判断文件是否存在
boolean exists = Files.exists(targetPath);

// 删除文件
Files.delete(targetPath);

// 创建目录
Path directoryPath = Paths.get("/path/to/directory");
Files.createDirectory(directoryPath);

// 读取文件的所有行
List<String> lines = Files.readAllLines(sourcePath, StandardCharsets.UTF_8);

// 将字节数组写入文件
byte[] data = "Hello, World!".getBytes();
Files.write(targetPath, data, StandardOpenOption.CREATE);

// 创建文件输入流
InputStream inputStream = Files.newInputStream(sourcePath);

// 创建文件输出流
OutputStream outputStream = Files.newOutputStream(targetPath);
}
}