一、java IO流概述

Java IO(输入/输出)流是 Java 提供的用于数据输入和输出的机制,主要用于文件操作、网络通信、数据流处理等。Java IO 以 流(Stream) 的方式处理数据,即数据像水流一样从一个地方流向另一个地方。

  • File:表示系统中的文件或者文件夹的路径。利用File可以获取文件信息(大小、文件名、修改时间),判断文件的类型,创建文件/文件夹,删除文件/文件夹等操作。

注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据。

  • IO流:用于读写文件中的数据(可以读写文件,或网络中的数据)。

IO流的分类

Java IO 流按照不同的标准可以进行不同的分类。

1 按数据流向分类(以程序为参照物)

  • 输入流(Input Stream):数据从外部流入程序(如从文件读取数据)。
  • 输出流(Output Stream):数据从程序流出到外部(如将数据写入文件)。

2 按数据类型分类

  • 字节流(Byte Stream):以 字节(8-bit) 为单位进行数据读写,适用于任何类型的文件(文本、图片、视频等)。
  • 字符流(Character Stream):以 字符(16-bit Unicode) 为单位进行数据读写,专门用于处理纯文本数据(指能用Windows自带的记事本打开并且能读懂的文件)。

二、IO流的体系

示例图

img

三、字节流

字节流示例图

img

1. FileOutputStream

FileOutputStream:操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。

书写步骤

  1. 创建字节输出流对象
    • 细节1:参数是字符串表示的路径(底层也是将字符串路径变为File对象)或者是File对象都是可以的。如FileOutputStream fos = new FileOutputStream("D:\\a.txt");或者FileOutputStream fos = new FileOutputStream(new File("D:\\a.txt"));
    • 细节2:如果文件不存在会创建一个新的文件,但是要保证父路径是存在的。
    • 细节3:如果文件已经存在,则会清空文件。
  2. 写数据
    • 细节:write()方法的参数是整数,但是实际上写到本地文件中的是整数再ASCII上对应的字符。如 97 —> a。
  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
package com.ryan.iostream;

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

public class ByteStreamDemo1 {
public static void main(String[] args) throws IOException {
/**
* 演示:字节输出流FileOutputStream
* 实现需求:写出一段文字到本地文件中。(暂时不写中文)
* 实现步骤:
* 1. 创建字节输出流对象
* 2. 写数据
* 3. 释放资源
*/
// 1. 创建对象
// 创建对象时需要填入文件的绝对路径,
// 在当前项目下创建一个a.txt
FileOutputStream fos = new FileOutputStream("D:\\Code\\teacher_han\\Homework\\IOStream\\a.txt");
// 2. 写出数据
fos.write(97);
// 3. 释放资源
fos.close();

}
}

FileOutputStream写数据的3种方式

方法名 说明
void write(ing b) 一次写一个字节数据
void write(byte[] b) 一次写一个字节数组数据
void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据

void write(byte[] b, int off, int len)参数说明

  • 参数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
package com.ryan.iostream;

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

public class ByteStreamDemo2 {
public static void main(String[] args) throws IOException {
/*
FileOutputStream三种写数据的方法演示
*/
// 1. 创建对象
FileOutputStream fos = new FileOutputStream("D:\\Code\\teacher_han\\Homework\\IOStream\\a.txt");
// 2. 写出数据
// 方法1
fos.write(97); // a
fos.write(98); // b

// 方法2
byte[] bytes = {97, 98, 99, 100, 101};
fos.write(bytes);

// 方法3
byte[] bytes1 = {97, 98, 99, 100, 101};
fos.write(bytes1, 1, 2);

// 3. 关闭资源
fos.close();
}
}

FileOutputStream写数据的两个小问题

1. 换行写数据

  • 再次写出一个换行符即可
  • Windows: \r\n
  • Linux: \n
  • Mac:\r
  • 细节:在Windows中,Java对回车换行进行了优化,虽然完整的是\r\n,但是我们写其中一个\r或者\n,Java都可以实现换行,因为Java在底层会补全。但是建议不要省略,补全写。
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 com.ryan.iostream;

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

