Java-IO,IO流,数据即是水流

时人不识凌云木,直待凌云始道高

什么是IO

把数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入input输出output ,即流向内存是输入流,流出内存的输出流。

Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。

IO的分类

根据数据的流向分为:输入流输出流

  • 输入流 :把数据从其他设备上读取到内存中的流。
  • 输出流 :把数据从内存 中写出到其他设备上的流。

格局数据的类型分为:字节流字符流

  • 字节流 :以字节为单位,读写数据的流。
  • 字符流 :以字符为单位,读写数据的流。

顶级父类

输入流 输出流
字节流 字节输入流 InputStream 字节输出流 OutputStream
字符流 字符输入流 Reader 字符输出流 Writer

字节流

一切皆为字节

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

文件字节输出流 FileOutputStream

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

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

public class ByteStreamOutputDemo1 {
public static void main(String[] args) throws IOException {
/**
* 字符输出流的细节:
* 1.创建字符输出流对象
* 细节1:参数是字符串表示的路径或者是File对象都是可以的
* 细节2:如果文件不存在,会创建一个新的文件,但是需要保证父级路径是存在的
* 细节3:如果文件已经存在,则会清空文件
* 2.写数据
* 细节:write方法的参数是整数,但是实际上写到本地文件中的是整数在ACSII码表上对应的字符
* 3.释放资源
* 每次使用完流后都要释放资源,如果不释放资源,会一直占用文件资源,不能进行其他操作,如删除
*/
FileOutputStream fos = new FileOutputStream("D:\\Code\\Java\\SE\\aaa.txt");
fos.write(123);
fos.close();
}
}

写数据的三种方式

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

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

public class ByteStreamOutputDemo2 {
public static void main(String[] args) throws IOException {
// void write(int b) 一次写一个字节数据
// void write(byte[] b) 一次写一个字节数组数据
// void write(byte[] b,int off,int len) 一次写一个字节数组的部分数据
/**
* 方法三:
* 参数1:数组
* 参数2:起始索引 0
* 参数3:个数
*/

FileOutputStream fos = new FileOutputStream("D:\\Code\\Java\\SE\\aaa.txt");
// 方法1
fos.write(97);
// 方法2
byte[] bytes1 = {98, 99, 100, 101};
fos.write(bytes1);
// 方法3
byte[] bytes2 = {102, 103, 104};
fos.write(bytes2, 0, 3);

fos.close();
}
}

写入数据的三个问题

换行写、续写
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
package io;

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

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


FileOutputStream fos = new FileOutputStream("D:\\Code\\Java\\SE\\aaa.txt", true);
/*
换行写:
再次写出一个换行符即可
windows:\r\n 回车换行
linux:\n
mac:\r
细节:
在windows操作系统中,java对回车换行进行了优化。
虽然完整的是\r\n,但是写其中的一个也可以
java底层会补全
续写:
创建对象的时候打开续写开关
创建对象的第二个参数,默认false,关闭续写

*/

String str = "ajiehaoshuai\r\n666\r\n";
// 将字符串转换为字节对象
byte[] strBytes = str.getBytes();
fos.write(strBytes);


fos.close();
}
}

文件字节输入流 FileInputStream

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
  • public abstract int read(): 从输入流读取数据的下一个字节。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
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
package io;

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

public class ByteInputStreamDemo1 {
public static void main(String[] args) throws IOException {
/*
字节输入流的细节
1.创建字节输入流:
细节1:如果文件不存在,就会直接报错,没有数据,不会创建文件夹

2.写数据
细节1:一次读一个字节,读出来的数据在ACSII上对应的数字
细节2:读到文件末尾了,read方法返回-1
3.释放资源
*/


FileInputStream fis = new FileInputStream("D:\\Code\\Java\\SE\\aaa.txt");

// while (true) {
// int read = fis.read();
// if (read == -1) break;
// System.out.println((char) read);
//
// }

int read;
while ((read = fis.read()) != -1) {
System.out.print((char) read);
}
fis.close();
}
}

文件拷贝1.0

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

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

