【Java基础】java.io包下的常用API-io流

File类

之前学习的变量、数组、对象和集合都是内存中的数据容器,它们记住的数据在断电或者程序终止时会丢失。

如果想长期保存数据,应该怎么做?

  • 文件是非常重要的存储方式,在计算机硬盘中。
  • 即便断电,或者程序终止了,存储在硬盘文件中的数据也不会丢失。

(1)File类

File是java.io.包下的类, File类的对象,用于代表当前操作系统的文件(可以是文件、或文件夹)。

常见功能

  • 获取文件信息(大小,文件名,修改时间)。
  • 创建文件/文件夹。
  • 删除文件/文件夹。
  • 判断文件的类型。
  • ……

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

(2)IO流

用于读写数据的(可以读写文件,或网络中的数据…)。

创建对象

  1. public File(String pathname)。根据文件路径创建文件对象。(最重要!)
  2. public File(String parent, String child)。根据父路径和子路径名字创建文件对象。
  3. public File(File parent, String child)。根据父路径对应文件对象和子路径名字创建文件对象。
  • File对象既可以代表文件、也可以代表文件夹
  • File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的。

路径分隔符

  1. /(推荐
  2. \\
  3. File.separator

绝对路径

  • 从盘符开始。
  • File file1 = new File(“D:\itheima\a.txt”);

相对路径(开发规范

  • 不带盘符,默认直接到当前工程下的目录寻找文件。
  • File file3 = new File(“模块名\a.txt”);

常用方法1:判断文件类型、获取文件信息

  1. public boolean exists()。判断当前文件对象,对应的文件路径是否存在,存在返回true。
  2. public boolean isFile()。判断当前文件对象指代的是否是文件,是文件返回true,反之。
  3. public boolean isDirectory()。判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之。
  4. public String getName()。获取文件的名称(包含后缀)。
  5. public long length()。获取文件的大小,返回字节个数。(然后可以转换成SimpleDateFormat并用format格式化。)
  6. public long lastModified()。获取文件的最后修改时间。
  7. public String getPath()。获取创建文件对象时,使用的路径。
  8. public String getAbsolutePath()。获取绝对路径。

常用方法2:创建文件、删除文件

创建文件

  1. public boolean createNewFile()。创建一个新的空的文件。(程序有可能担心我们乱写盘符,因此报异常,直接alt+enter抛出去就行。)
  2. public boolean mkdir()。只能创建一级文件夹。
  3. public boolean mkdirs()。可以创建多级文件夹。(强大!)

删除文件

  1. public boolean delete()。删除文件、空文件夹。

注意:delete方法默认只能删除文件和空文件夹,删除后的文件不会进入回收站

常用方法3:遍历文件夹

  1. public String[] list()。获取当前目录下所有的”一级文件名称“到一个字符串数组中去返回。
  2. public File[] listFiles()。获取当前目录下所有的”一级文件对象“到一个文件对象数组中去返回(重点)。

注意事项:

  1. 当主调是文件,或者路径不存在时,返回null。
  2. 当主调是空文件夹时,返回一个长度为0的数组。
  3. 当主调是一个有内容的文件夹时,将里面所有一级文件和文件夹的路径放在File数组中返回。
  4. 当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏文件。
  5. 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null。

前置:方法递归

为了实现文件的多级操作。

是什么?

  • 递归是一种算法,在程序设计语言中广泛应用。
  • 从形式上说:方法调用自身的形式称为方法递归( recursion)。

形式

  • 直接递归:方法自己调用自己。(比较常见。)
  • 间接递归:方法调用其他方法,其他方法又回调方法自己。

注意

  • 递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出错误
  • 递归调用的特点是:一层一层调用,再一层一层往回返。

递归算法三要素

以求阶乘为例。

  1. 递归的公式: f(n) = f(n-1) * n;
  2. 递归的终结点:f(1) 。
  3. 递归的方向必须走向终结点。

递归文件搜索

莫得公式,怎么办?

案例:在D:\\判断下搜索QQ.exe这个文件,然后直接输出。

1
2
3
4
5
6
1.先调用文件夹的listFiles方法,获取文件夹的一级内容,得到一个数组
2.然后再遍历数组,获取数组中的File对象
3.因为File对象可能是文件也可能是文件夹,所以接下来就需要判断
判断File对象如果是文件,就获取文件名,如果文件名是`QQ.exe`则打印,否则不打印
判断File对象如果是文件夹,就递归执行1,2,3步骤
所以:把12,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
/**
* 目标:掌握文件搜索的实现。
*/
public class RecursionTest3 {
public static void main(String[] args) throws Exception {
searchFile(new File("D:/") , "QQ.exe");
}

/**
* 去目录下搜索某个文件
* @param dir 目录
* @param fileName 要搜索的文件名称
*/
public static void searchFile(File dir, String fileName) throws Exception {
// 1、把非法的情况都拦截住
if(dir == null || !dir.exists() || dir.isFile()){
return; // 代表无法搜索
}

// 2、dir不是null,存在,一定是目录对象。
// 获取当前目录下的全部一级文件对象。
File[] files = dir.listFiles();

// 3、判断当前目录下是否存在一级文件对象,以及是否可以拿到一级文件对象。
if(files != null && files.length > 0){
// 4、遍历全部一级文件对象。
for (File f : files) {
// 5、判断文件是否是文件,还是文件夹
if(f.isFile()){
// 是文件,判断这个文件名是否是我们要找的
if(f.getName().contains(fileName)){
System.out.println("找到了:" + f.getAbsolutePath());
Runtime runtime = Runtime.getRuntime();
runtime.exec(f.getAbsolutePath());
}
}else {
// 是文件夹,继续重复这个过程(递归)
searchFile(f, fileName);
}
}
}
}
}

案例:删除一个非空文件夹。

1
2
1、File默认不可以删除非空文件夹
2、我们需要遍历文件夹,先删除里面的内容,再删除自己。

代码:

得空补上。

前置:字符集

常见字符集

标准ASCII字符集

  • ASCII(American Standard Code for Information Interchange): 美国信息交换标准代码,包括了英文、数字、符号等。
  • 标准ASCII使用1个字节存储一个字符,0~127作为码点,首位是0,总共可表示128个字符,对美国佬来说完全够用。

GBK(汉字内码扩展规范,国标)

  • 汉字编码字符集,包含了2万多个汉字等字符,GBK中一个中文字符编码成两个字节的形式存储。
  • 注意:GBK兼容了ASCII字符集。GBK规定,汉字的第一个字节的第一位必须是1。

Unicode字符集(统一码,也叫万国码)

  • Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集。
  • 提供多重编码方案。最早是UTF-32,4个字节表示一个字符,但是占内存太大,通信效率变低。

UTF-8

  • 是Unicode字符集的一种编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节。
  • 英文字符、数字等只占1个字节(兼容标准ASCII编码),汉字字符占用3个字节。
  • 注意:技术人员在开发时都应该使用UTF-8编码
  • UTF-8编码方式(二进制)
    • 0xxxxxxx (ASCII码)
    • 110xxxxx 10xxxxxx
    • 1110xxxx 10xxxxxx 10xxxxxx
    • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

注意事项

  • 注意1:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码
  • 注意2:英文、数字一般不会乱码,因为很多字符集都兼容了ASCII编码。

字符集的编码、解码操作

String类下的方法和构造器。

编码:把字符按照指定字符集编码成字节。

  1. byte[] getBytes()。使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中。
  2. byte[] getBytes(String charsetName)。使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 。

解码:把字节按照指定字符集解码成字符。

  1. String(byte[] bytes)。通过使用平台的默认字符集解码指定的字节数组来构造新的String。
  2. String(byte[] bytes, String charsetName)。通过指定的字符集解码指定的字节数组来构造新的String。

IO流(字节流)

IO流概述:输入输出流,用来读写数据的。

  • I指Input,称为输入流:负责把数据读到内存中去。
  • O指Output,称为输出流:负责写数据出去。

怎么学?

  1. 先搞清楚IO流的分类、体系。
  2. 再挨个学习每个IO流的作用、用法。

分类

  • 按照流的方向,分为输入流、输出流。
  • 按照数据的最小单位,分为字节流(适合操作所有类型的文件)、字符流(只适合操作纯文本文件)。
  • –>所以一共是4大基础流:
    1. 字节输入流。以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流。
    2. 字节输出流。以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流。
    3. 字符输入流。以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流。
    4. 字符输出流。以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流。

IO流的体系

  • java.io包下:
    • 字节流–>字节输入流(InputStream)、字节输出流(OutputStream);
    • 字符流–>字符输入流(Reader)、字符输出流(Writer)。
  • 上述都是抽象类。然后对应有实现类,是在前面加个File:
    • 字节流–>字节输入流(FileInputStream)、字节输出流(FileOutputStream);
    • 字符流–>字符输入流(FileReader)、字符输出流(FileWriter)。

FileInputStream文件字节输入流

以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去。

构造器

  1. public FileInputStream(File file)。创建字节输入流管道与源文件接通。
  2. public FileInputStream(String pathname)。创建字节输入流管道与源文件接通。(是1的简化写法,也是推荐写法,系统里帮忙创建了File类。)

常用方法

  1. public int read()。每次读取一个字节返回,如果发现没有数据可读会返回-1。返回值表示当前这一次读取的字节个数。
  2. public int read(byte[] buffer)。每次用一个字节数组去读取数据,返回字节数组读取了多少个字节,如果发现没有数据可读会返回-1。

FileInputStream读取一个字节

使用FileInputStream读取文件中的字节数据,步骤如下:

1
2
3
1.创建FileInputStream文件字节输入流管道,与源文件接通。
2.调用read()方法开始读取文件的字节数据。
3.调用close()方法释放资源

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 目标:掌握文件字节输入流,每次读取一个字节。
*/
public class FileInputStreamTest1 {
public static void main(String[] args) throws Exception {
// 1、创建文件字节输入流管道,与源文件接通。
InputStream is = new FileInputStream(("file-io-app\\src\\itheima01.txt"));

// 2、开始读取文件的字节数据。
// public int read():每次读取一个字节返回,如果没有数据了,返回-1.
int b; // 用于记住读取的字节。
while ((b = is.read()) != -1){
System.out.print((char) b);
}

//3、流使用完毕之后,必须关闭!释放系统资源!
is.close();
}
}

存在的问题:

  1. 这种方法读取数据的性能很差(从系统资源里面调)。(开发规范:尽量减少从硬件内存中读取数据的频次。)
  2. 读取汉字会乱码。一个中文在UTF-8编码方案中是占3个字节,采用一次读取一个字节的方式,读一个字节就相当于读了1/3个汉字,此时将这个字节转换为字符,会有乱码。

FileInputStream读取多个字节

为了提高效率,我们可以使用另一个read(byte[] bytes)的重载方法,可以一次读取多个字节,至于一次读多少个字节,就在于你传递的数组有多大。

使用FileInputStream一次读取多个字节的步骤如下:

1
2
3
1.创建FileInputStream文件字节输入流管道,与源文件接通。
2.调用read(byte[] bytes)方法开始读取文件的字节数据。
3.调用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
/**
* 目标:掌握使用FileInputStream每次读取多个字节。
*/
public class FileInputStreamTest2 {
public static void main(String[] args) throws Exception {
// 1、创建一个字节输入流对象代表字节输入流管道与源文件接通。
InputStream is = new FileInputStream("file-io-app\\src\\itheima02.txt");

// 2、开始读取文件中的字节数据:每次读取多个字节。
// public int read(byte b[]) throws IOException
// 每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1.

// 3、使用循环改造。
byte[] buffer = new byte[3];
int len; // 记住每次读取了多少个字节。 abc 66
while ((len = is.read(buffer)) != -1){
// 注意:读取多少,倒出多少。(否则填不满的位置还是存放之前的内容)
String rs = new String(buffer, 0 , len);
System.out.print(rs);
}
// 性能得到了明显的提升!!
// 这种方案也不能避免读取汉字输出乱码的问题!!

is.close(); // 关闭流
}
}

存在的问题:

  1. 使用FileInputStream每次读取多个字节,读取性能得到了提升。
  2. 但读取汉字输出还是会乱码。

使用字节流读取中文,如何保证输出不乱码,怎么解决?

  • 方式1:自己定义一个字节数组与被读取的文件大小一样大,然后使用该字节数组,一次读完文件的全部字节。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1、一次性读取完文件的全部字节到一个字节数组中去。
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt");

// 2、准备一个字节数组,大小与文件的大小正好一样大。
File f = new File("file-io-app\\src\\itheima03.txt");
long size = f.length();
byte[] buffer = new byte[(int) size];

int len = is.read(buffer);
System.out.println(new String(buffer));

//3、关闭流
is.close();
  • 方式2:Java官方为InputStream提供了如下方法,可以直接把文件的全部字节读取到一个字节数组中返回。///// public byte[] readAllBytes() throws IOException。直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回。
1
2
3
4
5
6
7
8
9
10
// 1、一次性读取完文件的全部字节到一个字节数组中去。
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt");

//2、调用方法读取所有字节,返回一个存储所有字节的字节数组。
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));