public class ByteStreamDemo3 {
public static void main(String[] args) throws IOException {
// 1. 创建对象
FileOutputStream fos = new FileOutputStream("D:\\Code\\teacher_han\\Homework\\IOStream\\b.txt");
// 2. 写出数据
String str = "ryanlearnjava";
byte[] bytes1 = str.getBytes();
fos.write(bytes1);
// 再次写一个换行符即可
String wrap = "\r\n";
byte[] bytes2 = wrap.getBytes();
fos.write(bytes2);
// 换行后写数据
String str2 = "666";
byte[] bytes3 = str2.getBytes();
fos.write(bytes3);

// 3. 关闭资源
fos.close();
}
}

2. 不清空文件,续写数据

  • 在创建FileOutputStream对象的时候,在构造方法中加入第二个参数true即可,表示打开续写。
1
FileOutputStream fos = new FileOutputStream("D:\\Code\\teacher_han\\Homework\\IOStream\\b.txt", true);

2. FileInputStream

FileInputStream:操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中。

书写步骤:

  1. 创建字节输入流对象
    • 细节1:如果文件不存在,就直接报错
  2. 读数据
    • 细节1:一次读一个字节的数据,读出来的是数据在ASCII上对应的数字。
    • 细节2:read()每执行一次,就会把指向数据的指针向后移动一次。
    • 细节2:读到文件末尾了,read方法返回-1。
  3. 释放资源
    • 每次使用完流必须要释放资源。

FileInputStream循环读取

代码演示

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

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

public class ByteStreamDemo2 {
public static void main(String[] args) throws IOException {
/*
字节输入轮流循环读取
*/

// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\Code\\teacher_han\\Homework\\IOStream\\src\\a.txt");
// 2. 循环读取
int b; // 用于保存读到的数据
while ((b = fis.read()) != -1) {
System.out.println((char) b);
}
// 3. 关闭资源
fis.close();
}
}

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
package com.ryan.inputstream;

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

public class ByteStreamDemo3 {
public static void main(String[] args) throws IOException {
/*
练习:
文件拷贝:把D:\ryan.jpg拷贝到当前模块下
注意:
选择一个比较小的文件,不要太大
*/

// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\ryan.jpg");
FileOutputStream fos = new FileOutputStream("D:\\Code\\teacher_han\\Homework\\IOStream\\copy.jpg");
// 2. 拷贝
// 核心思想:边读边写
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
// 3. 释放资源
// 规则:先开的流最后关闭
fos.close();
fis.close();
}
}

FileInputStream读取问题

IO流:如果拷贝的文件过大,速度会非常慢。因为一次读写一个字节

4. FileInputStream一次读多个字节

方法名称 说明
public int read() 一次读一个字节数据
public int read(byte[] buffer) 一次读一个字节数组数据

注意:一次读一个字节数组的数据,每次读取会尽可能把数组填满。

创建字节数组一般是1024的整数倍。

代码示例

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 com.ryan.inputstream;

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

public class ByteStreamDemo4 {
public static void main(String[] args) throws IOException {
// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\Code\\teacher_han\\Homework\\IOStream\\src\\a.txt");
byte[] bytes = new byte[2];
/*
一次读取多个字节数据,具体读多少,跟数组长度有关
返回值:本次读取到了多少个字节数据
*/
int len1 = fis.read(bytes);
System.out.println(len1); // 2
String str1 = new String(bytes, 0, len1);
System.out.println(str1);

int len2 = fis.read(bytes);
System.out.println(len1); // 2
String str2 = new String(bytes, 0, len2);
System.out.println(str2);

int len3 = fis.read(bytes);
System.out.println(len3); // 2
String str3 = new String(bytes, 0, len3);
System.out.println(str3);
}
}
输出结果(a.txt中的数据是:abcde):
2
ab
2
cd
1
e

