进入java IO部分的学习,首先学习IO基础,内容如下。需要了解流的概念、分类还有其他一些如集合与文件的转换,字符编码问题等,这次先学到字节流的读写数据,剩余下次学完。

一、IO基础

1、背景

1.1 数据存储问题

变量、数组、对象和集合中存储的数据是暂时存在的,一旦程序结束它们就会丢失。

解决:为了永久保存程序创建的数据,需要将其保存到磁盘文件中。

1.2 流与IO

1)流
是一种抽象概念,是对数据传输的总称。即数据在设备间的传输称为流,流的本质是数据传输

  • Java 中所有数据都是用流读写的。
  • 流是一组有序的数据序列(以输入流的形式获取,输出流的形式输出),将数据从一个地方带到另一个地方。(可类比水管里水的流动)
    • 输入:将数据从各种输入设备(包括文件、键盘等)中读取到内存中
    • 输出:将数据写入到各种输出设备(比如文件、显示器、磁盘等)
  • 流相关的类都封装在 java.io 包中,且每个数据流都是一个对象。

可分为输入和输出两种模式

输入流:文件、网络、数据库及其他数据源—(输入流)—>目的地
输出流:源—(输出流)—>文件、网络、数据库及其他数据源

2)Java 的 I/O(输入/输出)技术
作用:处理设备间数据传输问题

将数据保存到文本文件和二进制文件中, 以达到永久保存数据的要求。常见应用:文件复制;文件上传;文件下载

2、流的分类

2.1 按流的方向

1)输入流(input)
==用于读数据==

所有输入流类都是 InputStream 抽象类(字节输入流)和 Reader 抽象类(字符输入流)的子类

InputStream(字节输入流)结构——>了解即可

1
2
3
4
5
6
7
8
9
10
11
12
FileInputStream  文件输入流
PipedInputStream 管道输入流
ObjectInputStream 对象输入流

FilterInputStream 过滤器输入流
- PushBackInputStream 回压输入流
- BufferedInputStream 缓冲输入流
- DataInputStream 数据输入流

SequenceInputStream 顺序输入流
ByteArrayInputStream 字节数组输入流
StringBufferInputStream 缓冲字符串输入流

InputStream 类常用方法——>掌握

1
2
3
4
5
6
7
8
9
10
11
12
read()方法(重载)3
- int read():从输入流读入一个 8 字节的数据,将它转换成一个 0~ 255 的整数,返回一个整数,如果遇到输入流的结尾返回 -1
- int read(byte[] b):从输入流读取若干字节的数据保存到参数 b 指定的字节数组中,返回的字节数表示读取的字节数,如果遇到输入流的结尾返回 -1
- int read(byte[] b,int off,int len):从输入流读取若干字节的数据保存到参数 b 指定的字节数组中,其中 off 是指在数组中开始保存数据位置的起始下标,len 是指读取字节的位数。返回的是实际读取的字节数,如果遇到输入流的结尾则返回 -1

close():关闭数据流,当完成对数据流的操作之后需要关闭数据流
available():返回可以从数据源读取的数据流的位数
skip(long n):从输入流跳过参数 n 指定的字节数目

markSupported():判断输入流是否可以重复读取
mark(int readLimit):如果输入流可以被重复读取,从流的当前位置开始设置标记,readLimit 指定可以设置标记的字节数
reset():使输入流重新定位到刚才被标记的位置,这样可以重新读取标记过的数据

两点注意:

最后 3 个方法一般结合使用,先用 markSupported() 判断,如果可以重复读取,则用 mark(int readLimit) 方法进行标记,标记完成后可以用 read() 方法读取标记范围内的字节数,最后用 reset() 方法使输入流重新定位到标记的位置,继而完成重复读取操作。

Java 中的字符是 Unicode 编码(双字节),而 InputerStream 是用来处理单字节的,在处理字符文本时不是很方便。这时可以使用 Java 的文本输入流 Reader 类,该类是字符输入流的抽象类,即所有字符输入流的实现都是它的子类,该类的方法与 InputerSteam 类的方法类似,不再赘述。

2)输出流(Output)
==用于写数据==

所有输出流类都是 OutputStream 抽象类(字节输出流)和 Writer 抽象类(字符输出流)的子类

OutputStream(字节输出流)结构——>了解即可

1
2
3
4
5
6
7
8
FileOutputStream  文件输出流
PipedOutputStream 管道输出流
ObjectOutputStream 对象输出流
FilterOutputStream 过滤器输出流
PrintStream 打印输出流
BufferedOutputStream 缓冲输出流
DataOutputStream 数据输出流
ByteArrayOutputStream 字节数组输出流

OutputStream 类常用方法——>掌握

1
2
3
4
5
6
write()方法(重载)3
- int write(b):将指定字节的数据写入到输出流
- int write(byte[] b):将指定字节数组的内容写入输出流
- int write(byte[] b,int off,int len):将指定字节数组从 off 位置开始的 len 字节的内容写入输出流
close():关闭数据流
flush():刷新数据流