//3、关闭流
is.close();

注意:一次读取所有字节虽然可以解决乱码问题,但是文件不能过大,如果文件过大,可能导致内存溢出。

—>读写文本内容更适合用字符流。字节流适合做数据的转移,例如,文件复制等。

FileOutputStream文件字节输出流

作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去。

构造器

  1. public FileOutputStream(File file)。创建字节输出流管道与源文件对象接通。
  2. public FileOutputStream(String filepath)。创建字节输出流管道与源文件路径接通。
  3. public FileOutputStream(File file,boolean append)。创建字节输出流管道与源文件对象接通,可追加数据
  4. public FileOutputStream(String filepath,boolean append)。创建字节输出流管道与源文件路径接通,可追加数据

常用方法

  1. public void write(int a)。写一个字节出去。
  2. public void write(byte[] buffer)。写一个字节数组出去。
  3. public void write(byte[] buffer , int pos , int len)。写一个字节数组的一部分出去。
  4. public void close() throws IOException。关闭流。

FileOutputStream往文件中写数据的步骤如下:

1
2
3
1.创建FileOutputStream文件字节输出流管道,与目标文件接通。
2.调用wirte()方法往文件中写数据
3.调用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
/**
* 目标:掌握文件字节输出流FileOutputStream的使用。
*/
public class FileOutputStreamTest4 {
public static void main(String[] args) throws Exception {
// 1、创建一个字节输出流管道与目标文件接通。
// 覆盖管道:覆盖之前的数据
// OutputStream os =
// new FileOutputStream("file-io-app/src/itheima04out.txt");

// 追加数据的管道
OutputStream os =
new FileOutputStream("file-io-app/src/itheima04out.txt", true);

// 2、开始写字节数据出去了
os.write(97); // 97就是一个字节,代表a
os.write('b'); // 'b'也是一个字节
// os.write('磊'); // [ooo] 默认只能写出去一个字节

byte[] bytes = "我爱你中国abc".getBytes();
os.write(bytes);

os.write(bytes, 0, 15);

// 换行符
os.write("\r\n".getBytes());

os.close(); // 关闭流
}
}