拷贝(大文件)改进代码

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 com.ryan.inputstream;

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

public class ByteStreamDemo5 {
public static void main(String[] args) throws IOException {
/*
练习:
把D:\movie.mp4(15.8MB)拷贝到当前模块下
*/
// 测定拷贝该文件需要多长时间
// 获取当前系统时间
long start = System.currentTimeMillis();

// 1. 创建对象
FileInputStream fis = new FileInputStream("D:\\movie.mp4");
FileOutputStream fos = new FileOutputStream("D:\\Code\\teacher_han\\Homework\\IOStream\\src\\copy.mp4");
// 拷贝
int b;
byte[] bytes = new byte[1024*1024*5]; // 5MB
while ((b = fis.read(bytes)) != -1) {
fos.write(bytes, 0, b);
}
// 3. 释放资源
fos.close();
fis.close();

long end = System.currentTimeMillis();
System.out.println(end - start);
}
}

四、字符集

字符集(Charset)是用于 字符编码和解码 的规则集合,它定义了字符如何在计算机内部表示。字符集决定了字符的 编码方式(存储为二进制数据)和 解码方式(将二进制数据转换回字符)。

编码 特点 适用范围
ASCII 7 位,128 个字符 仅适用于英文
ISO-8859-1 8 位,支持西欧字符 英语和西欧语言
GB2312 / GBK / GB18030 适用于中文 中文文档、应用
Unicode 统一全球字符 适用于所有语言
UTF-8 变长(1~4 字节),兼容 ASCII 网络、Web 开发
UTF-16 2/4 字节,适合存储 Java 内部字符处理
UTF-32 4 字节,存储浪费 很少使用

在 Java 开发中,推荐使用 UTF-8,避免乱码问题!

编码:就是将要存储的数据变成真正的能存储在硬盘当中的字节数据。

为什么会有乱码?

  1. 读取数据时未读完整个汉字
  2. 编码和解码时的方式不统一

如何不产生乱码?

  1. 不要用字节流读取文本文件
  2. 编码解码时使用同一个码表,同一个编码方式。

Java中编码的方法

String类中的方法 说明
public byte[] getBytes() 使用默认方式进行编码
public byte[] getBytes(String charsetName) 使用指定方式进行编码

java中解码的方法

String类中的方法 说明
String(byte[] bytes) 使用默认方式进行解码
String(byte[] bytes, String charsetName) 使用指定方式进行解码

代码实现

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
package com.ryan.chatset;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class CharsetDemo1 {
public static void main(String[] args) throws UnsupportedEncodingException {
// 1. 编码
// 使用默认方式UTF-8
// 一个英文一个字节,一个中文三个字节
String str = "ai你哟";
byte[] bytes1 = str.getBytes();
System.out.println(Arrays.toString(bytes1));

// 使用指定方式 GBK
// 一个英文一个字节,一个中文两个字节
byte[] bytes2 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));

// 2. 解码
// 使用默认方式
String str2 = new String(bytes1);
System.out.println(str2);

// 使用指定方式-GBK
// 编码解码方式不一样,中文产生乱码
String str3 = new String(bytes1, "GBK");
System.out.println(str3);
}
}

五、字符流

字符流:底层其实还是字节流。

字符流 = 字节流 + 字符集

特点

输入流:一次读一个字节,遇到中文时,一次读多个字节。

输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中。

使用场景

对于纯文本文件进行读写操作。

字符流示例图

img

1. FileRreader

步骤

  1. 创建字符输入流对象

    构造方法 说明
    public FileReader(File file) 创建字符输入流关联本地文件
    public FileReader(String pathname) 创建字符输入流关联本地文件

    细节1:如果文件不存在,就直接报错

  2. 读取数据

    成员方法 说明
    public int read() 读取数据,读到末尾返回-1
    public int read(char[] buffer) 读取多个数据,读到末尾返回-1

    细节1:按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个十进制整数。

    细节2:读到文件末尾了,read方法返回-1

  3. 释放资源

