IO流
为什么需要IO流: IO流可以让我们对硬盘中的文件进行读写操作;
IO流的分类:
- 根据数据流向:输入流和输出流
- 根据数据类型:字节流和字符流
使用场景: 复制文件、文件的上传与下载;
字节流
概述:
字节流从字面意思上就是它传输的是字节数据,不是字符数据更不是字符串或其他数据;其中InputStream和OutputStream是字节流的顶层抽象类;字节流一般用来处理音频和图片数据,处理中文一般用字符流;
FileInputStream
概述:
FileInputStream是InputStream的直接子类,用于对文件的读操作;
FileInputStream常用方法:
方法 | 方法说明 |
---|---|
int read() | 从输入流中读取一个字节的数据 |
int read(byte[] b) | 从输入流中读取最多b.length个字节数据到字节数组中 |
int read(byte[] b, int off, int len) | 从输入流中读取最多len个字节数据到字节数组中 |
方法的具体说明:
-
int read():该方法的返回值为读取到的内容,如果读取到了文件末尾就会返回-1;
-
int read(byte[] b):该方法的返回值为读取到的内容的长度(不是数组的长度),实际读取到的数据存储在字节数组中;数组的长度一般为1024的倍数;
代码示例一:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo03 {
public static void main(String[] args) {
InputStream in = null;
// 创建目标源
String path = "IO\\a.txt";
try {
// 创建输入流对象
in = new FileInputStream(path);
// 具体操作
int len = -1;
// 判断len是否等于-1的原因是:当读取到文件末尾时,会返回-1
// 这里的条件操作是:先获取内容值并赋值给len,然后len与-1进行比较
while ((len = in.read()) != -1) {
System.out.print((char)len);
}
} catch (IOException e) {
e.printStackTrace();
} finally { // 释放资源
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
代码示例二:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo04 {
public static void main(String[] args) throws IOException {
// 创建目标源
String path = "IO\\a.txt";
// 创建输入流对象
InputStream in = new FileInputStream(path);
// 具体操作
byte[] data = new byte[1024];
int len = -1;
while ((len = in.read(data)) != -1) {
// 这里通过String的构造方法将字节数组转为字符串
System.out.println(new String(data, 0, len));
}
// 释放资源
in.close();
}
}
问题: 这里为什么使用String(byte[] b, offset, length)这个构造方法呢?
答:因为这里的获取到的字节数据长度len未必达到了字节数组的长度,而我们想要的只是获取到的len长度的字节数据,所以我们需要对字节数组进行截取;
FileOutputStream
概述:
文件输出流,向文件中写入数据,其是OutputStream的直接子类,完成对文件的写操作;
FileOutputStream的常用方法:
方法 | 方法说明 |
---|---|
void write(int b) | 将指定的字节数据写入到目标文件中,一次只写入一个字节的数据 |
void write(byte[] b) | 将b.length长度的字节从指定的字节数组写到到目标文件中 |
void write(byte[] b, int off, int len) | 将字节数组中截取len长度字节的数据写入到目标文件中 |
代码示例:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Demo05 {
public static void main(String[] args) throws IOException {
// 创建目标文件源
String target = "IO\\b.txt";
// 创建输出流对象
OutputStream out = new FileOutputStream(target);
// 具体操作
byte[] data = {97, 98, 99, 100, 101}; // 对应的英文字母为a,b,c,d,e
out.write(data);
// 释放资源
out.close();
}
}
文件的复制
概述:
学过了文件的读与取,那么我们现在就可以完成将数据从一个文件中复制到另一个文件中了,那么如何操作呢?
具体步骤:
- 创建源文件路径
- 创建目标文件路径
- 创建输入流对象
- 创建输出流对象
- 将数据读出来,然后写入到目标文件中
- 关闭资源
代码示例一: 一个字节一个字节的复制
import java.io.*;
public class Demo06 {
public static void main(String[] args) throws IOException {
// 创建源文件路径
String source = "IO\\a.txt";
// 创建目标文件路径
String target = "IO\\b.txt";
// 创建输入流对象
InputStream in = new FileInputStream(source);
// 创建输出流对象
OutputStream out = new FileOutputStream(target);
// 具体操作
int len = -1;
while ((len = in.read()) != -1) {
out.write(len);
}
// 释放资源
// 释放资源的原则:先创建的后释放
out.close();
in.close();
}
}
代码示例二: 一次读写一个字节数组
import java.io.*;
/**
* 一般建议用try-catch-finally而不是throws
*/
public class Demo07 {
public static void main(String[] args) {
InputStream in = null;
OutputStream out = null;
// 创建源文件路径
String source = "IO\\a.txt";
// 创建目标文件路径
String target = "IO\\b.txt";
try{
// 创建输入流
in = new FileInputStream(source);
// 创建输出流
out = new FileOutputStream(target);
// 具体操作
byte[] data = new byte[1024];
int len = -1;
while ((len = in.read(data)) != -1) {
// 如果不进行截取的话,可能会造成之前读取的字节再次写入的情况
// 因为如果len<data.length的话,那么之前读取的字节依旧充斥在[len+1,data.length]的位置
// 不进行截取,就会将之前的数据再去写入
out.write(data, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try{
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
需要注意的问题
问题一:
OutputStream out = new FileOutputStream(“IO\a.txt”); 到底做了什么?
答:我们可能会发现,如果你没有创建a.txt这个文件,但是当你执行了该语句之后,a.txt就已经被创建好了,我们都知道我们之前创建文件是通过File类的createNewFile()方法来创建的,所以,如果你没有创建指定的目标文件的时候,它底层就会调用file对象来创建该文件;如果,将这些代码反复执行,很容易观察出,每次执行完,a.txt里面都是一样的文本,并没有追加,这是因为每次执行该语句时,都会将文件中的内容清除;
总结:
-
如果a.txt不存在,那么会自动创建这个文件;
-
如果a.txt已经存在了,那么就会将a.txt中的内容清空;
问题二:
上面说到了不能进行追加,那么有没有办法让它追加而不是每次都清空呢?
答:通过FileOutputStream的另外一个构造方法就可以实现,
public FileOutputStream(String name,boolean append),创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头;如果第二个参数为false,则先将内容清空在写入;
即,将原来的构造方法,再添加一个boolean参数即可:
OutputStream out = new FileOutputStream("IO\\a.txt", true);
问题三:
字符串与byte[]数组之间如何转换呢?
- String -----> byte[]:byte[] bytes = “中国”.getBytes();
- byte[] ------> String:String s = new String(bytes, 0, len);
字节缓冲流
为什么需要字节缓冲流:
之前我们如何需要进行读写的话,都是通过每次读写一个字节数据或每次读写一个字节数组的数据,效率低下,通过字节缓冲流将字节数在内存中暂时存储,然后再从内存中写入到文件中,提高了效率;
概念:
字节缓冲流是在字节流的基础上,添加一个大小为8192的数组缓冲区,用来提供效率;
BufferedOutputStream
构造方法
BufferedOutputStream(OutputStream out) // 创建字节缓冲输出流对象
BufferedInputStream
构造方法
BufferedInputStream(InputStream in) // 创建字节缓冲输入流对象
--------------------------------------------------------------------------------------
BufferedInputStream(InputStream in, int size) // 创建具有指定缓冲区大小的缓冲流
代码示例
利用缓冲字节流来实现图片的复制:
import java.io.*;
public class Demo07 {
public static void main(String[] args) throws IOException {
// 创建源文件路径
String source = "IO\\a.png";
// 创建目标文件路径
String target = "IO\\b.png";
// 创建缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
// 创建缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target));
// 具体操作
byte[] data = new byte[1024];
int len = -1;
while ((len = bis.read(data)) != -1) {
bos.write(data, 0, len);
}
// 释放资源
bos.close();
bis.close();
}
}
需要注意的问题
问题一:
我们都知道我们的字节流是对文件进行操作,那为什么这里构造方法中的参数值字节流对象而不是file对象呢?
答:因为字节缓冲流只是提供了一个默认大小为8192的字节数组缓冲区,而真正做事情,对数据进行传输操作的仍是字节流;
问题二:
字节缓冲流中的方法与文件字节流中的方法一样;它只是提供了一个默认长度为8192的字节缓冲数组;
问题三:
复制文件有哪几种?
答:四种;
- 字节流一次读写一个字节;
- 字节流一次读写一个字节数组;
- 缓冲流一次读写一个字节;
- 缓冲流一次读写一个字节数组;