Java笔记11篇原来是写java的输入输出的,但是写着写着发现太乱,没有体系,实在没法理解,所以另写一篇作为总结与复习.

File

常用方法

先复习下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
30
31
32
33
34
35
36
import java.io.File;
import java.util.Date;

public class TestFile {

public static void main(String[] args) {

File f = new File("d:/LOLFolder/LOL.exe");
System.out.println("当前文件是:" +f);
//文件是否存在
System.out.println("判断是否存在:"+f.exists());

//是否是文件夹
System.out.println("判断是否是文件夹:"+f.isDirectory());

//是否是文件(非文件夹)
System.out.println("判断是否是文件:"+f.isFile());

//文件长度
System.out.println("获取文件的长度:"+f.length());

//文件最后修改时间
long time = f.lastModified();
Date d = new Date(time);
System.out.println("获取文件的最后修改时间:"+d);
//设置文件修改时间为1970.1.1 08:00:00
f.setLastModified(0);

//文件重命名
File f2 =new File("d:/LOLFolder/DOTA.exe");
f.renameTo(f2);
System.out.println("把LOL.exe改名成了DOTA.exe");

System.out.println("注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息");
}
}
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
import java.io.File;
import java.io.IOException;

public class TestFile {

public static void main(String[] args) throws IOException {

File f = new File("d:/LOLFolder/skin/garen.ski");

// 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
f.list();

// 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
File[]fs= f.listFiles();

// 以字符串形式返回获取所在文件夹
f.getParent();

// 以文件形式返回获取所在文件夹
f.getParentFile();
// 创建文件夹,如果父文件夹skin不存在,创建就无效
f.mkdir();

// 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹
f.mkdirs();

// 创建一个空文件,如果父文件夹skin不存在,就会抛出异常
f.createNewFile();
// 所以创建一个空文件之前,通常都会创建父目录
f.getParentFile().mkdirs();

// 列出所有的盘符c: d: e: 等等
f.listRoots();

// 刪除文件
f.delete();

// JVM结束的时候,刪除文件,常用于临时文件的删除
f.deleteOnExit();

}
}

练习

1.遍历文件夹

一般说来操作系统都会安装在C盘,所以会有一个 C:\WINDOWS目录。 遍历这个目录下所有的文件(不用遍历子目录) 找出这些文件里,最大的和最小(非0)的那个文件,打印出他们的文件名.

注意题目啊,要求是找文件而不是文件夹,文件夹用length方法结果是0.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.File;
import java.util.Comparator;
import java.util.TreeSet;

/**
* @author YL
*/
public class FileTraverse {
static File f = new File("D:\\test");

public static void main(String[] args) {
File[] fileArray = f.listFiles();
TreeSet<File> fileSet = new TreeSet<>((f1, f2) -> (f1.length() < f2.length()) ? -1 : ((f1.length() == f2.length() ? 0 : 1)));
for (File file : fileArray
) {
fileSet.add(file);
}
System.out.println(f.length());
System.out.println("min is" + fileSet.first() + ' ' + fileSet.first().length());
System.out.println("max is" + fileSet.last() + ' ' + fileSet.last().length());
}

}

不得不说,IDEA真的厉害,上面的是我写的原始代码,它提示我修改为下面的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import java.util.TreeSet;

/**
* @author YL
*/
public class FileTraverse {
static File f = new File("D:\\test");

public static void main(String[] args) {
File[] fileArray = f.listFiles();
TreeSet<File> fileSet = new TreeSet<>(Comparator.comparingLong(File::length));//方法引用
//断言
assert fileArray != null;
fileSet.addAll(Arrays.asList(fileArray));
System.out.println(f.length());
System.out.println("min is" + fileSet.first() + ' ' + fileSet.first().length());
System.out.println("max is" + fileSet.last() + ' ' + fileSet.last().length());
}

}

关于方法引用和断言,我会在写一篇.

下面是不使用容器写的代码:

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 file;

import java.io.File;