空参read()方法代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.ryan.chatset;

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

public class CharsetDemo2 {
public static void main(String[] args) throws IOException {
// 1. 创建对象
FileReader fr = new FileReader("src\\a.txt");
// 2. 读取数据
int ch;
while ((ch = fr.read()) != -1) {
System.out.println(ch);// 读十进制数
System.out.print((char)ch);// 强制转换变成中文
}
}
}

空参的read方法:读取数据+解码。

所以如果想看到中文的打印结果,还需要我们进行强制转换。

有参read(char[] chars)方法代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.ryan.chatset;

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

public class CharsetDemo3 {
public static void main(String[] args) throws IOException {
// 1. 创建对象
FileReader fr = new FileReader("src\\a.txt");
// 读取数据
char[] chars = new char[2];
int len;
while ((len = fr.read(chars)) != -1) {
// 把数组中的数据变成字符串再进行打印
System.out.print(new String(chars, 0, len));
}
// 3. 释放资源
fr.close();
}
}

有参的read方法:读取数据,解码,强制转换三步合并了,把强制转换后的字符放到数组当中。

2. FileWriter

构造方法

构造方法 说明
public FileWriter(File file) 创建字符输出流关联本地文件
public FileWriter(String pathname) 创建字符输出流关联本地文件
public FileWriter(File file, boolean append) 创建字符输出流关联本地文件,续写
public FileWriter(String pathname, boolean append) 创建字符输出流关联本地文件,续写

FileWriter书写细节

  1. 创建字符输出流对象
    • 细节1:参数是字符串表示的路径或者File对象都是可以的
    • 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
    • 细节3:如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关。
  2. 写数据
    • 细节:如果write方法的参数是整数,但是实际上写到本地文件中的是整数再字符集上对应的字符。
  3. 释放资源

3. 字符流原理解析

字符输入流原理解析

  1. 创建字符输入流对象

    • 底层:关联文件,并创建缓冲区(长度为8192的字节数组)
  2. 读取数据

    • 底层:1. 判断缓冲区中是否有数据可以读取
      2. 缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能填满缓冲区;如果文件中也没有数据了,就返回-1。
      3. 缓冲区有数据:就从缓冲区中读取。
      空参的read方法:一次读取一个字节,遇到中文一次读取多个字节,把字节解码并转成十进制返回。

      有参的read方法:把读取字节,解码,强制转换三步合并了,强制转换之后的字符放到数组中。

字符输出流原理解析

成员方法 说明
public void flush() 将缓冲区中的数据,刷新到本地文件中
public void close() 释放资源/关流

flush刷新:刷新之后,还可以继续往文件中写出数据。

close关流:断开通道,无法再往文件中写出数据。

  1. 创建字符输出流对象

    • 底层:关联文件,并创建缓冲区(长度为8192的字节数组)
  2. 写出数据

    • 底层:先写到缓冲区,再写到本地文件。

数据什么时候真正的保存到本地文件中?

  1. 缓冲区装满了,自动保存到本地文件。
  2. 手动刷新,使用flush()方法。
  3. 释放资源/关流 close()方法。

六、字节流和字符流的使用场景

字节流

  1. 拷贝任意类型的文件

字符流

  1. 读取纯文本文件中的数据
  2. 纯文本文件中写出数据

习题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
package com.ryan.chatset;

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

public class Test01 {
public static void main(String[] args) throws IOException {
// 拷贝一个文件夹,考虑子文件夹
// 1. 创建对象表示数据源
File src = new File("D:\\src");
// 2. 创建对象表示目的地
File dest = new File("D:\\dest");
// 3. 调用方法开始拷贝
copydir(src, dest);
}

/**
* 作用:拷贝文件夹
* 参数一:数据源
* 参数二:目的地
*/
private static void copydir(File src, File dest) throws IOException {
dest.mkdir();
// 递归
// 1. 进入数据源
File[] files = src.listFiles();
// 2. 遍历数组
for (File file : files) {
if (file.isFile()) {
// 3. 判断文件,拷贝
FileInputStream fis = new FileInputStream(src);
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(src, new File(dest, file.getName()));
}
}
}
}