案例:字节流复制文件

需求:要复制一张图片,从磁盘D:/resource/meinv.png的一个位置,复制到C:/data/meinv.png位置。

思路:源路径–(创建字节输入流管道)->内存(字节数组)–(创建字节输出流管道)->新路径。

1
2
3
1.需要创建一个FileInputStream流与源文件接通,创建FileOutputStream与目标文件接通
2.然后创建一个数组,使用FileInputStream每次读取一个字节数组的数据,存如数组中
3.然后再使用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
/**
* 目标:使用字节流完成对文件的复制操作。
*/
public class CopyTest5 {
public static void main(String[] args) throws Exception {
// 需求:复制照片。
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("D:/resource/meinv.png");
// 2、创建一个字节输出流管道与目标文件接通。
OutputStream os = new FileOutputStream("C:/data/meinv.png");

System.out.println(10 / 0);
// 3、创建一个字节数组,负责转移字节数据。
byte[] buffer = new byte[1024]; // 1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少。
int len; // 记住每次读取了多少个字节。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}

os.close();
is.close();
System.out.println("复制完成!!");
}
}

总结:字节流非常适合做一切文件的复制操作。 任何文件的底层都是字节,字节流做复制,是一字不漏的转移完全部字节,只要复制后的文件格式一致就没问题!

