1. I/O 文件对象

文件和文件夹都是用File代表。

使用File类根据绝对路径或相对路径创建File对象。

方法 简介
exists 文件是否存在
isDirectory 是否是文件夹
isFile 是否是文件
length 文件长度
lastModified 文件最后修改时间
setLastModified 设置文件修改时间
renameTo 重命名
list 以字符串数组的形式,返回当前文件夹下所有文件
listFiles 以文件数组的形式,返回当前文件夹下所有文件(可以返回文件夹,不能返回子文件夹下的文件)
getParent 以字符串形式返回获取所在文件夹
getParentFile 以文件形式返回获取所在文件夹
mkdir 创建文件夹,如果父文件夹不存在,创建无效
mkdirs 创建文件夹,如果父文件夹不存在,会创建父文件夹
createNewFile 创建一个空文件,如果父文件夹不存在,会抛出异常
listRoots 列出所有盘符
delete 删除文件
deleteOnExit JVM结束的时候,删除文件,常用于临时文件的删除。
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
package io.demo01;

import java.io.File;

public class Practice02 {

public static void main(String[] args) {
// 遍历文件夹下的所有内容,包括子文件夹。
File file = new File("D:\\Media");
goThroughDirectory(file);
}

public static void goThroughDirectory(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File file1 : files) {
goThroughDirectory(file1);
}
} else {
System.out.printf("文件路径:%s,文件大小:%d", file.getAbsolutePath(), file.length());
System.out.println();
}
}

}

2. 什么是流?

什么是流(Stream),流就是一系列的数据。当不同的介质之间有数据交互的时候,Java就使用流来实现。数据源可以是文件,还可以是数据库,网络甚至是其它的程序。

比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流。

输入流:InputStream
输出流:OutputStream

3. 字节流

InputStream 字节输入流
OutputStream 字节输出流
用于以字节的形式读取和写入数据。

3.1 ASCII码

所有的数据存放在计算机中都是以数字的形式存放的。所以字母就需要转换为数字才能够存放。比如A就对应的数字65,a对应的数字97。不同的字母和符号对应不同的数字,就是一张码表。ASCII就是这样的一张码表。它只包含简单的英文字母,符号,数字等等。不包含中文,德文,俄语等复杂的。

3.2 以字节流的形式读取文件内容

InputStream是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。FileInputStream是InputStream的子类,以FileInputStream进行文件读取。

步骤:

  1. 创建File对象
  2. 创建基于文件的字节输入流
  3. 创建字节数组(长度就是文件长度)
  4. 以字节流的形式读取文件所有内容
  5. 使用完流,应该进行关闭