七、高级流

体系图

img

八、缓冲流

体系图

img

1. 字节缓冲流

原理:底层自带了长度为8192的缓冲区提高性能

字节缓冲流的构造方法

方法名称 说明
public BufferedInputStream(InputStream is) 把基本流包装成高级流,提高读取数据的性能
pubic BufferedOutputStream(OutputStream os) 把基本流包装成高级流,提高写出数据的性能

练习:利用字节缓冲流拷贝文件

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

import java.io.*;

public class BufferedStreamDemo1 {
public static void main(String[] args) throws IOException {
// 1. 创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\b.txt"));
// 2. 循环读取并写到目的地
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
// 3. 释放资源
bos.close();
bis.close();
}
}

一次拷贝多个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.ryan.bufferedstream;

import java.io.*;

public class BufferedStreamDemo2 {
public static void main(String[] args) throws IOException {
// 1. 创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\b.txt"));
// 拷贝,用字节数组,一次拷贝多个字节
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 3. 关闭资源
bos.close();
bis.close();
}
}

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

示例图

img

创建的缓冲区是在内存中执行的,内存执行是非常快的,所以字节缓冲流节省的是内存和硬盘之间打交道的时间。没必要没读取一个字节就从硬盘的文件中读取一次,而是从已经被尽可能填满的缓冲区中进行读取。

2. 字符缓冲流

原理:底层自带了长度为8192的缓冲区提高性能(但是实际上字符流底层自带了缓冲区,所以字符缓冲流提高的性能不是很明显)。

字符缓冲流的构造方法

方法名称 说明
public BufferedReader(Reader r) 把基本流变成高级流
public BufferedWriter(Writer w) 把基本流变成高级流

字符缓冲流特有方法

字符缓冲输入流特有方法 说明
public String readLine() 读取一行数据,如果没有数据可读了,会返回null
字符缓冲输出流特有方法 说明
public void newLine() 跨平台的换行

字符缓冲输入流代码示例

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 com.ryan.bufferedstream;

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

public class BufferedStreamDemo3 {
public static void main(String[] args) throws IOException {
// 1. 创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("src\\a.txt"));
// 2. 读取数据
/*
细节:
readLine()方法读取的时候,一次读一整行
遇到回车换行结束
但是不会把回车换行读取到内存中
*/
/* String line1 = br.readLine();
System.out.println(line1);

String line2 = br.readLine();
System.out.println(line2);*/
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}

// 3. 释放资源
br.close();
}
}

字符缓冲输出流代码示例

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

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedStreamDemo4 {
public static void main(String[] args) throws IOException {
// 1. 创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\b.txt"));
// 2. 写出数据
bw.write("123");
bw.newLine(); // 跨平台换行
bw.write("456");
bw.newLine();
// 3. 释放资源
bw.close();
}
}

打开续写:BufferedWriter bw = new BufferedWriter(new FileWriter(“src\b.txt”, true));

BufferedWriter没有打开续写的功能,所以要在FileWriter中打开。

九、转换流

转换流属于字符流,用于 字节流字符流 之间的转换,主要用于处理 不同编码格式的文本数据,防止乱码问题。

  • InputStreamReader:将 字节输入流 转换为 字符输入流解码)。
  • OutputStreamWriter:将 字符输出流 转换为 字节输出流编码)。

作用

  1. InputStream(字节流) 能够以 字符 方式读取数据。
  2. OutputStream(字节流) 能够以 字符 方式写入数据。
  3. 处理 不同字符编码,如 UTF-8GBK
  4. 字节流想要使用字符流中的方法

InputStreamReader(字节流 → 字符流)

