一、IO概述
- 概念
IO流用来处理设备之间的数据传输,包括上传文件和下载文件。Java对数据的操作是通过流的方式。Java用于操作流的对象都在IO包中。 - 分类
- 按照数据流向
输入流:只能从中读取数据,不能向其写入数据。
输出流:只能向其写入数据,不能从中读取数据。
这里的输入,输出都是从程序运行所在内存的角度来划分的。
Java的输入流主要有InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类。他们都是一些抽象基类,无法直接创建实例。 - 按照数据类型
字节流:操作的数据单元是8位的字节。
字符流:操作的数据单元是16位的字符。
什么情况下使用哪种流呢?
- 任何文件都可以使用字节流。
- 纯文本文件使用字符流,例如txt文件、xml文件、html文件等。
*建议:如果是纯文本文件建议使用字符流,图片、电影、音乐、压缩包等,使用字节流。
![](https://img.haomeiwen.com/i5887463/2f0f76ac568851e5.png)
二、流常用基类
1.字节流的抽象基类
字节流主要由InputStream和OutputStream作为基类,这两个类都是抽象类。
*注意:
由这两个个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如InputStream子类FileInputStream,OutputStream子类FileOutputStream。
2.字符流的抽象基类
字符流则主要由Reader和Writer作为基类。这两个类也都是抽象类。
*注意:由这两个个类派生出来的子类名称都是以其父类名作为子类名的后缀。
例如:Reader的子类FileReader, Writer的子类FileWriter
三、OutputStream 字节流写数据
1.FileOutputStream的构造方法
//创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
//file - 为了进行写入而打开的文件
FileOutputStream(File file)
//创建一个向具有指定名称的文件中写入数据的输出文件流。
//name - 与系统有关的文件名
FileOutputStream(String name)
//创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
//file - 为了进行写入而打开的文件。
//append - 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处
FileOutputStream(File file, boolean append)
2.字节流写数据的方法
//将指定字节写入此文件输出流。
//b - 要写入的字节
public void write(int b)
//将 b.length 个字节从指定 byte 数组写入此文件输出流中。
//b - 要写入的字节数组
public void write(byte[] b)
//将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
//b - 数据。
//off - 数据中的起始偏移量。
//len - 要写入的字节数。
public void write(byte[] b,int off,int len)
3.字节输出流操作步骤
①创建字节输出流对象
②调用write()方法
③释放资源
案例代码:
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
// OutputStream os = new FileOutputStream("fos2.txt"); // 多态
FileOutputStream fos = new FileOutputStream("fos2.txt");
// 调用write()方法
String str = "abcdebcd";
byte[] bys=str.getBytes();
fos.write(bys);
//public void write(byte[] b,int off,int len):写一个字节数组的一部分
//fos.write(bys,0,bys.length);
//释放资源
fos.close();
}
}
4.创建字节输出流到底做了哪些事情?
①数据写成功后,为什么要close()?
程序里打开的文件IO资源,占用内存,垃圾回收机制无法回收该资源,所以应该手动关闭文件IO资源(close()方法),释放内存。
②如何实现数据的换行?
为什么现在没有换行呢?因为你只写了字节数据,并没有写入换行符号。写入换行符号即可换行。
③不同的系统针对不同的换行符号识别是不一样的?
windows: \r\n
linux: \n
Mac: \r
而一些常见的个高级记事本,是可以识别任意换行符号的。
④如何实现数据的追加写入?
用构造方法带第二个参数是true的情况即可。
//此程序执行多遍可以看到每次写入文件的内容都是在原来文件的基础上添加的
public static void main(String[] args) {
// 加入异常处理的IO流操作
try {
//创建文件输出流
//不写默认是false
FileOutputStream fos = new FileOutputStream("d://abc/fos4.txt",true);
fos.write("java".getBytes());//2 把"java"字符串转换成字节数组,再保存到奥文件中
fos.close();//关闭资源
} catch (FileNotFoundException e) {
//如果创建文件输出流时,路径中制定的文件路径不存在,或者文件夹路径不存在则
e.printStackTrace();
} catch (IOException e) {
//如果文件时只读属性会运行到这里
e.printStackTrace();
}
}
5.字节流写数据加入异常处理★★★
public static void main(String[] args) {
// 加入异常处理的IO流操作
try {
//创建文件输出流
//不写默认是false
FileOutputStream fos = new FileOutputStream("d://abc/fos4.txt");//1
fos.write("java".getBytes());//2 把"java"字符串转换成字节数组,再保存到奥文件中
fos.close();//关闭资源
} catch (FileNotFoundException e) {
//如果创建文件输出流时,路径中制定的文件路径不存在,或者文件夹路径不存在则
e.printStackTrace();
} catch (IOException e) {
//如果文件时只读属性会运行到这里
e.printStackTrace();
}
}
以上代码如果d盘中没有abc文件夹,执行到注释1位置后发生异常,并被第一个catch捕获.并打印出:FileNotFoundException 文件找不到异常
如果d盘中存在文件夹abc,也存在fos4.txt,但是fos4,txt文件有只读属性, 则会执行到注释2的位置发生异常,并被第二个catch捕获,并打印出: IOException IO异常。
*注意:
以上两种异常,任意一个发生,都会导致代码不能执行到close方法, 所以FileOutputStream资源不能释放。
改进型实例:无论发生什么异常,FileOutputStream一定会关闭。
IO字节流写数据加入异常处理的标准格式:
public static void main(String[] args) {
//为了在finally里面能够看到该对象就必须定义到外面,为了访问不出问题,还必须给初始化值
FileOutputStream fos = null;
try {
fos = new FileOutputStream("fos4.txt");
fos.write("java".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 如果fos不是null,才需要close()
if (fos != null) {
// 为了保证close()一定会执行,就放到这里了
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
四、字节流读取数据
1.InputStream的构造方法和成员方法★★★
构造方法:InputStream()
子类:FileInputStream
FileInputStream的构造方法
//通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定
//file - 为了进行读取而打开的文件。
FileInputStream(File file)
//通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定
//name - 与系统有关的文件名
FileInputStream(String name)
FileInputStream的成员方法
//从输入流中读取数据的下一个字节,返回值实际是一个byte字节数据
public int read()
//从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中,返回值是读取到的字节个数
//b - 存储读入数据的缓冲区
public int read(byte[] b)
//将输入流中最多 len 个数据字节读入 byte 数组
//b - 读入数据的缓冲区。
//off - 数组 b 中将写入数据的初始偏移量。
//len - 要读取的最大字节数。
public int read(byte[] b, int off, int len)
案例分析:通过字节输入流读取文件中的字符串。
案例代码:
public static void main(String[] args){
// 创建字节输入流对象
FileInputStream fis = null;
try {
fis = new FileInputStream("d://abc/fis.txt");
// 数组的长度一般是1024或者1024的整数倍
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
// 释放资源
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.读取数据的三个方法区别★★★
1.public int read() 一次读取一个字节, 返回值为读取到的字节。
2.public int read(byte[] b) 一次读取多个字节(最多不能超过b数组的长度个字节),并把读取到的字节放入字节缓冲区b中,返回值为读取到的字节个数。
3.public int read(byte[] b, int offset, int len) 一次读取len个字节, 并把读取到的数据按照偏移量offset存储到b数组中,返回值为读取到的字节个数。
如果文件中有1000个字节,字节缓冲区b为100. 第一种读取方式,要读取1000次,第二种读取方式读取10次.第三种读取方式根据制定的每次读取字节个数决定读取次数.。
第二种方式实际是第三种方式的一个个例: read(byte[] b, 0, b.length) 就是第第二种方法了。
使用三种不同的读取方法读取同一个文件举例:
案例一:
案例分析:
1.创建字节输入流,读取一个有1000个字节的文件
2.使用public int read()方法,一次读取1个字节
3.循环读取,需要循环1000次
案例代码:
public static void main(String[] args){
// 创建字节输入流对象
FileInputStream fis = null;
try {
//d://abc/fis.txt文件中有1000个字节
fis = new FileInputStream("d://abc/fis.txt");
int c;
//一次读取一个字节,要读取1000次才能读取完毕
while((c = fis.read())!=-1){
System.out.println((char)c);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
// 释放资源
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
案例二:
案例分析:
1.创建字节输入流,读取一个有1000个字节的文件
2.使用public int read(byte[] b)方法,一次读取100个字节
3.循环读取,需要循环10次
案例代码:
public static void main(String[] args){
// 创建字节输入流对象
FileInputStream fis = null;
try {
//d://abc/fis.txt文件中有1000个字节
fis = new FileInputStream("d://abc/fis.txt");
// 定义缓冲区数组,大小为100个字节
byte[] bys = new byte[100];
int len = 0;
//一次读取100个字节,循环10次结束循环
while ((len = fis.read(bys)) != -1) {
System.out.println(new String(bys, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
// 释放资源
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
案例三:
案例分析:
1.创建字节输入流,读取一个有1000个字节的文件
2.使用public int read(byte[] b, int off, int len)方法,一次读取50个字节
3.循环读取,需要循环20次
案例代码:
public static void main(String[] args){
// 创建字节输入流对象
FileInputStream fis = null;
try {
//d://abc/fis.txt文件中有1000个字节
fis = new FileInputStream("d://abc/fis.txt");
// 定义缓冲区数组,大小为100个字节
byte[] bys = new byte[100];
int len = 0;
//一次读取50个字节,循环20次结束循环
while ((len = fis.read(bys,0,50)) != -1) {
System.out.println(new String(bys, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
// 释放资源
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
案例分析:
1.创建字节输入流对象
2.创建字节输出流对象
3.创建字节缓冲区,大小为1024(1k)
4.循环读取文件,一次读取1024个字节,存入缓冲区,并记录循环每次读取的字节数
5.在循环过程中把存储在缓冲区的数据再写入新的文件中,每次读取多少个字节写入多少个字节
使用public int read(byte[] b, int off, int len)方法,可以控制每次写入的字节个数
6.循环完毕关闭字节输出缓冲流
7.关闭字节输入缓冲流
案例代码:
public static void main(String[] args) {
//创建字节输入缓冲流对象
FileInputStream bri = null;
FileOutputStream bro = null;
try {
bri = new FileInputStream("D://abc/ab.mp4");
//创建字节输出缓冲流对象
bro = new FileOutputStream("D://abc/cd.mp4");
//创建缓冲区数组
byte[] buf = new byte[1024];
int len=0;//用来记录读取数据字节个数
while((len = bri.read(buf))!=0-1){
bro.write(buf,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源, 后开先关原则
if(bro!=null){
try {
bro.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bri!=null){
try {
bri.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
五、字节缓冲流
1.字节缓冲流的概述
计算机对磁盘的操作比对内存操作速度慢了很多,如果保存1000个字节数据,我们一次操作一个字节,操作1000次,会比一次操作1000个字节,只操作一次慢了很多。字节缓冲流,就是在内存中开辟出足够大的内存,在满足一定条件时,才去操作磁盘,这样就减少了对磁盘的操作次数,提升了IO流的工作效率。
字节输出缓冲流: 把数据先写入缓冲区,当缓冲区满了以后,或者我们调用方法flush()时,系统把缓冲区数据一次性写入磁盘。
字节读取缓冲流: 系统开辟缓冲区,一次把缓冲区读满,或者把文件中的数据读取完毕时,才把数据交给我们用户。
2.BufferedOutputStream(字节缓冲输出流)相关构造方法和成员方法
BufferedOutputStream自己创建了一个字节数据组缓冲区,默认大小为8192个字节,当我们调用write()方法时,数据并没有直接写入文件,而是写入了缓冲区.当我们写满缓冲区时,或者调用flush()方法时数据才会真正的写入了文件中.通过缓冲区方式,减少了文件操作,所以提升了效率.但是写操作完成后一定要调用flush()方法,或者关闭资源,close()方法中也调用了flush()方法.
案例分析:使用BufferedOutputStream包装一个字节输出流,并保存字符串到文件
构造方法:
//创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
BufferedOutputStream(OutputStream out)
案例代码:
public static void main(String[] args){
// BufferedOutputStream(OutputStream out);
BufferedOutputStream bos = null;
try {
//BufferedOutputStream开辟了缓冲区,默认大小为8192个字节 8k
bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
// 写数据
bos.write("hello".getBytes());
//刷新缓冲区,这个时候数据才会真真写入文件
bos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
// 释放资源
try {
//关闭资源,在这个方法中也调用了flush()方法
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.BufferedInputStream字节缓冲输入流相关构造方法和成员方法★★★
BufferedInputStream创建了内存缓冲区(8k),当我们读取数据的时候,系统会先把数据读取到内存缓冲区,知道内存缓冲区满了,或者文件数据读取完毕才会把数据交给我们用户使用,这样我们读取磁盘数据就转换成了读取内存数据,提高了效率,但是耗费了内存空间(以空间换时间)。
案例分析:使用BufferedInputStream包装一个字节输入流,读取一个字符串文件到内存。并打印输出。
构造方法:
//创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
BufferedInputStream(InputStream in)
案例代码:
public static void main(String[] args){
//
BufferedInputStream bis = null;
try {
//BufferedInputStream创建了内存缓冲区8k 用来预存储读取到的数据
//bos.txt文件中存储的hello
bis = new BufferedInputStream(new FileInputStream("bos.txt"));
byte[] bys = new byte[1024];
int len = 0;
//实际我们这里读取到的数据是从内存中读取的,由系统批量读取到内存中,我们再从内存中读取减少了对磁盘操作
while ((len = bis.read(bys)) != -1) {
System.out.print(new String(bys, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.使用字节缓冲流完成文件复制★★★
案例分析:
1.创建字节输入缓冲流对象
2.创建字节输出缓冲流对象
3.创建字节缓冲区,大小为1024(1k)
4.循环读取文件,一次读取1024个字节,存入缓冲区,并记录循环每次读取的字节数
5.在循环过程中把存储在缓冲区的数据再写入新的文件中,每次读取多少个字节写入多少个字节
使用public int read(byte[] b, int off, int len)方法,可以控制每次写入的字节个数
6.循环完毕关闭字节输出缓冲流
7.关闭字节输入缓冲流
案例代码:
public static void main(String[] args) {
//创建字节输入缓冲流对象
BufferedInputStream bri = null;
BufferedOutputStream bro = null;
try {
bri = new BufferedInputStream(new FileInputStream("D://abc/ab.mp4"));
//创建字节输出缓冲流对象
bro = new BufferedOutputStream(new FileOutputStream("D://abc/cd.mp4"));
//创建缓冲区数组
byte[] buf = new byte[1024];
int len=0;//用来记录读取数据字节个数
while((len = bri.read(buf))!=0-1){
bro.write(buf,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源, 后开先关原则
if(bro!=null){
try {
bro.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bri!=null){
try {
bri.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
网友评论