public class TestFile {

public static void main(String[] args) {
File f = new File("c:\\windows");
File[] fs = f.listFiles();
if(null==fs)
return;
long minSize = Integer.MAX_VALUE;
long maxSize = 0;
File minFile = null;
File maxFile = null;
for (File file : fs) {
if(file.isDirectory())
continue;
if(file.length()>maxSize){
maxSize = file.length();
maxFile = file;
}
if(file.length()!=0 && file.length()<minSize){
minSize = file.length();
minFile = file;
}
}
System.out.printf("最大的文件是%s,其大小是%,d字节%n",maxFile.getAbsoluteFile(),maxFile.length());
System.out.printf("最小的文件是%s,其大小是%,d字节%n",minFile.getAbsoluteFile(),minFile.length());

}
}

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

import java.io.File;

public class TestFile {

static long minSize = Integer.MAX_VALUE;
static long maxSize = 0;
static File minFile = null;
static File maxFile = null;

//使用递归来遍历一个文件夹的子文件
public static void listFiles(File file){
if(file.isFile()){
if(file.length()>maxSize){
maxSize = file.length();
maxFile = file;
}
if(file.length()!=0 && file.length()<minSize){
minSize = file.length();
minFile = file;
}
return;
}

if(file.isDirectory()){
File[] fs = file.listFiles();
if(null!=fs)
for (File f : fs) {
listFiles(f);
}
}
}

public static void main(String[] args) {
File f = new File("c:\\windows");
listFiles(f);
System.out.printf("最大的文件是%s,其大小是%,d字节%n",maxFile.getAbsoluteFile(),maxFile.length());
System.out.printf("最小的文件是%s,其大小是%,d字节%n",minFile.getAbsoluteFile(),minFile.length());

}
}

输入输出流

体系

这张图表明了流的体系结构,斜体是基类,粗体是节点流.我们从三个方面来划分它们的种类.

  • 输入流和输出流
  • 字节流和字符流
  • 节点流和处理流

以上的具体划分参见Java笔记11那篇.

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