构造方法

1
2
public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in, String charsetName) // 指定编码

OutputStreamWriter(字符流 → 字节流)

构造方法

1
2
public OutputStreamWriter(OutputStream out)
public OutputStreamWriter(OutputStream out, String charsetName) // 指定编码

练习一 转换文件编码

  • 需求1:手动创建一个GBK文件,把文件中的中文读取到内存中,不能出现乱码。
  • 需求2:把一段中文按照GBK的方式写到本地文件
  • 需求3:将本地文件中的GBK文件,转成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
package com.ryan.convertstream;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.Charset;

public class ConvertStreamDemo3 {
public static void main(String[] args) throws IOException {
/*
将本地文件中的GBK文件,转成UTF-8
*/
// JDK11之后的写法
FileReader fr = new FileReader("src\\a.txt", Charset.forName("GBK"));
FileWriter fw = new FileWriter("src\\b.txt", Charset.forName("UTF-8"));
int b;
while ((b = fr.read()) != -1) {
fw.write(b);
}

fw.close();
fr.close();
}
}

练习二 读取文件中的数据

利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.ryan.convertstream;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class ConvertStreamDemo4 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("src\\a.txt")));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
}

十、序列化流和反序列化流

序列化流也是高级流,也是用来包装基本流的,属于字节流的一种。

序列化(Serialization) 是指 将 Java 对象转换为字节流 以便存储到文件或通过网络传输。
反序列化(Deserialization) 是指 从字节流恢复 Java 对象

2. 序列化的核心类

作用
ObjectOutputStream 对象序列化(写入文件/网络)
ObjectInputStream 对象反序列化(读取对象)

1. 序列化流/对象造作输出流

可以把Java中的对象写到本地文件中,而且写到文件中的内容我们是看不懂的

构造方法 说明
public ObjectOutputStream(OutputStream out) 把基本流包装成高级流
成员方法 说明
public final writeObject(Object obj) 把对象序列化(写出)到文件中去

序列化流的小细节

使用对象输出流将对象保存到文件时会出现NotSerializableException异常

解决方案:需要让Javabean类实现Serializable接口

JavaBean 是一种 符合特定规范的 Java 类,主要用于封装数据,并可在 Web 开发、GUI 开发(如 JSP、Spring、JavaFX) 中使用。

JavaBean 特点:

  1. 必须有一个无参构造方法(方便反射创建实例)。
  2. 所有属性都是私有(private,不能直接访问。
  3. 提供 gettersetter 方法 访问属性。
  4. 可序列化(通常实现 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
57
package com.ryan.objectstream;

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

public class ObjectStreamDemo1 {
public static void main(String[] args) throws IOException {
/*
需求:
利用序列化流/对象操作输出流,把一个对象写到本地文件中
*/
// 1. 创建对象
Student stu = new Student("zhangsan", 23);
// 2. 创建序列化流的对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\a.txt"));
// 3. 写出数据
oos.writeObject(stu);
// 4. 释放资源
oos.close();
}
}

/**
* Serializable接口里面是没有抽象方法的,称为:标记型接口
* 一旦实现了这个接口,那么就表示当前类可以被序列化
*/
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;
}
}

2. 反序列化流/对象操作输入流

可以把序列化到本地文件中的对象,读取到程序中来

构造方法 说明
public ObjectInputStream(InputStream in) 把基本流变成高级流
成员方法 说明
public Object readObject() 把序列化到本地文件中的对象,读取到程序中

3. 序列化流/反序列化流的细节汇总

  1. 使用序列化流将对象写到文件时,需要让javabean类实现Serializable接口,否则会出现NotSerializableException异常
  2. 序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了。
  3. 序列化对象后,修改了Javabean类,再次反序列化,会不会有问题?
    • 会出问题,会抛出InvalidClassException异常
    • 解决方法:给Javabean类添加serialVersionUID(序列号、版本号)
  4. 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现?
    • 解决方法:给该成员变量添加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
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
1. 序列化代码
package com.ryan.objectstream;

