0%

Java基础-IO流

image-20200822214135992

File类

实例创建

此时并不能在硬盘中创建文件

ps: Windows: \\; Unix: /

创建一个具体文件File类实例:File(String filePath)

1
2
File file1 = new File("hello.txt"); // 相对路径
File file2 = new File("D:\\JavaLearning\\JavaSenior\\day08\\hello.txt"); // 绝对路径

创建一个具体文件夹File类实例:File(String parentPath, String childPath)File(File parentFile, String childPath)

1
2
File file3 = new File("D:\\JavaLearning","JavaSenior");
File file4 = new File(file3,"hi.txt");

在idea中,对于相对路径,文件创建于此文件所在的Module文件夹下。

常见方法

获取File类文件信息

1
2
3
4
5
6
7
8
9
10
11
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值


//如下的两个方法适用于文件目录:
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组

判断File类文件信息

1
2
3
4
5
6
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏

对File类实现硬盘文件创建

1
2
3
4
5
6
7
8
//创建硬盘中对应的文件或文件目录
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建

//删除磁盘中的文件或文件目录
public boolean delete():删除文件或者文件夹
//注意事项:Java中的删除不走回收站。

遍历所有文件(递归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* @description: 获取目标文件目录的所有文件
* @author: YuanbaoQiang
* @date: 2020/8/21 9:43
* @param arr 传入一个空的动态数组
* @param file 目标文件目录
* @return: java.util.ArrayList<java.io.File>
*/
public static ArrayList<File> listAllFiles(ArrayList<File> arr, File file){
// 记录file目录下的文件和文件夹,并输出
File[] files = file.listFiles();
// 处理文件
for(File data : files){
if(data.isDirectory()){ // 文件夹,递归
listAllFiles(arr,data);
}else{ // 文件
arr.add(data);
}
}
return arr;
}

IO流

基本分类

  1. 操作数据类型:字节流,字符流
  2. 数据的流向:输入流,输出流
  3. 流的角色:节点流,处理流

流的体系

image-20200822214534742

数据读入写出

对于文本文件(.txt; .c; .cpp),使用字符流处理

对于非文本文件(.jpg,.mp3,.avi,.ppt,.doc),使用字节流

基本操作

  1. 实例化File类对象,指明要操作的文件(需要读出的文件,需要写入的文件);
  2. 提供具体的处理字节(字符)流(输入流、输出流);
  3. 数据的读取、写入;
  4. 关闭流操作。
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
@Test
public void testFileReaderFileWriter() {
FileReader fr = null;
FileWriter fw = null;
try {
//1. 创建File类的对象,指明读入和写出的文件
File scrFile = new File("hello.txt");
File destFile = new File("hello2.txt");
// 不能使用字符流来使用图片等字节数据
/* File scrFile = new File("wallhaven-oxrq99.jpg");
File destFile = new File("wallhaven.jpg");*/

//2. 创建输入流和输出流的对象
fr = new FileReader(scrFile);
fw = new FileWriter(destFile);

//3. 数据的读入和写出操作
char[] cbuf = new char[5];
int len; //记录每次读入cbuf数组中的数据的字符个数
while((len = fr.read(cbuf)) != -1){
// 每次写出len个字符
fw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭流资源
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

控制台输出乱码

对于一个hello.txt文本,有以下文本内容,并用字符流读取文件,将内容打印输出在控制台。

中南大学在读中南大学在读中南大学在读中南大学在读中南大学在读中南大学在读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void test1(){
FileInputStream fis = null;
try {
fis = new FileInputStream(new File("hello.txt"));

byte[] buffer = new byte[5];
int len;
while((len = fis.read(buffer)) != -1){
String str = new String(buffer,0,len);
System.out.print(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

最终输出,出现乱码:

中�����学在�����南大�����读中�����学在�����南大�����读中�����学在�����南大�����读

明显,在读取过程中,汉字并不能被连续输出显示。由于是UTF-8编码,所以一个汉字是三个字节,导致被在被长度为5的byte数组接收输出时,会出现断码情况。但是如果将字节再写入到另外一个文本文件,依旧是完整的文本信息,此时字节自动拼接成汉字。

改进

采用ByteArrayOutputStream(),空参情况下,会新建一个长度32字节的数组,如果加入的内容超过32字节,就会自动进行扩充,保证在文本输出前,不会出现断码情况。

1
2
3
4
5
6
7
/**
* Creates a new {@code ByteArrayOutputStream}. The buffer capacity is
* initially 32 bytes, though its size increases if necessary.
*/
public ByteArrayOutputStream() {
this(32);
}
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
@Test
public void test2() {
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try {
fis = new FileInputStream(new File("hello.txt"));

baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = fis.read(buffer)) != -1){
baos.write(buffer,0,len);
}

System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
if(baos != null){

try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(fis != null){

try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

最终输出文本,无乱码:

中南大学在读中南大学在读中南大学在读中南大学在读中南大学在读中南大学在读

处理流使用

缓冲流

缓冲流BufferedInputStreamBufferedReaderBufferedOutputStreamBufferedWriter 通过设置缓冲区,空间换时间,减少读取写出次数,达到提高效率的目的。具体可以参考:缓冲流如何提高性能,感兴趣的顺便可以利用数组实现循环队列,刷一道中等力扣:622. 设计循环队列

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
@Test
public void BufferedStreamTest() {
FileInputStream fis = null;
FileOutputStream fos = null;
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
// 1. 造File类b
File srcFile = new File("wallhaven-oxrq99.jpg");
File destFile = new File("wallhaven1.jpg");
// 2. 造流
// 2.1 造节点流
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
// 2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);

// 3. 复制的细节:读取和写入
byte[] buffer = new byte[10];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4. 关闭流
// 要求:先关闭外层的流,再关闭内层的流

if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}

}

if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}

// 说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭可以省略
//fos.close();
//fis.close();
}

}

转换流

InputStreamReader: 字节 —> 字符 ——— 解码过程

OutputStreamWriter: 字符 —> 字节 ———编码过程

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
@Test
public void test2(){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");

fis = new FileInputStream(file1);
fos = new FileOutputStream(file2);

// 转换流
// 参数1确定输入输出流,参数2指定读入或者写出的字符集
// 其中读取时字符集的设定,取决于文件保存时的字符集编码
// 例如文件dbcp.txt以utf-8格式保存,则也应该用utf-8字符集解码
InputStreamReader isr = new InputStreamReader(fis,"UTF-8"); // 第二个形参不写默认为UTF-8
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk"); // 目标文件改为gbk编码

// 读写过程
char[] cbuf= new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fos != null){

try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if(fis != null){

try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

对象流

用于存储和读取基本数据类型或者对象的处理流

对象的序列化就是将对象转换成二进制数据流的一种实现手段,通过将对象序列化,可以方便的实现对象的传输及保存。当使用对象流写入或者读取对象的时候,必须保证该对象是序列化的,这样是为了保证对象能够正确的写入文件,并能够把对象正确的读回程序。在Java中提供了ObejctInputStreamObjectOutputStream这两个类用于序列化对象的操作。ObjectOutputStreamObjectInputStream不能序列化statictransient修饰的成员变量。

序列化条件:

  1. 需要实现接口:Serializable;(该接口没有任何属性和方法,仅仅作为序列化的表示)
  2. 需要当前类提供一个全局常量:serialVersionUID;
  3. 除了需要读入的类需要实现Serializable接口之外,还必须保证其内部所有属性也必须是序列化的。(默认情况下,基本数据类型也是可序列化的)。

RandomAccessFile类

RandomAccessFile 既可以读取文件内容,也可以向文件输出数据。由于 RandomAccessFile 可以从任意位置访问文件,所以在只需要访问文件部分内容的情况下,使用 RandonAccessFile 类是一个很好的选择。

RandomAccessFile一共有两个构造器,形参1都是为了提供文件,形参2则是指定文件读写类型:

  • r: 以只读形式打开
  • rw: 打开以便读取和写入
  • rwd: 打开以便读取和写入,同步文件内容的更新
  • rws: 打开以便读取和写入,同步文件内容和元数据的更新

一般常用:rrw

1
2
3
4
5
public RandomAccessFile(String name, String mode)
throws FileNotFoundException
{
this(name != null ? new File(name) : null, mode);
}
1
2
3
4
5
public RandomAccessFile(File file, String mode)
throws FileNotFoundException
{
this(file, mode, false);
}

该类存在一个seek指针(默认为0),实现从指针处开始向后覆盖数据。对于初始文本数据:abcdefghijk,将指针改为3后写入xyz,最后文本数据为:abcxyzghijk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void test2() {
RandomAccessFile raf1 = null;
try {
raf1 = new RandomAccessFile("hello.txt","rw");

raf1.seek(3);// 将指针直到角标为3的位置
raf1.write("xyz".getBytes()); // 覆盖的操作
} catch (IOException e) {
e.printStackTrace();
} finally {
if(raf1 != null){
try {
raf1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}