IO流资源释放

如果前面的操作出现问题,那就没有机会执行close()方法了。

try-catch-finally

  • finally代码区的特点:无论try中的程序是正常执行了,还是出现了异常,最后都一定会执行finally区,除非JVM终止。(注意千万不要在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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Test2 {
public static void main(String[] args) {
InputStream is = null;
OutputStream os = null;
try {
System.out.println(10 / 0);
// 1、创建一个字节输入流管道与源文件接通
is = new FileInputStream("file-io-app\\src\\itheima03.txt");
// 2、创建一个字节输出流管道与目标文件接通。
os = new FileOutputStream("file-io-app\\src\\itheima03copy.txt");

System.out.println(10 / 0);

// 3、创建一个字节数组,负责转移字节数据。
byte[] buffer = new byte[1024]; // 1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少。
int len; // 记住每次读取了多少个字节。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
System.out.println("复制完成!!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源的操作
try {
if(os != null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(is != null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

try-with-resource

JDK7以后的资源释放。

格式如下:

1
2
3
4
5
6
7
try(资源对象1; 资源对象2;){
使用资源的代码
}catch(异常类 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
/**
* 目标:掌握释放资源的方式:try-with-resource
*/
public class Test3 {
public static void main(String[] args) {
try (
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("D:/resource/meinv.png");
// 2、创建一个字节输出流管道与目标文件接通。
OutputStream os = new FileOutputStream("C:/data/meinv.png");
){
// 3、创建一个字节数组,负责转移字节数据。
byte[] buffer = new byte[1024]; // 1KB.
// 4、从字节输入流中读取字节数据,写出去到字节输出流中。读多少写出去多少。
int len; // 记住每次读取了多少个字节。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0, len);
}
System.out.println(conn);
System.out.println("复制完成!!");

} catch (Exception e) {
e.printStackTrace();
}
}
}
  • 注意小括号里面只能放置资源变量(流对象)。
  • 什么是资源?资源都是会实现AutoCloseable接口的,都会有一个close方法。
  • 资源放到小括号里面后,用完会被自动调用其close方法完成资源的释放操作。

IO流(字符流)

  • 字节流:适合复制文件等,不适合读写文本文件。
  • 字符流:适合读写文本文件内容。

作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去。

FileReader文件字符输入流

构造器

  1. public FileReader(File file)。创建字符输入流管道与源文件接通。
  2. public FileReader(String pathname)。创建字符输入流管道与源文件接通。

常用方法

  1. public int read()。每次读取一个字符返回,如果发现没有数据可读会返回-1。
  2. public int read(char[] buffer)。每次用一个字符数组去读取数据,返回字符数组读取了多少个字符,如果发现没有数据可读会返回-1。

FileReader读取文件的步骤如下:

1
2
3
1.创建FileReader对象与要读取的源文件接通
2.调用read()方法读取文件中的字符
3.调用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
/**
* 目标:掌握文件字符输入流。
*/
public class FileReaderTest1 {
public static void main(String[] args) {
try (
// 1、创建一个文件字符输入流管道与源文件接通
Reader fr = new FileReader("io-app2\\src\\itheima01.txt");
){
// 2、一个字符一个字符的读(性能较差)
// int c; // 记住每次读取的字符编号。
// while ((c = fr.read()) != -1){
// System.out.print((char) c);
// }
// 每次读取一个字符的形式,性能肯定是比较差的。

// 3、每次读取多个字符。(性能是比较不错的!)
char[] buffer = new char[3];
int len; // 记住每次读取了多少个字符。
while ((len = fr.read(buffer)) != -1){
// 读取多少倒出多少
System.out.print(new String(buffer, 0, len));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

FileWriter文件字符输出流

作用:以内存为基准,把内存中的数据以字符的形式写出到文件中去。

构造器

  1. public FileWriter(File file)。创建字节输出流管道与源文件对象接通。
  2. public FileWriter(String filepath)。创建字节输出流管道与源文件路径接通。
  3. public FileWriter(File file,boolean append)。创建字节输出流管道与源文件对象接通,可追加数据。
  4. public FileWriter(String filepath,boolean append)。创建字节输出流管道与源文件路径接通,可追加数据。

常用方法

  1. void write(int c)。写一个字符。
  2. void write(String str)。写一个字符串。
  3. void write(String str, int off, int len)。写一个字符串的一部分。
  4. void write(char[] cbuf)。写入一个字符数组。
  5. void write(char[] cbuf, int off, int len)。写入字符数组的一部分。

步骤如下:

1
2
3
1.创建FileWirter对象与要读取的目标文件接通
2.调用write(字符数据/字符数组/字符串)方法读取文件中的字符
3.调用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
/**
* 目标:掌握文件字符输出流:写字符数据出去
*/
public class FileWriterTest2 {
public static void main(String[] args) {
try (
// 0、创建一个文件字符输出流管道与目标文件接通。
// 覆盖管道
// Writer fw = new FileWriter("io-app2/src/itheima02out.txt");
// 追加数据的管道
Writer fw = new FileWriter("io-app2/src/itheima02out.txt", true);
){
// 1、public void write(int c):写一个字符出去
fw.write('a');
fw.write(97);
//fw.write('磊'); // 写一个字符出去
fw.write("\r\n"); // 换行

// 2、public void write(String c)写一个字符串出去
fw.write("我爱你中国abc");
fw.write("\r\n");

// 3、public void write(String c ,int pos ,int len):写字符串的一部分出去
fw.write("我爱你中国abc", 0, 5);
fw.write("\r\n");

// 4、public void write(char[] buffer):写一个字符数组出去
char[] buffer = {'黑', '马', 'a', 'b', 'c'};
fw.write(buffer);
fw.write("\r\n");

// 5、public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
fw.write(buffer, 0, 2);
fw.write("\r\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}

注意事项

字符输出流写出数据后,必须刷新流,或者关闭流,写出去的数据才能生效。

  1. public void flush() throws IOException。刷新流,就是将内存中缓存的数据立即写到文件中去生效!刷新流之后数据流可以继续使用。(数据在内存中装满了会自动刷新写进文件然后继续接。)
  2. public void close() throws IOException。关闭流的操作,包含了刷新!关闭之后就不可以继续用了。

总结

  • 字节流适合做一切文件数据的拷贝(音视频,文本);字节流不适合读取中文内容输出。
  • 字符流适合做文本文件的操作(读,写)。

缓冲流

更新(原因/区别)

  • 前面学的以File开头的实现类称为原始流/低级流。
  • 以Buffered开头称为包装流/处理流。对原始流进行包装,以提高原始流读写数据的性能。

字节缓冲流

原理

  • 字节缓冲输入流自带了8KB缓冲池;字节缓冲输出流也自带了8KB缓冲池。

构造器

  1. public BufferedInputStream(InputStream is)。把低级的字节输入流包装成一个高级的缓冲字节输入流,从而提高读数据的性能。
  2. public BufferedOutputStream(OutputStream os)。把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能。

功能上并无很大变化,性能提升。

字符缓冲流

原理:

  • 自带8K(8192)的字符缓冲池,可以提高字符输入流、输出流读取字符数据的性能。

构造器:

  1. public BufferedReader(Reader r)。把低级的字符输入流包装成字符缓冲输入流管道,从而提高字符输入流读字符数据的性能。
  2. public String readLine()。(新增,按照行读取。)读取一行数据返回,如果没有数据可读了,会返回null。
  3. public BufferedWriter(Writer r)。把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流写数据的性能。
  4. public void newLine()。(新增,换行。)

案例:拷贝出师表并恢复顺序。

原始流、缓冲流的性能分析[重点]

测试用例:

  • 分别使用原始的字节流,以及字节缓冲流复制一个很大视频。

测试步骤:

  1. 使用低级的字节流按照一个一个字节的形式复制文件。
  2. 使用低级的字节流按照字节数组的形式复制文件。
  3. 使用高级的缓冲字节流按照一个一个字节的形式复制文件。
  4. 使用高级的缓冲字节流按照字节数组的形式复制文件。
  • 默认情况下,采用一次复制1024个字节,缓冲流完胜。
  • 一次读取8192个字节时,低级流和缓冲流性能相当。相差的那几毫秒可以忽略不计。
  • 数组越大性能越高,低级流和缓冲流性能相当。相差的那几秒可以忽略不计。
  • 数组大到一定程度,性能已经提高了多少了,甚至缓冲流的性能还没有低级流高。

结论:推荐使用哪种方式提高字节流读写数据的性能?

  • 缓冲流的性能不一定比低级流高,其实低级流自己加一个数组,性能其实是不差。只不过缓冲流帮你加了一个相对而言大小比较合理的数组 。
  • 建议使用字节缓冲输入流、字节缓冲输出流,结合字节数组的方式,目前来看是性能最优的组合。

IO流-转换流

转换流:可以将字节流转换为字符流,并且可以指定编码方案。

  • 解决不同编码时,字符流读取文本内容乱码的问题。
  • 解决思路:先获取文件的原始字节流,再将其按真实的字符集编码转成字符输入流,这样字符输入流中的字符就不乱码了

原因:

  • FileReader默认只能读取UTF-8编码格式的文件。如果使用FileReader读取GBK格式的文件,可能存在乱码,因为FileReader它遇到汉字默认是按照3个字节来读取的,而GBK格式的文件一个汉字是占2个字节,这样就会导致乱码。
  • 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码!

InputStreamReader类

  • InputStreamzhuan转换为Reader,是Reader的子类,也算是字符输入流。
  • 不能单独使用,内部需要封装一个InputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换。

常用方法:

  1. public InputStreamReader(InputStream is)。把原始的字节输入流,按照代码默认编码转成字符输入流(与直接用FileReader的效果一样)。
  2. public InputStreamReader(InputStream is ,String charset)。把原始的字节输入流,按照指定字符集编码转成字符输入流(重点)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InputStreamReaderTest2 {
public static void main(String[] args) {
try (
// 1、得到文件的原始字节流(GBK的字节流形式)
InputStream is = new FileInputStream("io-app2/src/itheima06.txt");
// 2、把原始的字节输入流按照指定的字符集编码转换成字符输入流
Reader isr = new InputStreamReader(is, "GBK");
// 3、把字符输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(isr);
){
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}


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

OutputStreamWriter类

如何控制写出去的字符使用的字符集编码?

  1. 调用String提供的getBytes方法。
  2. 用字符输出转换流实现。
  • OutputStream转换为Writer,是Writer的子类,算是字符输出流。
  • 不能单独使用,内部需要封装一个OutputStream的子类对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换。
  • 作用:可以控制写出去的字符使用什么字符集编码。
  • 解决思路:获取字节输出流,再按照指定的字符集编码将其转换成字符输出流,以后写出去的字符就会用该字符集编码了。

常用方法:

  1. public OutputStreamWriter(OutputStream os)。可以把原始的字节输出流,按照代码默认编码转换成字符输出流。
  2. public OutputStreamWriter(OutputStream os,String charset)。可以把原始的字节输出流,按照指定编码转换成字符输出流(重点)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class OutputStreamWriterTest3 {
public static void main(String[] args) {
// 指定写出去的字符编码。
try (
// 1、创建一个文件字节输出流
OutputStream os = new FileOutputStream("io-app2/src/itheima07out.txt");
// 2、把原始的字节输出流,按照指定的字符集编码转换成字符输出转换流。
Writer osw = new OutputStreamWriter(os, "GBK");
// 3、把字符输出流包装成缓冲字符输出流
BufferedWriter bw = new BufferedWriter(osw);
){
bw.write("我是中国人abc");
bw.write("我爱你中国123");

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

IO流-打印流

PrintStream/PrintWriter(打印流)

  • 作用:打印流可以实现更方便、更高效的打印数据出去,能实现打印啥出去就是啥出去。
  • 优势:能上都是使用方便,性能高效(核心优势)。

PrintStream构造器

继承自字节输出流OutputStream,支持写字节。

  1. public PrintStream(OutputStream/File/String)。打印流直接通向字节输出流/文件/文件路径。
  2. public PrintStream(String fileName, Charset charset)。可以指定写出去的字符编码。
  3. public PrintStream(OutputStream out, boolean autoFlush)。可以指定实现自动刷新。
  4. public PrintStream(OutputStream out, boolean autoFlush, String encoding)。可以指定实现自动刷新,并可指定字符的编码。

PrintStream常用方法

  1. public void println(Xxx xx)。打印任意类型的数据出去。
  2. public void write(int/byte[]/byte[]一部分)。可以支持写字节数据出去。

PrintWriter构造器

继承自字符输出流Writer,支持写字符。

  1. public PrintWriter(OutputStream/Writer/File/String)。打印流直接通向字节输出流/文件/文件路径。
  2. public PrintWriter(String fileName, Charset charset)。可以指定写出去的字符编码。
  3. public PrintWriter(OutputStream out/Writer, boolean autoFlush)。可以指定实现自动刷新。
  4. public PrintWriter(OutputStream out, boolean autoFlush, String encoding)。可以指定实现自动刷新,并可指定字符的编码。

PrintWriter常用方法

  1. public void println(Xxx xx)。打印任意类型的数据出去。
  2. public void write(int/String/char[]/..)。可以支持写字符数据出去。

PrintStream和PrintWriter的区别

  • 打印数据的功能上是一模一样的:都是使用方便,性能高效(核心优势)。
  • PrintStream继承自字节输出流OutputStream,因此支持写字节数据的方法。
  • PrintWriter继承自字符输出流Writer,因此支持写字符数据出去。

注意:高级流方法不能直接追加true来控制,需要先包装成低级流。

1
new PrintWriterStream(new FileOutputStream("path",true))

输出语句的重定向。

  • 打印流的一种应用。
  • 可以把输出语句的打印位置改到某个文件中去。
  • System.out.println(“老骥伏枥”)这个语句的out实际上是帮我们创建了一个打印对象,然后默认打印到控制台上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PrintTest2 {
public static void main(String[] args) {
System.out.println("老骥伏枥");
System.out.println("志在千里");

try ( PrintStream ps = new PrintStream("io-app2/src/itheima09.txt"); ){
// 把系统默认的打印流对象改成自己设置的打印流
System.setOut(ps);//定向到这里了

System.out.println("烈士暮年");
System.out.println("壮心不已");
} catch (Exception e) {
e.printStackTrace();
}
}
}

IO流-数据流

需求:把数据和数据的类型一并写到文件中去,读取的时候也将数据和数据类型一并读出来。有两个:DataInputStream和DataOutputStream。

DataOutputStream类

允许把数据和其类型一并写出去。

构造器

  1. public DataOutputStream(OutputStream out)。创建新数据输出流包装基础的字节输出流

常用方法

  1. public final void writeByte(int v) throws IOException。将byte类型的数据写入基础的字节输出流。
  2. public final void writeInt(int v) throws IOException。将int类型的数据写入基础的字节输出流。
  3. public final void writeDouble(Double v) throws IOException。将double类型的数据写入基础的字节输出流。
  4. public final void writeUTF(String str) throws IOException。将字符串数据以UTF-8编码成字节写入基础的字节输出流。
  5. public void write(int/byte[]/byte[]一部分)。支持写字节数据出去。

DataInputStream类

用于读取数据输出流写出去的数据。

构造器

  1. public DataInputStream(InputStream is)。创建新数据输入流包装基础的字节输入流

常用方法

  1. Public final byte readByte() throws IOException。读取字节数据返回。
  2. public final int readInt() throws IOException。读取int类型的数据返回。
  3. public final double readDouble() throws IOException。读取double类型的数据返回。
  4. public final String readUTF() throws IOException。读取字符串数(UTF-8)据返回。
  5. public int readInt()/read(byte[])。支持读字节数据进来。

注意:二者需要搭配使用,存进去的是什么类型,读取就要怎么读,而且存进去的数据并不是为了读者直接来看的。(在通信中非常方便。)

IO流-序列化流

序列化

  • 对象序列化:把Java对象写入到文件中去。
  • 对象反序列化:把文件里的Java对象读出来。

ObjectOutputStream类

可以把Java对象进行序列化:把Java对象存入到文件中去。

构造器

  1. public ObjectOutputStream(OutputStream out)。创建对象字节输出流,包装基础的字节输出流

常用方法

  1. public final void writeObject(Object o) throws IOException。把对象写出去。

注意:对象如果要参与序列化,必须实现序列化接口(java.io.Serializable)!相当于一个标记,虽然接口里面什么都没有,但是虚拟机需要。

ObjectInputStream类

可以把Java对象进行反序列化:把存储在文件中的Java对象读入到内存中来。

构造器

  1. public ObjectInputStream(InputStream is)。创建对象字节输入流,包装基础的字节输入流

常用方法

  1. public final Object readObject()。把存储在文件中的Java对象读出来。

注意:如果成员变量中加transient修饰符,这个成员变量将不参与序列化。

如果要一次系列化多个对象,怎么做?

  • 用一个ArrayList集合存储多个对象,然后直接对集合进行序列化即可。
  • 注意:ArrayList集合已经实现了序列化接口!

补充知识: IO框架

什么是框架

  • 解决某类问题,编写的一套类、接口等,可以理解成一个半成品,大多框架都是第三方研发的。
  • 好处:在框架的基础上开发,可以得到优秀的软件架构,并能提高开发效率
  • 框架的形式:一般是把类、接口等编译成class形式,再压缩成一个.jar结尾的文件发行出去。

什么是IO框架

  • 封装了Java提供的对文件、数据进行操作的代码,对外提供了更简单的方式来对文件进行操作,对数据进行读写等。

Commons-io框架

Commons-io是apache开源基金组织提供的一组有关IO操作的小框架,目的是提高IO流的开发效率。

FileUtils类提供的部分方法展示

  1. public static void copyFile(File srcFile, File destFile)。复制文件。
  2. public static void copyDirectory(File srcDir, File destDir)。复制文件夹。
  3. public static void deleteDirectory(File directory)。删除文件夹。
  4. public static String readFileToString(File file, String encoding)。读数据。
  5. public static void writeStringToFile(File file, String data, String charname, boolean append)。写数据。

IOUtils类提供的部分方法展示

  1. public static int copy(InputStream inputStream, OutputStream outputStream)。复制文件。
  2. public static int copy(Reader reader, Writer writer)。复制文件。
  3. public static void write(String data, OutputStream output, String charsetName)。写数据。

使用指北

  1. 下载:Commons IO – Download Apache Commons IO Binary的zip包。
  2. 复制“commons-io-2.11.0.jar”包。
  3. 项目文件夹邮件,新建Directory,取名“lib”。粘贴。
  4. 右键lib文件夹,选择“Add as library”。
  5. 可以使用了。

最后,其实Java原生自己从1.7也提供了File类的copy、readString等方法来做,但是功能上还没有第三方框架强大。