public class ByteFileCopy {
public static void main(String[] args) throws IOException {
long begin = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("D:\\Code\\Java\\SE\\aaa.txt");
FileOutputStream fos = new FileOutputStream("C:\\Users\\任俊杰\\Desktop\\课设2\\bbb.txt");
int b;
while ((b = fis.read())!=-1){
fos.write(b);
}
// 先开的资源后关
fos.close();
fis.close();
// 计算运行时间
long end = System.currentTimeMillis();
System.out.println(end-begin);
}
}

一次读多个字节

注意:一次读取多个字节的数据(字节数组),当读取的数据不能填满字节数组的时候,就会出现错误

错误是:当字节数组读不满发生时,字节数组中的内容仍是上一次读满的数据,然后最后一次读不满的情况发生时,会将只读取一部分的数据替换到字节数组中,字节数组这时就变成了:最后一次读的部分数据+上一次读的未被覆盖的部分数据

细节:fis.read()方法 返回的是-1和读到的数据

而fis.read(bytes)方法 返回的是-1和读到的数据个数

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

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

public class ByteInputStreamDemo2 {
public static void main(String[] args) throws IOException {
// 一次读取多个字节数据
FileInputStream fis = new FileInputStream("D:\\Code\\Java\\SE\\aaa.txt");
// 字节数组,存放每次读取的数据,数组长度决定了一次能读取多少个字节
byte[] bytes = new byte[2];

int readLen1 = fis.read(bytes);
System.out.println(readLen1);
// 该方法可以将字节数组的一部分变为字符串,意为从0开始,变readLen个,readLen为读取到的字节个数,解决了读不满字节数组的问题
String str1 = new String(bytes,0,readLen1);
System.out.println(str1);


int readLen2 = fis.read(bytes);
System.out.println(readLen2);
String str2 = new String(bytes,0,readLen2);
System.out.println(str2);


int readLen3 = fis.read(bytes);
System.out.println(readLen3);
String str3 = new String(bytes,0,readLen3);
System.out.println(str3);

fis.close();

}
}

文件拷贝2.0

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

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

public class ByteFileCopy2 {
public static void main(String[] args) throws IOException {
// 多字节拷贝

FileInputStream fis = new FileInputStream("D:\\Code\\Java\\SE\\aaa.txt");
FileOutputStream fos = new FileOutputStream("C:\\Users\\任俊杰\\Desktop\\课设2\\bbb.txt");

int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes,0,len);
}

fos.close();
fis.close();
}
}

异常处理

IO异常的处理