import java.io.*;
import java.util.ArrayList;

public class Test {
public static void main(String[] args) throws IOException {
// 1. 序列化多个对象
Teacher t1 = new Teacher("zhangsan", 23, "南京");
Teacher t2 = new Teacher("lisi", 24, "重庆");
Teacher t3 = new Teacher("zhangsan", 25, "北京");

// 规定:当有多个对象时,将对象放入到ArrayList中
ArrayList<Teacher> list = new ArrayList<>();
list.add(t1);
list.add(t2);
list.add(t3);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\a.txt"));
// 2. 写出对象
oos.writeObject(list);
// 3. 释放资源
oos.close();
}
}

2. 反序列化代码
package com.ryan.objectstream;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;

public class Test01 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1. 创建反序列化流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\a.txt"));
// 2. 读取数据
ArrayList<Teacher> list = (ArrayList<Teacher>) ois.readObject();
for (Teacher teacher : list) {
System.out.println(teacher);
}

// 3. 释放资源
ois.close();
}
}

3. Javabean类
package com.ryan.objectstream;

import java.io.Serializable;

public class Teacher implements Serializable {

private static final long serialVersionUID = -8095654425841779262L;

private String name;
private int age;
private String address;

public Teacher() {
}

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

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

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}

十一、打印流

打印流也是高级流,用来包装基本流,打印流不能读,只能写,所以打印流只有输出流。

分类:打印流一般是指:PrintStream(字节打印流), PrintWriter(字符打印流)两个类

特点

  1. 打印流只操作文件目的地,不操作数据源。
  2. 特有的写出方法可以实现数据原样写出
  3. 特有的写出方法,可以实现自动刷新,自动换行。

打印一次数据 = 写出 + 换行 + 刷新

1. 字节打印流

构造方法 说明
public PrintStream(OutputStream/File/String) 关联字节输出流/文件/文件路径
public PrintStream(String fileName, Charset harset) 指定字符编码
public PrintStream(OutputStream out, boolean autoFlush) 自动刷新
public PrintStream(OutputStream out, boolean autoFlush, String enconding) 指定字符编码且自动刷新

字节流底层没有缓冲区,所以开不开自动刷新都一样。

成员方法 说明
public void write(int b) 常规方法:将指定的字节写出
public void println(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
public void print(Xxx xx) 特有方法:打印任意数据,不换行
public void printf(String fomat, Object… args) 特有方法:带占位符的打印语句,不换行

2. 字符打印流

构造方法 说明
public PrintWriter(Write/File/String) 关联字节输出流/文件/文件路径
public PrintWriter(String fileName, Charset charset) 指定字符编码
public PrintWriter(Write w, boolean, autoFlush) 自动刷新
public PrintWriter(OutputStream out, boolean autoFlush, Charset charset) 指定字符编码且自动刷新

字符打印流底层有缓冲区,想要自动刷新需要开启。

成员方法 说明
public void write(int b) 常规方法:将指定的字节写出
public void println(Xxx xx) 特有方法:打印任意数据,自动刷新,自动换行
public void print(Xxx xx) 特有方法:打印任意数据,不换行
public void printf(String fomat, Object… args) 特有方法:带占位符的打印语句,不换行

十二、解压缩流/压缩流

1. 解压缩流

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

2. 压缩流

压缩本质:把每一个(文件/文件夹)看成ZipEntry对象,放到压缩包中。

十三、常用工具包 Commons-io

Commons-io是apache开源基金组织提供的一组有关IO操作的开源工具包。

作用:提高IO流的开发效率

使用步骤

  1. 在项目中创建一个文件夹:lib
  2. 将jar包复制粘贴到lib文件夹
  3. 右键点击jar包,选择Add as Library -> 点击OK
  4. 在类中导包使用

Commons-io常见方法

img

img

十四、Hutool工具包

img