2.2 按数据单位

1
2
3
4
一般IO流是按数据单位(即数据类型)来分的
流的使用:
- 如果数据通过window自带的记事本软件打开,还可以读懂里面的内容,就用字符流,否则用字节流
- 如果不知道使用哪种类型的流,就用字节流(字节流是万能的流)

1)字节流
==写数据==:
字节流抽象基类(父类)

1
2
3
InputStream:是字节输入流的所有类的超类(父类)
OutputStream:是字节输出流的所有类的超类
子类名特点:子类名称都是以其父类名作为子类名的后缀 (可参考上一节的输入流和输出流内容)

FileOutputStream:文件输出流用于将数据写入File

FileOutputStream(String name):创建文件输出流以指定的名称写入文件

使用字节输出流写数据的步骤:(创建对象–>写数据–>释放资源)

1
2
3
4
5
6
7
1、创建字节输出流对象(做了3件事情)
- 调用系统功能创建了文件
- 创建字节输出流对象
- 让字节输出流对象指向文件

2、调用字节输出流对象的写数据方法
3、释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)

示例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
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo1 {
public static void main(String[] args) throws IOException { // 抛出异常
// 创建字节输出流对象
// FileOutputStream(String name):创建文件输出流以指定的名称写入文件
FileOutputStream fos=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");
/*
做了三件事情:
1:调用系统功能创建了文件
2:创建字节输出流对象
3:让字节输出流对象指向创建好的文件
*/
//void write(int b):将指定的字节写入此文件输出流
fos.write(97); // a
fos.write(57); // 9 (是字符不是数字)
fos.write(55); // 7 (是字符不是数字)

// 最后都要释放资源
// void close():关闭此文件输出流并释放与此流相关联的任何系统资源
fos.close();
}
}

运行结果(会生成一个fos.txt文件,打开查看内容如下)

1
a97

字节流写数据的三种方式(write()方法的重载–3个)

1
2
3
int write(b):将指定字节的数据写入到输出流
int write(byte[] b):将指定字节数组的内容写入输出流
int write(byte[] b,int off,int len):将指定字节数组从 off 位置开始的 len 字节的内容写入输出流