InputStream是字节输入流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。
FileInputStream 是InputStream子类,以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
package stream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream {

public static void main(String[] args) {
try {
//准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66
File f =new File("d:/lol.txt");
//创建基于文件的输入流
FileInputStream fis =new FileInputStream(f);
//创建字节数组,其长度就是文件的长度
byte[] all =new byte[(int) f.length()];
//以字节流的形式读取文件所有内容
fis.read(all);
for (byte b : all) {
//打印出来是65 66
System.out.println(b);
}

//每次使用完流,都应该进行关闭
fis.close();

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

以字节流的形式向文件写入数据

OutputStream是字节输出流,同时也是抽象类,只提供方法声明,不提供方法的具体实现。 FileOutputStream 是OutputStream子类,以FileOutputStream 为例向文件写出数据.

注: 如果文件d:/lol2.txt不存在,写出操作会自动创建该文件。  但是如果是文件d:/xyz/lol2.txt,而目录xyz又不存在,会抛出异常.

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

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

public class TestStream {

public static void main(String[] args) {
try {
// 准备文件lol2.txt其中的内容是空的
File f = new File("d:/lol2.txt");
// 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Y
byte data[] = { 88, 89 };

// 创建基于文件的输出流
FileOutputStream fos = new FileOutputStream(f);
// 把数据写入到输出流
fos.write(data);
// 关闭输出流
fos.close();

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

练习一

1.拆分文件

找到一个大于100k的文件,按照100k为单位,拆分成多个子文件,并且以编号作为文件名结束。 比如文件 eclipse.exe,大小是309k。 拆分之后,成为

eclipse.exe-0

eclipse.exe-1

eclipse.exe-2

eclipse.exe-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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package stream;

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

public class TestStream {

public static void main(String[] args) {
int eachSize = 100 * 1024; // 100k
File srcFile = new File("d:/eclipse.exe");
splitFile(srcFile, eachSize);
}

/**
* 拆分的思路,先把源文件的所有内容读取到内存中,然后从内存中挨个分到子文件里
* @param srcFile 要拆分的源文件
* @param eachSize 按照这个大小,拆分
*/
private static void splitFile(File srcFile, int eachSize) {

if (0 == srcFile.length())
throw new RuntimeException("文件长度为0,不可拆分");

byte[] fileContent = new byte[(int) srcFile.length()];
// 先把文件读取到数组中
try {
FileInputStream fis = new FileInputStream(srcFile);
fis.read(fileContent);
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 计算需要被划分成多少份子文件
int fileNumber;
// 文件是否能被整除得到的子文件个数是不一样的
// (假设文件长度是25,每份的大小是5,那么就应该是5个)
// (假设文件长度是26,每份的大小是5,那么就应该是6个)
if (0 == fileContent.length % eachSize)
fileNumber = (int) (fileContent.length / eachSize);
else
fileNumber = (int) (fileContent.length / eachSize) + 1;

for (int i = 0; i < fileNumber; i++) {
String eachFileName = srcFile.getName() + "-" + i;
File eachFile = new File(srcFile.getParent(), eachFileName);
byte[] eachContent;

// 从源文件的内容里,复制部分数据到子文件
// 除开最后一个文件,其他文件大小都是100k
// 最后一个文件的大小是剩余的
if (i != fileNumber - 1) // 不是最后一个
eachContent = Arrays.copyOfRange(fileContent, eachSize * i, eachSize * (i + 1));
else // 最后一个
eachContent = Arrays.copyOfRange(fileContent, eachSize * i, fileContent.length);

try {
// 写出去
FileOutputStream fos = new FileOutputStream(eachFile);
fos.write(eachContent);
// 记得关闭
fos.close();
System.out.printf("输出子文件%s,其大小是 %d字节%n", eachFile.getAbsoluteFile(), eachFile.length());
} catch (IOException e) {
// TODO Auto-generated catch block
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
package stream;

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

import javax.security.auth.DestroyFailedException;

public class TestStream {

public static void main(String[] args) {
murgeFile("d:/", "eclipse.exe");
}

/**
* 合并的思路,就是从eclipse.exe-0开始,读取到一个文件,就开始写出到 eclipse.exe中,直到没有文件可以读
* @param folder
* 需要合并的文件所处于的目录
* @param fileName
* 需要合并的文件的名称
* @throws FileNotFoundException
*/
private static void murgeFile(String folder, String fileName) {

try {
// 合并的目标文件
File destFile = new File(folder, fileName);
FileOutputStream fos = new FileOutputStream(destFile);
int index = 0;
while (true) {
//子文件
File eachFile = new File(folder, fileName + "-" + index++);
//如果子文件不存在了就结束
if (!eachFile.exists())
break;

//读取子文件的内容
FileInputStream fis = new FileInputStream(eachFile);
byte[] eachContent = new byte[(int) eachFile.length()];
fis.read(eachContent);
fis.close();

//把子文件的内容写出去
fos.write(eachContent);
fos.flush();
System.out.printf("把子文件 %s写出到目标文件中%n",eachFile);
}

fos.close();
System.out.printf("最后目标文件的大小:%,d字节" , destFile.length());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

关闭流

在try中关闭

在try的作用域里关闭文件输入流,在前面的练习中都是使用这种方式,这样做有一个弊端; 如果文件不存在,或者读取的时候出现问题而抛出异常,那么就不会执行这一行关闭流的代码,存在巨大的资源占用隐患.不推荐使用.

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 stream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream {

public static void main(String[] args) {
try {
File f = new File("d:/lol.txt");
FileInputStream fis = new FileInputStream(f);
byte[] all = new byte[(int) f.length()];
fis.read(all);
for (byte b : all) {
System.out.println(b);
}
// 在try 里关闭流
fis.close();
} catch (IOException e) {
e.printStackTrace();
}

}
}

在finally中关闭

这是标准的关闭流的方式

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

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream {

public static void main(String[] args) {
File f = new File("d:/lol.txt");
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
byte[] all = new byte[(int) f.length()];
fis.read(all);
for (byte b : all) {
System.out.println(b);
}

} catch (IOException e) {
e.printStackTrace();
} finally {
// 在finally 里关闭流
if (null != fis)
try {

fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}
}

使用try()的方式

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

所有的流,都实现了一个接口叫做 AutoCloseable,任何类实现了这个接口,都可以在try()中进行实例化.并且在try, catch, finally结束的时候自动关闭,回收相关资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package stream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class TestStream {

public static void main(String[] args) {
File f = new File("d:/lol.txt");

//把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
try (FileInputStream fis = new FileInputStream(f)) {
byte[] all = new byte[(int) f.length()];
fis.read(all);
for (byte b : all) {
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
}

}
}

使用字符流读取文件

FileReader是Reader子类,以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
package stream;

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

public class TestStream {

public static void main(String[] args) {
// 准备文件lol.txt其中的内容是AB
File f = new File("d:/lol.txt");
// 创建基于文件的Reader
try (FileReader fr = new FileReader(f)) {
// 创建字符数组,其长度就是文件的长度
char[] all = new char[(int) f.length()];
// 以字符流的形式读取文件所有内容
fr.read(all);
for (char b : all) {
// 打印出来是A B
System.out.println(b);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

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

FileWriter是Writer的子类,以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
package stream;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class TestStream {

public static void main(String[] args) {
// 准备文件lol2.txt
File f = new File("d:/lol2.txt");
// 创建基于文件的Writer
try (FileWriter fr = new FileWriter(f)) {
// 以字符流的形式把数据写入到文件中
String data="abcdefg1234567890";
char[] cs = data.toCharArray();
fr.write(cs);

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

练习二

1.加密文件

准备一个文本文件(非二进制),其中包含ASCII码的字符和中文字符。

设计一个方法:

public static void encodeFile(File encodingFile, File encodedFile);

在这个方法中把encodingFile的内容进行加密,然后保存到encodedFile文件中。

加密算法:

  1. 数字:
  • 如果不是9的数字,在原来的基础上加1,比如5变成6, 3变成4
  • 如果是9的数字,变成0
  1. 字母字符:
  • 如果是非z字符,向右移动一个,比如d变成e, G变成H
  • 如果是z,z->a, Z-A。
  • 字符需要保留大小写
  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
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
package stream;

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

public class TestStream {
/**
*
* @param encodingFile
* 被加密的文件
* @param encodedFile
* 加密后保存的位置
*/
public static void encodeFile(File encodingFile, File encodedFile) {

try (FileReader fr = new FileReader(encodingFile); FileWriter fw = new FileWriter(encodedFile)) {
// 读取源文件
char[] fileContent = new char[(int) encodingFile.length()];
fr.read(fileContent);
System.out.println("加密前的内容:");
System.out.println(new String(fileContent));

// 进行加密
encode(fileContent);
// 把加密后的内容保存到目标文件
System.out.println("加密后的内容:");
System.out.println(new String(fileContent));

fw.write(fileContent);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

private static void encode(char[] fileContent) {
for (int i = 0; i < fileContent.length; i++) {
char c = fileContent[i];
if (isLetterOrDigit(c)) {
switch (c) {
case '9':
c = '0';
break;
case 'z':
c = 'a';
break;
case 'Z':
c = 'A';
break;
default:
c++;
break;
}
}
fileContent[i] = c;
}
}

public static boolean isLetterOrDigit(char c) {
// 不使用Character类的isLetterOrDigit方法是因为,中文也会被判断为字母
String letterOrDigital = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
return -1 == letterOrDigital.indexOf(c) ? false : true;
}

public static void main(String[] args) {
File encodingFile = new File("E:/project/j2se/src/Test1.txt");
File encodedFile = new File("E:/project/j2se/src/Test2.txt");
encodeFile(encodingFile, encodedFile);
}
}

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

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

public class TestStream {

/**
*
* @param decodingFile
* 被解密的文件
* @param decodedFile
* 解密后保存的位置
*/
public static void decodeFile(File decodingFile, File decodedFile) {

try (FileReader fr = new FileReader(decodingFile); FileWriter fw = new FileWriter(decodedFile)) {
// 读取源文件
char[] fileContent = new char[(int) decodingFile.length()];
fr.read(fileContent);
System.out.println("源文件的内容:");
System.out.println(new String(fileContent));
// 进行解密
decode(fileContent);
System.out.println("解密后的内容:");
System.out.println(new String(fileContent));
// 把解密后的内容保存到目标文件
fw.write(fileContent);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

private static void decode(char[] fileContent) {
for (int i = 0; i < fileContent.length; i++) {
char c = fileContent[i];
if (isLetterOrDigit(c)) {
switch (c) {
case '0':
c = '9';
break;
case 'a':
c = 'z';
break;
case 'A':
c = 'Z';
break;
default:
c--;
break;
}
}
fileContent[i] = c;
}
}

public static boolean isLetterOrDigit(char c) {
// 不使用Character类的isLetterOrDigit方法是因为,中文也会被判断为字母
String letterOrDigital ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
return -1 == letterOrDigital.indexOf(c) ? false : true;
}

public static void main(String[] args) {
File decodingFile = new File("E:/project/j2se/src/Test2.txt");
File decodedFile = new File("E:/project/j2se/src/Test1.txt");

decodeFile(decodingFile, decodedFile);

}
}

编码问题

编码问题真的是非常烦,我总结了三点注意事项:

  • 注意环境的默认编码

    这里的环境指的的是你的IDE默认编码,读取的文件的编码,保存时的编码,总之要做到对于文件会是什么编码要心里有数

  • 搞清打开文件要用什么编码

  • 将上面两者对应起来

更多编码问题参见连接:http://how2j.cn/k/io/io-encoding/695.html#step2489

缓存流

按照前面的划分:

  • 输入流和输出流
  • 字节流和字符流
  • 节点流和处理流

已经从输入输出角度和字符字节角度讲了一些,而节点流是连接物理节点的,处理流是连接节点流的.还有和特殊类别是缓存流.先看看一个通俗解释:

flush本意是冲刷,这个方法大概取自它引申义冲马桶的意思,马桶有个池子,你往里面扔东西,会暂时保存在池子里,只有你放水冲下去,东西才会进入下水道。同理很多流都有一个这样的池子,专业术语叫缓冲区,当你print或者write的时候,会暂时保存在缓冲区,并没有发送出去,这是出于效率考虑的,因为数据不会自己发送过去,必须有其他机制,而且这个很消耗资源,就像马桶你需要很多水,才能冲走,你如果扔一点东西,就冲一次水,那你水费要爆表了,同样如果你写一行文字,或者一个字节,就要马上发送出去,那网络流量,CPU使用率等等都要爆表了,所以一般只有在你真正需要发送否则无法继续的时候,调用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
package stream;

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

public class TestStream {

public static void main(String[] args) {
// 准备文件lol.txt其中的内容是
// garen kill teemo
// teemo revive after 1 minutes
// teemo try to garen, but killed again
File f = new File("d:/lol.txt");
// 创建文件字符流
// 缓存流必须建立在一个存在的流的基础上
try (
FileReader fr = new FileReader(f);
BufferedReader br = new BufferedReader(fr);
)
{
while (true) {
// 一次读一行
String line = br.readLine();
if (null == line)
break;
System.out.println(line);
}
} catch (IOException e) {
// TODO Auto-generated catch block
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
package stream;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class TestStream {

public static void main(String[] args) {
// 向文件lol2.txt中写入三行语句
File f = new File("d:/lol2.txt");

try (
// 创建文件字符流
FileWriter fw = new FileWriter(f);
// 缓存流必须建立在一个存在的流的基础上
PrintWriter pw = new PrintWriter(fw);
) {
pw.println("garen kill teemo");
pw.println("teemo revive after 1 minutes");
pw.println("teemo try to garen, but killed again");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

数据流

为什么要用数据流?当向某个文件连续写入数字1,2,3时,如果用普通的输出流,就必须认为加上一些标识符(例如’,’用来分割数据,不至于变成123).同样的为了正确取出数据,在读入后还需要将这些标志性的字符去掉.而使用数据流就可以避免这些操作.

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

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class TestStream {

public static void main(String[] args) {
write();
read();
}

private static void read() {
File f =new File("d:/lol.txt");
try (
FileInputStream fis = new FileInputStream(f);
DataInputStream dis =new DataInputStream(fis);
){
boolean b= dis.readBoolean();
int i = dis.readInt();
String str = dis.readUTF();

System.out.println("读取到布尔值:"+b);
System.out.println("读取到整数:"+i);
System.out.println("读取到字符串:"+str);

} catch (IOException e) {
e.printStackTrace();
}

}

private static void write() {
File f =new File("d:/lol.txt");
try (
FileOutputStream fos = new FileOutputStream(f);
DataOutputStream dos =new DataOutputStream(fos);
){
dos.writeBoolean(true);
dos.writeInt(300);
dos.writeUTF("123 this is gareen");
} catch (IOException e) {
e.printStackTrace();
}

}
}

有几点要注意:

  • DataOutputStream的文件要DataInputStream来打开,否则会产生异常
  • 注意写入和读取时的顺序

对象流

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

练习

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

1
2
3
4
5
6
7
8
9
10
11
import java.io.Serializable;

/**
* @author YL
*/
class Hero implements Serializable {
private static final long serialVersionUID = 1L;
String name;
float hp;
}

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
import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
* @author YL
*/
public class SerialFileTest {
public static void main(String[] args) {
int capacity = 10;
List<Hero> heroes = new ArrayList<>(capacity);
for (int i = 0; i < capacity; i++) {
String n = "hero" + String.valueOf(i);
int h = 616 + i;
Hero hero = new Hero() {{
name = n;
hp = h;
}};
heroes.add(hero);
}

File f = new File("./src/heroes.lol");
try (FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(heroes);
} catch (IOException e) {
e.printStackTrace();
}

try (FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis)) {
List<Hero> list = (List<Hero>) ois.readObject();
for (Hero hero : list) {
System.out.println(hero.name);
System.out.println(hero.hp);
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

标准输入输出

System.in的定义是public static final InputStream in = null;,而System.out定义是public static final PrintStream out = null;,所以标准输入输出是字节流.

运用:

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
import java.io.*;
import java.util.Scanner;

/**
* @author YL
*/
public class ObjectCreate {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
System.out.println("请输入类名:");
String className = sc.nextLine();
System.out.println("请输入属性类别:");
String property = sc.nextLine();
System.out.println("请输入属性名称:");
String name = sc.nextLine();
getFile(className, property, name);
System.out.println("名为" + className + ".java的文件已生成,存放在d:/" + className + ".java");
}

public static void getFile(String className, String property, String name) {
File outFile = new File("./src/" + className + ".java");
File inFile = new File("./src/model.txt");
try (FileReader fr = new FileReader(inFile);
BufferedReader br = new BufferedReader(fr)
) {
StringBuilder read = new StringBuilder();
while (true) {
String s = br.readLine();
if (s == null) {
break;
}
read.append(s).append("\r\n");
}
System.out.println(read);
System.out.println("这里是分割线-----------------------------------------");
String write = read.toString().replaceAll("@class@", className);
write = write.replaceAll("@type@", property);
write = write.replaceAll("@property@", name);
char[] c = name.toCharArray();
if (c[0] >= 'a' && c[0] <= 'z') {
c[0] = (char) (c[0] - 32);
}
name = new String(c);
write = write.replaceAll("@Uproperty@", name);
System.out.println(write);
FileWriter fileWriter = new FileWriter(outFile);
BufferedWriter bw = new BufferedWriter(fileWriter);
bw.write(write);
br.close();
bw.flush();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

本篇中有不少东西没讲,包括RandomAccessFile,NIO,NIO.2,对象序列化都没讲,所以这部分会另开一篇.