写入文件内容类似(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
package io.demo03;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class Practice01 {

public static void main(String[] args) {
/* 当文件不存在时候,是会自动创建文件的。
但如果路径当中有文件夹存在时,而文件夹又没有存在时,则会抛出异常。
怎么自动创建目录呢?

判断该文件对象是否存在,若不存在,则getParentFile对象,创建父目录(mkdirs)
*/
String path = "D:\\def\\abc\\lol.txt";
writeFile(path);
}

public static void writeFile(String path) {
// 1. 创建File对象
File f = new File(path);
if (!f.exists()) {
System.out.println(f.getParentFile().mkdirs());
}
// 2. 创建字节数组
byte[] bytes = {78, 71, 95};
try {
// 3. 创建字节输出流
FileOutputStream fos = new FileOutputStream(f);
// 4. 把数据写入到流中
fos.write(bytes);
// 5. 关闭输出流
fos.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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package io.demo03;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;

public class Practice02 {

public static void main(String[] args) throws IOException{
/*
找到一个大于100k的文件,按照100k为单位,拆分成多个子文件,并且以编号作为文件名结束
*/
splitFile("D:\\JDK_API_1_6_zh_CN.chw");
}

public static void splitFile(String path) throws IOException {
File f = new File(path);
FileInputStream fis = new FileInputStream(f);
// 创建字节数组
byte[] bytes = new byte[(int) f.length()];
// 读取文件
fis.read(bytes);
fis.close();

int size = 100 * 1024;
// 判断要分割成几个文件
int sum;
if (0 == bytes.length % size) {
sum = (int) (bytes.length / size);
} else {
sum = (int) (bytes.length / size) + 1;
}
for (int i = 0; i < sum; i++) {
String fileName = String.format(f.getName() + "-%d", i + 1);
File fileSplit = new File(fileName);
FileOutputStream fos = new FileOutputStream(fileSplit);
byte[] eachContent;
if (i + 1 < sum) {
// 不是最后一个分割文件
eachContent = Arrays.copyOfRange(bytes, i * size, (i + 1) * size);
} else {
// 最后一个分割文件
eachContent = Arrays.copyOfRange(bytes, i*size, bytes.length);
}

fos.write(eachContent);
fos.close();

System.out.format("输出子文件:%s,大小是%d字节",
fileSplit.getName(), fileSplit.length());
System.out.println();
}
}

}
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
package io.demo03;

import java.io.*;

public class Practice03 {

public static void main(String[] args) throws IOException{
// 把上述拆分出来的文件,合并成一个原文件。
mergeFile(4, "JDK_API_1_6_zh_CN.chw");
}

public static void mergeFile(int sum, String fileName) throws IOException {
File linkFile = new File(fileName);
FileOutputStream fos = new FileOutputStream(linkFile);

for (int i = 0; i < sum; i++) {
String subFile = String.format(fileName + "-%d", i + 1);
File f = new File(subFile);
FileInputStream fis = new FileInputStream(f);
byte[] bytes = new byte[(int) f.length()];
fis.read(bytes);
fis.close();

fos.write(bytes);
fos.flush(); // 刷新释放输出流数据
System.out.printf("把子文件%s合并到%s", subFile, fileName);
System.out.println();
}
fos.close();

System.out.printf("合并完成,文件大小为:%d字节", linkFile.length());
System.out.println();
}

}

4. 关闭流的方式

所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。如果不关闭,会产生对资源占用的浪费。当量比较大的时候,会影响到业务的正常开展。

4.1 在try中关闭

在try的作用域中关闭文件输入流。

弊端:如果文件不存在,或者读取的时候出现问题抛出异常,那么就没有执行关闭流的代码,存在巨大的资源占用隐患。不推荐使用

4.2 在finally中关闭

这是标准的关闭流的方式

  1. 首先把流的引用声明在try外面,如果声明在try里面,其作用域无法抵达finally
  2. 在finally关闭之前,需要先判断流的引用是否为空
  3. 关闭的时候,需要再进行一次try catch处理。

这是标准的严谨的关闭流的方式,但是比较繁琐,所以写不重要的代码时,一般采用有隐患的try方式,比较简单。

4.3 使用try()的方式

把流定义再try()里,try, catch或者finally结束的时候,会自动关闭,这种编写代码的方式叫做try-with-resources,这是从JDK7开始支持的技术。

5. 字符流

Reader 字符输入流
Writer 字符输出流

专门用于字符的形式读取和写入数据

5.1 使用字符流读取文件

FileReader是Reader的子类。

步骤:

  1. 创建File对象
  2. 创建基于File对象的Reader
  3. 创建字符数组,其长度就是文件的长度
  4. 以字符流的形式读取文件所有内容。

5.2 使用字符流把字符串写入到文件

同读取文件类似。

6. 中文问题

6.1 编码问题

计算机存放数据只能存储数字,所有的字符都会被转换为数字。

ASCII - 数字和西欧字母
GBK GB2312 BIG5 - 中文
UNICODE - 统一码,万国码

GB2312 是简体中文,BIG5是繁体中文,GBK同时包含简体和繁体以及日文。
UNICODE 包括了所有的文字,无论中文,英文,藏文,法文,世界所有的文字都包含其中

6.2 UNICODE和UTF

由于UNICODE要存放所有的数据,每个数字对应4个字节,这样存储数据就会有很大的浪费。比如一篇文章大部分都是英文字母,按照UNICODE编码就会消耗很多存储空间。

在这种情况下,就出现了UNICODE的各种减肥子编码,比如UTF-8对数字和字母就使用一个字节,而对汉字就使用3个字节,从而达到了减肥还能保证健康的效果。

Java采用的是UNICODE.

7. 缓存流

以介质是硬盘为例,字节流和字符流的弊端是:在每一次读写的时候,都会访问硬盘。如果读写的频率比较高的时候,其性能表现不佳。

为了解决以上弊端,采用缓存流。缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,知道缓存中的数据读取完毕,再到硬盘中读取。

就好比吃饭,不同缓存就是每吃一口饭都到锅里去铲;用缓存就是先把饭盛到碗里,碗里的吃完了,再到锅里去铲。

缓存流在写入数据时,会先把数据写入到缓存区,知道缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了IO操作。

7.1 使用缓存流读取数据

缓存字符输入流BufferedReader可以一次读取一行数据。

步骤:

  1. 创建File对象
  2. 创建文件字符流FileReader
  3. 缓存流必须建立在一个存在的流的基础上BufferedReader
  4. 一次读取一行内容。(readLine)

7.2 使用缓存流写出数据

缓存字符输出流PrintWriter可以一次写出一行数据。

类似7.1缓存流读取数据。(println)

会以覆盖的形式写入。

7.3 flush

有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去,这时候就需要用到flush。(强制把缓存中的数据写入硬盘,无论缓存是否已满。)

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
package io.demo07;

/*
设计一个方法,用于移除Java文件中的注释(以//开头的注释行)
*/

import java.io.*;

public class Practice01 {

public static void main(String[] args) {
File javaFile = new File("D:\\新建文本文档.java");
removeComments(javaFile);
}

public static void removeComments(File javaFile) {
StringBuffer stringBuffer = new StringBuffer("");
try (FileReader fr = new FileReader(javaFile);
BufferedReader br = new BufferedReader(fr)) {
while (true) {
String line = br.readLine();
if (null == line) {
break;
}
if (line.startsWith("//")) {
continue;
}
stringBuffer.append(line);
stringBuffer.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}

try (FileWriter fw = new FileWriter(javaFile);
PrintWriter pw = new PrintWriter(fw)) {
pw.println(stringBuffer.toString());
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("修改成功");
}

}

8. 数据流

DataInputStream 数据输入流
DataOutputStream 数据输出流

使用数据流的writeUTF()和readUTF()可以进行数据的格式化顺序读写。例如可以通过DataOutputStream向文件顺序写入布尔值,整数和字符串。然后再通过DataInputStream顺序读入这些数据。

要使用DataInputStream读取一个文件,这个文件必须是由DataOutputStream写出的,否则会报错。

数据流也要建立在其它流之上(字节流)

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
package io.demo08;

/*
要求:
第一种方式:使用缓存流把两个数字以字符串的形式写到文件里,再用缓存流以字符串的形式读取,最后转换为
两个数字。
第二种方式:使用数据流DataOutputStream向文件连续写入两个数字,然后用DataInputStream连续读取两个数字。
*/

import java.io.*;

public class Practice01 {

public static void main(String[] args) {
File file = new File("D:\\a.txt");
String str = "234@78";
writeBuffer(str, file);
readBuffer(file);

File file2 = new File("D:\\b.txt");
int[] ints = {234, 78};
writeDataStream(ints, file2);
readDataStream(file2);
}

public static void writeBuffer(String str, File file) {
try (FileWriter fw = new FileWriter(file);
PrintWriter pw = new PrintWriter(fw)) {
pw.println(str);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}

public static void readBuffer(File file) {
try (FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr)) {
String str;
int a = 0;
int b = 0;
while (null != (str = br.readLine())) {
String[] strings = str.split("@");
a = Integer.parseInt(strings[0]);
b = Integer.parseInt(strings[1]);
}
System.out.println(a);
System.out.println(b);
} catch (IOException e) {
e.printStackTrace();
}
}

public static void writeDataStream(int[] ints, File file) {
try (FileOutputStream fos = new FileOutputStream(file);
DataOutputStream dos = new DataOutputStream(fos)) {
for (int anInt : ints) {
dos.writeInt(anInt);
}
} catch (IOException e) {
e.printStackTrace();
}
}

public static void readDataStream(File file) {
try (FileInputStream fis = new FileInputStream(file);
DataInputStream dis = new DataInputStream(fis)) {
for (int i = 0; i < 2; i++) {
System.out.println(dis.readInt());
}
} catch (IOException e) {
e.printStackTrace();
}
}

}

9. 对象流

对象流指的是可以直接把一个对象以流的方式传输给其它介质,比如硬盘。一个对象以流的形式进行传输,叫做序列化,该对象所对应的类,必须是实现Serializable接口

对象流也要建立在其它流之上(字节流)

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
package io.demo09;

/*
准备一个长度为10,类型是Hero的数组,使用10个Hero对象初始化该数组
然后把该数组序列化到一个文件heroes.lol
接着使用ObjectInputStream读取该文件,并转换为Hero数组,验证该数组中的内容,是否和序列化之前一样。
*/

import java.io.*;

public class Practice01 {

public static void main(String[] args) {
Hero[] heroes = new Hero[10];
for (int i = 0; i < 10; i++) {
heroes[i] = new Hero();
}
File file = new File("D:\\heroes.lol");
writeObjectStream(heroes, file);
Hero[] heroes1 = readObjectStream(file);
System.out.println(heroes == heroes1);
}

public static void writeObjectStream(Hero[] heroes, File file) {
try (FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
for (Hero hero : heroes) {
oos.writeObject(hero);
}
} catch (IOException e) {
e.printStackTrace();
}
}

public static Hero[] readObjectStream(File file) {
Hero[] heroes = new Hero[10];
try (FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis)) {
int i = 0;
Hero o;
while (true) {
if (null == (o = (Hero) ois.readObject())) {
break;
}
heroes[i] = o;
i++;
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return heroes;
}

}

10. System.in

属于输入流InputStream的子类,可以从控制台输入数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package io;

import java.io.IOException;
import java.io.InputStream;

public class Practice01 {

public static void main(String[] args) {
try (InputStream is = System.in) {
int i = is.read();
System.out.println(i);
} catch (IOException e) {
e.printStackTrace();
}
}

}

10.1 Scanner读取字符串

使用System.in.read虽然可以读取数据,但是很不方便
使用Scanner就可以逐行读取了

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
package io;

/*
自动创建一个有一个类属性的类文件。
通过控制台,获取类名,属性名称,属性类型,根据一个模板文件,自动创建这个类文件,并为属性提供setter和getter
*/

import java.io.*;
import java.util.Scanner;

public class Practice02 {

public static void main(String[] args) {

Scanner sc = new Scanner(System.in);
System.out.println("请输入类的名称:");
String className = sc.next();
System.out.println("请输入属性的类型:");
String type = sc.next();
System.out.println("请输入属性的名称:");
String property = sc.next();

File template = new File("D:\\template.txt");
createClass(className, property, type, template);

}

public static void createClass(String className, String property, String type, File template) {
File targetFile = new File(String.format("D:\\%s.java", className));

// 将property的首字母变为大写Uproperty
char[] chars = property.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
String uProperty = new String(chars);

try (//使用缓冲流
BufferedReader br = new BufferedReader(new FileReader(template));
PrintWriter pw = new PrintWriter(new FileWriter(targetFile))) {
String line;
while (null != (line = br.readLine())) {
if (line.contains("@class@")) {
line = line.replace("@class@", className);
}
if (line.contains("@property@")) {
line = line.replace("@property@", property);
}
if (line.contains("@type@")) {
line = line.replace("@type@", type);
}
if (line.contains("@Uproperty@")) {
line = line.replace("@Uproperty@", uProperty);
}
pw.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("文件保存在:" + targetFile.getAbsolutePath());
}
}


}

template文件

1
2
3
4
5
6
7
8
9
10
11
12
public class @class@ {
public @type@ @property@;
public @class@() {
}
public void set@Uproperty@(@type@ @property@){
this.@property@ = @property@;
}

public @type@ get@Uproperty@(){
return this.@property@;
}
}

11. 练习

11.1 复制文件

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
package io.demo11;

// 复制文件是常见的IO操作,设计方法,实现复制源文件srcFile到destFile

import java.io.*;

public class Practice01 {

public static void main(String[] args) {
File srcFile = new File("D:\\a.txt");
File destFile = new File("D:\\b.txt");
copyFile(srcFile, destFile);
}

public static void copyFile(File srcFile, File destFile) {
try (// 缓存输入流
BufferedReader br = new BufferedReader(new FileReader(srcFile));
// 缓存输出流
PrintWriter pw = new PrintWriter(new FileWriter(destFile))) {
String line;
while (null != (line = br.readLine())) {
pw.println(line);
}
pw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("文件复制完成");
}
}

}

11.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
package io.demo11;

// 复制文件夹,把源文件夹下所有的文件,复制到目标文件夹下(包括子文件夹)

// 流程:使用递归遍历文件夹,遇到文件调用复制文件方法,并获取文件的父目录,在目标文件夹下创建对应的父目录

import java.io.*;

public class Practice02 {

public static void main(String[] args) {
String srcFolder = "D:/MyBlog";
String destFolder = "D:/a";
copyFolder(srcFolder, destFolder);
}

public static void copyFolder(String srcFolder, String destFolder) {
String srcFolderName = srcFolder.split("/")[1];
File src = new File(srcFolder);
goThroughDirectoryAndCopyFile(src, srcFolderName, destFolder);
}

public static void goThroughDirectoryAndCopyFile(File srcFolder, String srcFolderName,
String destFolder) {
for (File file : srcFolder.listFiles()) {
if (file.isDirectory()) {
goThroughDirectoryAndCopyFile(file, srcFolderName, destFolder);
} else {
System.out.println("文件名:" + file.getName() +
", 大小为" + file.length() + "字节");
copyFile(file, srcFolderName, destFolder);
}
}
}

public static void copyFile(File srcFile, String srcFolderName, String destFile) {
// 获取源文件的目录
String srcPath = srcFile.getAbsolutePath();
// 按源文件夹名称进行分割,再合并目标文件夹路径则是目标文件路径
String destPath = destFile + srcPath.split(srcFolderName)[1];
File targetFile = new File(destPath);
if (!targetFile.exists()) {
targetFile.getParentFile().mkdirs();
}
try (BufferedReader br = new BufferedReader(new FileReader(srcFile));
PrintWriter pw = new PrintWriter(new FileWriter(targetFile))) {
String line;
while (null != (line = br.readLine())) {
pw.println(line);
}
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}

}

11.3 查找文件内容

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
package io.demo11;

/*
查找文件内容,假设你的项目目录是e:/project, 遍历这个目录下所有的java文件(包括子文件夹)
找出文件内容包括某个字符串的那些文件,并打印出来。
*/

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class Practice03 {

public static void main(String[] args) {
File folder = new File(
"D:\\Simulation and Calculation Projects\\Java\\BasisKnowledgeCode");
String search = "Practice01";
search(folder, search);
}

public static void search(File folder, String search) {
goThroughDirectory(folder, search);
}

public static void goThroughDirectory(File folder, String search) {
for (File file : folder.listFiles()) {
if (file.isDirectory()) {
goThroughDirectory(file, search);
} else {
// 判断文件是否以.java结尾
if (file.getAbsolutePath().endsWith(".java")) {
searchOneFile(file, search);
}
}
}
}

public static void searchOneFile(File file, String search) {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while (null != (line = br.readLine())) {
if (line.contains(search)) {
System.out.println(String.format("找到子字符串%s,在文件:%s",
search, file.getAbsolutePath()));
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}

}

12. 总结

  • 继承InputStream/OutputStream的,为真正意义上的流
  • 继承Reader/Writer的,是流的处理方式。内部创建了流,解决了编码问题
  • 继承Buffer的,是流的处理方式。使用外部传入的流,解决IO频繁问题。

除了这些流之外,还有很多其它流。大体上使用差不多的,只是在一些特殊场合下用起来更加方便,在工作中用到的时候再进行学习就可以了。

13. 参考