之前一直把异常抛出,而实际开发中并不能这样处理,建议使用`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
public class HandleException1 {
public static void main(String[] args) {
// 声明变量
FileWriter fw = null;
try {
//创建流对象
fw = new FileWriter("fw.txt");
// 写出数据
fw.write("黑马程序员"); //黑马程序员
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

字符集

GBK



为了区分英文和中文的二进制编码,所以高位字节一定是以1开头

Unicode

为了节省空间,使用UTF-8编码方式


乱码





编码与解码

字符流

文件字符输入流 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
package io;

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

public class FileReaderDemo1 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("D:\\Code\\Java\\SE\\aaa.txt");

// 字符流的底层也是字节流,默认也是一个一个字节读取的
// 如果遇到中文就会一次读取多个,GBK一次读取两个,UTF-8一次读取三个

// read()细节
// 1.默认是一个一个字节读取的,如果遇到中文就会一次读取多个
// 2.在读取之后,方法的底层还会进行编码并转成十进制,最终把这个十进制作为返回值,十进制的数据也表示字符集上的数字

// 如果想看到中文汉字,将十进制数据进行强转,就可以看到原数据
int ch;
while ((ch = fr.read()) != -1) {
System.out.println((char) ch);
}

fr.close();
}
}

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

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("D:\\Code\\Java\\SE\\aaa.txt");

char[] chars = new char[2];
int len;
//
// read(chars):读取数据,解码,强转三部合并,把强转之后的字符放到数组当中
// 空参的read+强转类型转换


while ((len = fr.read(chars))!=-1){
// 把数组中的数据变成字符串再进行打印
System.out.println(new String(chars,0,len));
}

fr.close();

}
}

原理解析

创建字符输入流对象的时候,底层会关联文件,并创建缓冲区(长度为8192的字节数组)

字符流也有缓冲区,read()先到缓冲区中判断是否有数据、读取数据,如果没有,就会从文件中读取数据,尽可能装满缓冲区,然后再从缓冲区中读取数据

缓冲区最多存放8192字节的数据,当读完缓冲区中的数据,会再去文件中获取数据覆盖到缓冲区

文件字符输出流 FileWriter



原理解析

拥有缓冲区,将数据写入到缓冲区
  1. 当缓冲区写满,自动保存到目的地
  2. 当刷新的时候,会将缓冲区的数据写入目的地
  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
public class Test01 {
public static void main(String[] args) throws IOException {
//拷贝一个文件夹,考虑子文件夹

//1.创建对象表示数据源
File src = new File("D:\\aaa\\src");
//2.创建对象表示目的地
File dest = new File("D:\\aaa\\dest");

//3.调用方法开始拷贝
copydir(src,dest);



}

/*
* 作用:拷贝文件夹
* 参数一:数据源
* 参数二:目的地
*
* */
private static void copydir(File src, File dest) throws IOException {
dest.mkdirs();
//递归
//1.进入数据源
File[] files = src.listFiles();
//2.遍历数组
for (File file : files) {
if(file.isFile()){
//3.判断文件,拷贝
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest,file.getName()));
byte[] bytes = new byte[1024];
int len;
while((len = fis.read(bytes)) != -1){
fos.write(bytes,0,len);
}
fos.close();
fis.close();
}else {
//4.判断文件夹,递归
copydir(file, new File(dest,file.getName()));
}
}
}
}

文件加密

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 Test02 {
public static void main(String[] args) throws IOException {
/*
为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理。
加密原理:
对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中。
解密原理:
读取加密之后的文件,按照加密的规则反向操作,变成原始文件。

^ : 异或
两边相同:false
两边不同:true

0:false
1:true

100:1100100
10: 1010

1100100
^ 0001010
__________
1101110
^ 0001010
__________
1100100

*/
}

public static void encryptionAndReduction(File src, File dest) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);
int b;
while ((b = fis.read()) != -1) {
fos.write(b ^ 2);
}
//4.释放资源
fos.close();
fis.close();
}


}

数字排序

文本文件中有以下的数据:
            2-1-9-4-7-8

将文件中的数据进行排序,变成以下的数据:

            1-2-4-7-8-9
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 Test03 {
public static void main(String[] args) throws IOException {
/*
文本文件中有以下的数据:
2-1-9-4-7-8
将文件中的数据进行排序,变成以下的数据:
1-2-4-7-8-9
*/


//1.读取数据
FileReader fr = new FileReader("myio\\a.txt");
StringBuilder sb = new StringBuilder();
int ch;
while((ch = fr.read()) != -1){
sb.append((char)ch);
}
fr.close();
System.out.println(sb);
//2.排序
String str = sb.toString();
String[] arrStr = str.split("-");//2-1-9-4-7-8

ArrayList<Integer> list = new ArrayList<>();
for (String s : arrStr) {
int i = Integer.parseInt(s);
list.add(i);
}
Collections.sort(list);
System.out.println(list);
//3.写出
FileWriter fw = new FileWriter("myio\\a.txt");
for (int i = 0; i < list.size(); i++) {
if(i == list.size() - 1){
fw.write(list.get(i) + "");
}else{
fw.write(list.get(i) + "-");
}
}
fw.close();
}
}
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 Test04 {
public static void main(String[] args) throws IOException {
/*
文本文件中有以下的数据:
2-1-9-4-7-8
将文件中的数据进行排序,变成以下的数据:
1-2-4-7-8-9

细节1:
文件中的数据不要换行

细节2:
bom头
*/
//1.读取数据
FileReader fr = new FileReader("myio\\a.txt");
StringBuilder sb = new StringBuilder();
int ch;
while((ch = fr.read()) != -1){
sb.append((char)ch);
}
fr.close();
System.out.println(sb);
//2.排序
Integer[] arr = Arrays.stream(sb.toString()
.split("-"))
.map(Integer::parseInt)
.sorted()
.toArray(Integer[]::new);
//3.写出
FileWriter fw = new FileWriter("myio\\a.txt");
String s = Arrays.toString(arr).replace(", ","-");
String result = s.substring(1, s.length() - 1);
fw.write(result);
fw.close();
}
}

缓冲流

字节缓冲流

读写数据仍然用到基本流,缓冲流提高了读写效率

默认缓冲区的大小是8192字节

不需要关闭基本流的原因是:缓冲流底层会关闭基本流

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

import java.io.*;

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


BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Code\\Java\\SE\\shi.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\Code\\Java\\SE\\shiCopy.txt"));

int data;
while ((data = bis.read()) != -1) {
bos.write(data);
}

bos.close();
bis.close();


}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 一次读写一个字节数组
package io;

import java.io.*;

public class BufferedStreamCopy2 {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Code\\Java\\SE\\shi.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\Code\\Java\\SE\\shiCopy2.txt"));

byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes))!= -1){
bos.write(bytes,0,len);
}

bos.close();
bis.close();
}
}

字节缓冲流提高效率的原理


缓冲区中的数据在内存中操作的时间非常快,节约了数据从硬盘到内存的时间

字符缓冲流

字符流底层自带了长度为8192的字符数组缓冲区提高性能,16KB


readLine()方法读取的时候,一次读取一整行,遇到回车换行结束,不会将回车读到内存当中

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

import java.io.*;

public class BufferedInputStreamDemo1 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("D:\\Code\\Java\\SE\\shi.txt"));

String line =br.readLine();
System.out.println(line);

br.close();
}
}

转换流

jdk11之前指定字符集需要用到转换流,jdk11以后字符流可以创建对象指定字符集

转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

在IDEA中,使用FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。

作用:

  1. 字节流想要使用字符流中的方法

构造方法:

InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。

InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

细节:

指定字符集淘汰(JDK11以后出现了替代方法),JDK11以后字符流创建对象的时候可以指定字符集,Reader和Writer相同

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 ReaderDemo2 {
public static void main(String[] args) throws IOException {
// 定义文件路径,文件为gbk编码
String FileName = "E:\\file_gbk.txt";
// 创建流对象,默认UTF8编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(FileName));
// 创建流对象,指定GBK编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(FileName) , "GBK");
// 定义变量,保存字符
int read;
// 使用默认编码字符流读取,乱码
while ((read = isr.read()) != -1) {
System.out.print((char)read); // ��Һ�
}
isr.close();

// 使用指定编码字符流读取,正常解析
while ((read = isr2.read()) != -1) {
System.out.print((char)read);// 大家好
}
isr2.close();
}
}

将GBK编码的文本文件,转换为UTF-8编码的文本文件。

  1. 指定GBK编码的转换流,读取文本文件。
  2. 使用UTF-8编码的转换流,写出文本文件。
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
public class TransDemo {
public static void main(String[] args) {
// 1.定义文件路径
String srcFile = "file_gbk.txt";
String destFile = "file_utf8.txt";
// 2.创建流对象
// 2.1 转换输入流,指定GBK编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile) , "GBK");
// 2.2 转换输出流,默认utf8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
// 3.读写数据
// 3.1 定义数组
char[] cbuf = new char[1024];
// 3.2 定义长度
int len;
// 3.3 循环读取
while ((len = isr.read(cbuf))!=-1) {
// 循环写出
osw.write(cbuf,0,len);
}
// 4.释放资源
osw.close();
isr.close();
}
}

序列/反序列化流

序列化流

Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该对象的数据对象的类型对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。

反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象。


一个对象要想序列化,必须满足两个条件:

  • 该类必须实现java.io.Serializable 接口,Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
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
package io;

import java.io.Serializable;

public class Student implements Serializable {

private String name;
private int age;

public Student() {
}

public Student(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

反序列化

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

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class ObjectStreamDemo {
public static void main(String[] args) throws IOException {
Student stu1 = new Student("zhangsan", 23);
// 创建序列化流的对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Code\\Java\\SE\\xu.txt"));
oos.writeObject(stu1);
oos.close();
}
}

反序列化流

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

import java.io.*;

public class ObjectStreamDemo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Code\\Java\\SE\\xu.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Code\\Java\\SE\\fan.txt"));

Object o = ois.readObject();
String s = o.toString();
bw.write(s);
bw.newLine();

bw.close();
ois.close();
}
}


当序列化一个对象时,将Java进行修改,反序列化这个对象的时候,就会出现一个版本号报错。真实业务场景中,需求发生变更,需要修改javabean类,此时需要固定版本号。



打印流

平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。


字节打印流



字符打印流



打印流与System.out.println()关系

System是一个最终类,没有子类 .out是获取的打印流的对象,默认指向控制台



System.out就是PrintStream类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,我们就可以玩一个”小把戏”,改变它的流向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PrintDemo {
public static void main(String[] args) throws IOException {
// 调用系统的打印流,控制台直接输出97
System.out.println(97);

// 创建打印流,指定文件的名称
PrintStream ps = new PrintStream("ps.txt");

// 设置系统的打印流流向,输出到ps.txt
System.setOut(ps);
// 调用系统的打印流,ps.txt中输出97
System.out.println(97);
}
}

解压缩流/压缩流

解压缩流

压缩包中的每一个文件都是一个ZipEntry对象

解压的本质:就是把每一个ZipEntry按照层级拷贝到本地另一个文件夹中

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 ZipStreamDemo1 {
public static void main(String[] args) throws IOException {

//1.创建一个File表示要解压的压缩包
File src = new File("D:\\aaa.zip");
//2.创建一个File表示解压的目的地
File dest = new File("D:\\");

//调用方法
unzip(src,dest);

}

//定义一个方法用来解压
public static void unzip(File src,File dest) throws IOException {
//解压的本质:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中
//创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));

//要先获取到压缩包里面的每一个zipentry对象
//表示当前在压缩包中获取到的文件或者文件夹
ZipEntry entry;
while((entry = zip.getNextEntry()) != null){
System.out.println(entry);
if(entry.isDirectory()){
//文件夹:需要在目的地dest处创建一个同样的文件夹
File file = new File(dest,entry.toString());
file.mkdirs();
}else{
//文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
FileOutputStream fos = new FileOutputStream(new File(dest,entry.toString()));
int b;
while((b = zip.read()) != -1){
//写到目的地
fos.write(b);
}
fos.close();
//表示在压缩包中的一个文件处理完毕了。
zip.closeEntry();
}
}
zip.close();
}
}

压缩流

压缩的本质是:将每一个文件、文件夹看成一个ZipEntry对象放到压缩包中
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
public class ZipStreamDemo2 {
public static void main(String[] args) throws IOException {
/*
* 压缩流
* 需求:
* 把D:\\a.txt打包成一个压缩包
* */
//1.创建File对象表示要压缩的文件
File src = new File("D:\\a.txt");
//2.创建File对象表示压缩包的位置
File dest = new File("D:\\");
//3.调用方法用来压缩
toZip(src,dest);
}

/*
* 作用:压缩
* 参数一:表示要压缩的文件
* 参数二:表示压缩包的位置
* */
public static void toZip(File src,File dest) throws IOException {
//1.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
//2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
//参数:压缩包里面的路径
ZipEntry entry = new ZipEntry("aaa\\bbb\\a.txt");
//3.把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
//4.把src文件中的数据写到压缩包当中
FileInputStream fis = new FileInputStream(src);
int b;
while((b = fis.read()) != -1){
zos.write(b);
}
zos.closeEntry();
zos.close();
}
}

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
public class ZipStreamDemo3 {
public static void main(String[] args) throws IOException {
/*
* 压缩流
* 需求:
* 把D:\\aaa文件夹压缩成一个压缩包
* */
//1.创建File对象表示要压缩的文件夹
File src = new File("D:\\aaa");
//2.创建File对象表示压缩包放在哪里(压缩包的父级路径)
File destParent = src.getParentFile();//D:\\
//3.创建File对象表示压缩包的路径
File dest = new File(destParent,src.getName() + ".zip");
//4.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
//5.获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
toZip(src,zos,src.getName());//aaa
//6.释放资源
zos.close();
}

/*
* 作用:获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
* 参数一:数据源
* 参数二:压缩流
* 参数三:压缩包内部的路径
* */
public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
//1.进入src文件夹
File[] files = src.listFiles();
//2.遍历数组
for (File file : files) {
if(file.isFile()){
//3.判断-文件,变成ZipEntry对象,放入到压缩包当中
ZipEntry entry = new ZipEntry(name + "\\" + file.getName());//aaa\\no1\\a.txt
zos.putNextEntry(entry);
//读取文件中的数据,写到压缩包
FileInputStream fis = new FileInputStream(file);
int b;
while((b = fis.read()) != -1){
zos.write(b);
}
fis.close();
zos.closeEntry();
}else{
//4.判断-文件夹,递归
toZip(file,zos,name + "\\" + file.getName());
// no1 aaa \\ no1
}
}
}
}

Commons-io

Commons是apache开源基金组织提供的工具包,里面有很多帮助我们提高开发效率的API

比如:

StringUtils   字符串工具类

NumberUtils   数字工具类 

ArrayUtils   数组工具类  

RandomUtils   随机数工具类

DateUtils   日期工具类 

StopWatch   秒表工具类 

ClassUtils   反射工具类  

SystemUtils   系统工具类  

MapUtils   集合工具类

Beanutils   bean工具类

Commons-io io的工具类

等等.....

使用方式:

  1. 新建lib文件夹
  2. 把第三方jar包粘贴到文件夹中
  3. 右键点击add as a library


IOUtils(数据相关)

拷贝方法:

copy方法有多个重载方法,满足不同的输入输出流

IOUtils.copy(InputStream input, OutputStream output)

IOUtils.copy(InputStream input, OutputStream output, int bufferSize)//可指定缓冲区大小

IOUtils.copy(InputStream input, Writer output, String inputEncoding)//可指定输入流的编码表

IOUtils.copy(Reader input, Writer output)

IOUtils.copy(Reader input, OutputStream output, String outputEncoding)//可指定输出流的编码表

拷贝大文件的方法:

这个方法适合拷贝较大的数据流,比如2G以上

IOUtils.copyLarge(Reader input, Writer output) // 默认会用1024*4的buffer来读取

IOUtils.copyLarge(Reader input, Writer output, char[] buffer)//可指定缓冲区大小

将输入流转换成字符串

IOUtils.toString(Reader input)

IOUtils.toString(byte[] input, String encoding)

IOUtils.toString(InputStream input, Charset encoding)

IOUtils.toString(InputStream input, String encoding)

IOUtils.toString(URI uri, String encoding)

IOUtils.toString(URL url, String encoding)

将输入流转换成字符数组

IOUtils.toByteArray(InputStream input)

IOUtils.toByteArray(InputStream input, int size)

IOUtils.toByteArray(URI uri)

IOUtils.toByteArray(URL url)

IOUtils.toByteArray(URLConnection urlConn)

IOUtils.toByteArray(Reader input, String encoding)

字符串读写

IOUtils.readLines(Reader input)

IOUtils.readLines(InputStream input, Charset encoding)

IOUtils.readLines(InputStream input, String encoding)

IOUtils.writeLines(Collection<?> lines, String lineEnding, Writer writer)

IOUtils.writeLines(Collection<?> lines, String lineEnding, OutputStream output, Charset encoding)

IOUtils.writeLines(Collection<?> lines, String lineEnding, OutputStream output, String encoding)

从一个流中读取内容

IOUtils.read(InputStream input, byte[] buffer)

IOUtils.read(InputStream input, byte[] buffer, int offset, int length) IOUtils.read(Reader input, char[] buffer)

IOUtils.read(Reader input, char[] buffer, int offset, int length)

把数据写入到输出流中

IOUtils.write(byte[] data, OutputStream output)

IOUtils.write(byte[] data, Writer output, Charset encoding)

IOUtils.write(byte[] data, Writer output, String encoding)

IOUtils.write(char[] data, Writer output)

IOUtils.write(char[] data, OutputStream output, Charset encoding)

IOUtils.write(char[] data, OutputStream output, String encoding)

IOUtils.write(String data, Writer output)

IOUtils.write(CharSequence data, Writer output)

从一个流中读取内容,如果读取的长度不够,就会抛出异常

IOUtils.readFully(InputStream input, int length)

IOUtils.readFully(InputStream input, byte[] buffer)

IOUtils.readFully(InputStream input, byte[] buffer, int offset, int length) IOUtils.readFully(Reader input, char[] buffer)

IOUtils.readFully(Reader input, char[] buffer, int offset, int length)

比较

IOUtils.contentEquals(InputStream input1, InputStream input2) // 比较两个流是否相等

IOUtils.contentEquals(Reader input1, Reader input2)

IOUtils.contentEqualsIgnoreEOL(Reader input1, Reader input2) // 比较两个流,忽略换行符

其他方法

IOUtils.skip(InputStream input, long toSkip) // 跳过指定长度的流

IOUtils.skip(Reader input, long toSkip)

IOUtils.skipFully(InputStream input, long toSkip) // 如果忽略的长度大于现有的长度,就会抛出异常

IOUtils.skipFully(Reader input, long toSkip)

FileUtils(文件/文件夹相关)

复制文件夹

FileUtils.copyDirectory(File srcDir, File destDir) // 复制文件夹(文件夹里面的文件内容也会复制)

FileUtils.copyDirectory(File srcDir, File destDir, FileFilter filter) // 复制文件夹,带有文件过滤功能

FileUtils.copyDirectoryToDirectory(File srcDir, File destDir) // 以子目录的形式将文件夹复制到到另一个文件夹下

复制文件

FileUtils.copyFile(File srcFile, File destFile) // 复制文件

FileUtils.copyFile(File input, OutputStream output) // 复制文件到输出流

FileUtils.copyFileToDirectory(File srcFile, File destDir) // 复制文件到一个指定的目录

FileUtils.copyInputStreamToFile(InputStream source, File destination) // 把输入流里面的内容复制到指定文件

FileUtils.copyURLToFile(URL source, File destination) // 把URL 里面内容复制到文件(可以下载文件)

FileUtils.copyURLToFile(URL source, File destination, int connectionTimeout, int readTimeout)

把字符串写入文件

FileUtils.writeStringToFile(File file, String data, String encoding)

FileUtils.writeStringToFile(File file, String data, String encoding, boolean append)

把字节数组写入文件

FileUtils.writeByteArrayToFile(File file, byte[] data)

FileUtils.writeByteArrayToFile(File file, byte[] data, boolean append) FileUtils.writeByteArrayToFile(File file, byte[] data, int off, int len) FileUtils.writeByteArrayToFile(File file, byte[] data, int off, int len, boolean append)

把集合里面的内容写入文件

// encoding:文件编码,lineEnding:每行以什么结尾

FileUtils.writeLines(File file, Collection<?> lines)

FileUtils.writeLines(File file, Collection<?> lines, boolean append)

FileUtils.writeLines(File file, Collection<?> lines, String lineEnding)

FileUtils.writeLines(File file, Collection<?> lines, String lineEnding, boolean append)

FileUtils.writeLines(File file, String encoding, Collection<?> lines)

FileUtils.writeLines(File file, String encoding, Collection<?> lines, boolean append)

FileUtils.writeLines(File file, String encoding, Collection<?> lines, String lineEnding)

FileUtils.writeLines(File file, String encoding, Collection<?> lines, String lineEnding, boolean append)

往文件里面写内容

FileUtils.write(File file, CharSequence data, Charset encoding)

FileUtils.write(File file, CharSequence data, Charset encoding, boolean append)

FileUtils.write(File file, CharSequence data, String encoding)

FileUtils.write(File file, CharSequence data, String encoding, boolean append)

文件移动

FileUtils.moveDirectory(File srcDir, File destDir) // 文件夹在内的所有文件都将移动FileUtils.moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) // 以子文件夹的形式移动到另外一个文件下

FileUtils.moveFile(File srcFile, File destFile) // 移动文件

FileUtils.moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) // 以子文件的形式移动到另外一个文件夹下

FileUtils.moveToDirectory(File src, File destDir, boolean createDestDir) // 移动文件或者目录到指定的文件夹内

清空和删除文件夹

FileUtils.deleteDirectory(File directory) // 删除文件夹,包括文件夹和文件夹里面所有的文件

FileUtils.cleanDirectory(File directory) // 清空文件夹里面的所有的内容

FileUtils.forceDelete(File file) // 删除,会抛出异常

FileUtils.deleteQuietly(File file) // 删除,不会抛出异常

创建文件夹

FileUtils.forceMkdir(File directory) // 创建文件夹(可创建多级)

FileUtils.forceMkdirParent(File file) // 创建文件的父级目录

获取文件输入/输出流

FileUtils.openInputStream(File file)

FileUtils.openOutputStream(File file)

读取文件

FileUtils.readFileToByteArray(File file) // 把文件读取到字节数组

FileUtils.readFileToString(File file, Charset encoding) // 把文件读取成字符串

FileUtils.readFileToString(File file, String encoding)

FileUtils.readLines(File file, Charset encoding) // 把文件读取成字符串集合

FileUtils.readLines(File file, String encoding)

测试两个文件的修改时间

FileUtils.isFileNewer(File file, Date date)

FileUtils.isFileNewer(File file, File reference)

FileUtils.isFileNewer(File file, long timeMillis)

FileUtils.isFileOlder(File file, Date date)

FileUtils.isFileOlder(File file, File reference)

FileUtils.isFileOlder(File file, long timeMillis)

文件/文件夹的迭代

FileUtils.iterateFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)

FileUtils.iterateFiles(File directory, String[] extensions, boolean recursive)

FileUtils.iterateFilesAndDirs(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)

FileUtils.lineIterator(File file)

FileUtils.lineIterator(File file, String encoding)

FileUtils.listFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)

FileUtils.listFiles(File directory, String[] extensions, boolean recursive)

FileUtils.listFilesAndDirs(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter)

其他

FileUtils.isSymlink(File file) // 判断是否是符号链接

FileUtils.directoryContains(File directory, File child) // 判断文件夹内是否包含某个文件或者文件夹

FileUtils.sizeOf(File file) // 获取文件或者文件夹的大小

FileUtils.getTempDirectory()// 获取临时目录文件

FileUtils.getTempDirectoryPath()// 获取临时目录路径

FileUtils.getUserDirectory()// 获取用户目录文件

FileUtils.getUserDirectoryPath()// 获取用户目录路径

FileUtils.touch(File file) // 创建文件

FileUtils.contentEquals(File file1, File file2) // 比较两个文件内容是否相同

FilenameUtils(文件名/后缀名相关)

FilenameUtils.concat(String basePath, String fullFilenameToAdd) // 合并目录和文件名为文件全路径

FilenameUtils.getBaseName(String filename) // 去除目录和后缀后的文件名

FilenameUtils.getExtension(String filename) // 获取文件的后缀

FilenameUtils.getFullPath(String filename) // 获取文件的目录

FilenameUtils.getName(String filename) // 获取文件名

FilenameUtils.getPath(String filename) // 去除盘符后的路径

FilenameUtils.getPrefix(String filename) // 盘符

FilenameUtils.indexOfExtension(String filename) // 获取最后一个.的位置

FilenameUtils.indexOfLastSeparator(String filename) // 获取最后一个/的位置

FilenameUtils.normalize(String filename) // 获取当前系统格式化路径

FilenameUtils.removeExtension(String filename) // 移除文件的扩展名

FilenameUtils.separatorsToSystem(String path) // 转换分隔符为当前系统分隔符

FilenameUtils.separatorsToUnix(String path) // 转换分隔符为linux系统分隔符

FilenameUtils.separatorsToWindows(String path) // 转换分隔符为windows系统分隔符

FilenameUtils.equals(String filename1, String filename2) // 判断文件路径是否相同,非格式化

FilenameUtils.equalsNormalized(String filename1, String filename2) // 判断文件路径是否相同,格式化

FilenameUtils.directoryContains(String canonicalParent, String canonicalChild) // 判断目录下是否包含指定文件或目录

FilenameUtils.isExtension(String filename, String extension) // 判断文件扩展名是否包含在指定集合(数组、字符串)中

FilenameUtils.wildcardMatch(String filename, String wildcardMatcher) // 判断文件扩展名是否和指定规则匹配

Hutool