示例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
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class FileOutputStreamDemo2 {
public static void main(String[] args) throws IOException { // 抛出异常
/**
* 1.创建对象
*/
// 创建字节输出流对象
// FileOutputStream(String name):创建文件输出流以指定的名称写入文件
FileOutputStream fos1=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");

/**
* 2.写数据(3种方式)
*/
// void write(int b):将指定的字节写入此文件输出流
// fos1.write(97);
// fos1.write(98);
// fos1.write(99);
// fos1.write(100);
// fos1.write(101); // 结果:abcde
// void write(byte[] b):将指定字节数组的内容写入输出流
// byte[] bys={97,98,99,100,101}; //方法1:创建数组
byte[] bys="abcde".getBytes(); //方法2:getBytes()方法
// fos1.write(bys); // 结果:abcde
// void write(byte[] b,int off,int len):将指定字节数组从 off 位置开始的 len 字节的内容写入输出流
// fos1.write(bys,0,bys.length); // 结果:abcde
fos1.write(bys,1,3); // 结果:bcd

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

getBytes()方法使用注意:

1
2
3
getBytes(String charsetName): 使用指定的字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

getBytes(): 使用平台的默认字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

字节流写数据的两个小问题
a、实现换行(添加换行符)

1
2
3
windows:\r\n
linux:\n
mac:\r

b、实现追加写入

1
2
3
4
public FileOutputStream(String name,boolean append)

创建文件输出流以指定名称写入文件。若第二个参数为true,则字节将写入文件的末尾而不是开头(开头覆盖,末尾追加)
- 默认是false,即写在开头,这样每运行一次都会覆盖原内容,追加写入时要改为true

示例3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo3 {
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
//boolean append为true实现追加(将字节写入末尾而不是开头)
FileOutputStream fos3=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt",true);
// 写数据
for(int i=0;i<10;i++){
fos3.write("hello".getBytes()); // getBytes()方法
fos3.write("\r\n".getBytes()); // 每写完一次做一个换行
}
// 释放资源
fos3.close();
}
}

补充:

try-catch异常处理(一般文件相关的异常直接用throws抛出即可,但try-catch异常捕获方式也要了解,不懂的可以回去看异常处理部分)

finally:在异常处理时提供finally块来执行所有清除操作。如IO流中的释放资源。特点:被finally控制的语句一定会执行,除非JVM退出

示例4

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
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo4 {
public static void main(String[] args) {
FileOutputStream fos4 = null; // 初始化文件对象为null(全局+初始化)
try {
// 创建对象
// fos4 = new FileOutputStream("Z:\\Ultimate JavaCode\\src\\test6\\fos4.txt"); //文件不存在会抛出FileNotFoundException异常
fos4 = new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");
// 写入数据
fos4.write("javaEE".getBytes());
// 关闭资源
fos4.close();
// 若写入操作存在异常,则直接跳到catch捕获,不会执行关闭资源操作,这时需要引入finally
} catch (IOException e) { // 捕获异常
e.printStackTrace(); //存在异常时,追踪异常信息
} finally { // 加入finally实现释放资源
if(fos4!=null){ // 文件不为null时,才需要释放资源(防止文件为null时关闭资源出现空指针异常NullPointerException)
try{
fos4.close(); // 释放资源
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}

==读数据==:
字节流读数据(一次读一个字节数据)

1
2
3
4
5
6
7
8
9
10
FileInputStream:从文件系统中的文件获取输入字节
- FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名

使用字节输入流读数据的步骤:(创建对象-->读数据-->释放资源)
1、创建字节输入流对象(做了3件事情)
- 调用系统功能创建了文件
- 创建字节输出流对象
- 让字节输出流对象指向文件
2、调用字节输入流对象的读数据方法
3、释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)

字节流读数据的3种方式(read()方法重载–3个)

1
2
3
int read():从输入流读入一个 8 字节的数据,将它转换成一个 0~ 255 的整数,返回一个整数,如果遇到输入流的结尾返回 -1
int read(byte[] b):从输入流读取若干字节的数据保存到参数 b 指定的字节数组中,返回的字节数表示读取的字节数,如果遇到输入流的结尾返回 -1
int read(byte[] b,int off,int len):从输入流读取若干字节的数据保存到参数 b 指定的字节数组中,其中 off 是指在数组中开始保存数据位置的起始下标,len 是指读取字节的位数。返回的是实际读取的字节数,如果遇到输入流的结尾则返回 -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
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo1 {
public static void main(String[] args) throws IOException {
// 创建对象
FileInputStream fis=new FileInputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");
// 读数据
// int bys= fis.read();
// System.out.println(bys); // 结果:98(文件的字符是'b')
// System.out.println((char)bys); // 大转小,强制转换,显示原字符(结果:b)

/*
字节流读数据标准代码如下
1.fis.read():读数据
2.by=fis.read():将读取的数据赋值给by
3.by!=-1:判断读取到的数据是否是-1(到达文件末尾)
*/
int by; // 类型声明
while((by=fis.read())!=-1){
System.out.print((char)by); // 强转显示原字符(结果:bcd),注意用print而不是println
}
// 释放资源
fis.close();
}
}

案例2:字节流复制文本文件
需求:把文本文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileInputStreamDemo2{
public static void main(String[] args) throws IOException {
// 创建对象
FileInputStream fis=new FileInputStream("D:\\文本复制案例.txt"); // 从指定路径下读取要复制的文本文件(数据源)
FileOutputStream fos=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");//目的地
// 先从数据源(fis)读取,后写入目的地(fos)
int by;
while((by=fis.read())!=-1){ // 一次读取一个字节,直到读完(到文件末尾)
fos.write(by); // 一次写入一个字节
}
// 释放资源
fos.close();
fis.close(); // 先创建的后释放
}
}

案例3
需求:字节流读数据(一次读一个字符数组),用到int read(byte[] b)构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo3 {
public static void main(String[] args) throws IOException {
// 创建对象
FileInputStream fis=new FileInputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");
byte[] bys=new byte[1024]; // 1024及其整数倍
int len;
// 读取数据
while((len=fis.read(bys))!=-1){ // 一次读一个字符数组,用到int read(byte[] b)构造方法
System.out.println(new String(bys,0,len)); // new String方法转换为字符串输出到控制台
}
// 释放资源
fis.close();
}
}

字节缓冲流

1
2
BufferedOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以将字节写入基础输出流,而不必为写入的每个字节调用底层系统。
BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。当从流中读取或跳过字节时内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节。

构造方法

1
2
3
4
5
字节缓冲输出流:BufferedOutputStream(OutputStream out)
字节缓冲输入流:BufferedInputStream(InputStream in)

问题:构造方法里需要的是字节流而不是具体的文件或路径?
原因:字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作。

示例

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

public class BufferStreamDemo1 {
public static void main(String[] args) throws IOException {
// FileOutputStream fos=new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt");
// BufferedOutputStream bos=new BufferedOutputStream(fos);
// 创建字节缓冲输出流对象
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt"));
// 操作1:写数据
bos.write("hello\n".getBytes());
bos.write("javaEE\n".getBytes());
// 创建字节缓冲输入流对象(用于读数据)
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("C:\\Users\\ASUS\\Desktop\\project1\\src\\fos.txt"));

// 操作2:读数据
// 方法1:一次读取一个字节数据
// int by;
// while((by=bis.read())!=-1){
// System.out.println((char)by);
// }
// 方法2:一次读取一个字节数组
byte[] bys=new byte[1024]; // 1024及其整数倍
int len;
while((len=bis.read(bys))!=-1){
System.out.println(new String(bys,0,len)); // 转换为字符串输出到控制台
}
// 释放资源
bos.close();
}
}

运行结果(打开fos.txt文件,显示如下)

1
2
hello
javaEE